/* * Copyright (C) 2015 Vincent Breitmoser * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.view.MenuItem; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class BackupActivity extends BaseActivity { public static final String EXTRA_MASTER_KEY_IDS = "master_key_ids"; public static final String EXTRA_SECRET = "export_secret"; @Override protected void initLayout() { setContentView(R.layout.backup_activity); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // noinspection ConstantConditions, we know this activity has an action bar getSupportActionBar().setDisplayHomeAsUpEnabled(true); if (savedInstanceState == null) { Intent intent = getIntent(); boolean exportSecret = intent.getBooleanExtra(EXTRA_SECRET, false); long[] masterKeyIds = intent.getLongArrayExtra(EXTRA_MASTER_KEY_IDS); Fragment frag = BackupCodeFragment.newInstance(masterKeyIds, exportSecret, true); FragmentManager fragMan = getSupportFragmentManager(); fragMan.beginTransaction() .setCustomAnimations(0, 0) .replace(R.id.content_frame, frag) .commit(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: FragmentManager fragMan = getSupportFragmentManager(); // pop from back stack, or if nothing was on there finish activity if ( ! fragMan.popBackStackImmediate()) { finish(); } return true; } return super.onOptionsItemSelected(item); } /** * Overridden in RemoteBackupActivity */ public void handleBackupOperation(CryptoInputParcel inputParcel) { // only used for RemoteBackupActivity } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 24423 Content-Disposition: inline; filename="BackupCodeFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "012ea0610b76279709b413404a00deb286f02c90" /* * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2016 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.SuppressLint; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager.OnBackStackChangedListener; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.widget.EditText; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.ExportResult; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.BackupKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Passphrase; import java.io.File; import java.io.IOException; import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Random; public class BackupCodeFragment extends CryptoOperationFragment implements OnBackStackChangedListener { public static final String ARG_BACKUP_CODE = "backup_code"; public static final String BACK_STACK_INPUT = "state_display"; public static final String ARG_EXPORT_SECRET = "export_secret"; public static final String ARG_EXECUTE_BACKUP_OPERATION = "execute_backup_operation"; public static final String ARG_MASTER_KEY_IDS = "master_key_ids"; public static final String ARG_CURRENT_STATE = "current_state"; public static final int REQUEST_SAVE = 1; public static final String ARG_BACK_STACK = "back_stack"; // https://github.com/open-keychain/open-keychain/wiki/Backups // excludes 0 and O private static final char[] mBackupCodeAlphabet = new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; // argument variables private boolean mExportSecret; private long[] mMasterKeyIds; String mBackupCode; private boolean mExecuteBackupOperation; private EditText[] mCodeEditText; private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator; private Integer mBackStackLevel; private Uri mCachedBackupUri; private boolean mShareNotSave; private boolean mDebugModeAcceptAnyCode; public static BackupCodeFragment newInstance(long[] masterKeyIds, boolean exportSecret, boolean executeBackupOperation) { BackupCodeFragment frag = new BackupCodeFragment(); Bundle args = new Bundle(); args.putString(ARG_BACKUP_CODE, generateRandomBackupCode()); args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds); args.putBoolean(ARG_EXPORT_SECRET, exportSecret); args.putBoolean(ARG_EXECUTE_BACKUP_OPERATION, executeBackupOperation); frag.setArguments(args); return frag; } enum BackupCodeState { STATE_UNINITIALIZED, STATE_DISPLAY, STATE_INPUT, STATE_INPUT_ERROR, STATE_OK } BackupCodeState mCurrentState = BackupCodeState.STATE_UNINITIALIZED; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Constants.DEBUG) { setHasOptionsMenu(true); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); if (Constants.DEBUG) { inflater.inflate(R.menu.backup_fragment_debug_menu, menu); } } @SuppressLint("SetTextI18n") @Override public boolean onOptionsItemSelected(MenuItem item) { if (Constants.DEBUG && item.getItemId() == R.id.debug_accept_any_log) { boolean newCheckedState = !item.isChecked(); item.setChecked(newCheckedState); mDebugModeAcceptAnyCode = newCheckedState; if (newCheckedState && TextUtils.isEmpty(mCodeEditText[0].getText())) { mCodeEditText[0].setText("ABCD"); mCodeEditText[1].setText("EFGH"); mCodeEditText[2].setText("IJKL"); mCodeEditText[3].setText("MNOP"); mCodeEditText[4].setText("QRST"); mCodeEditText[5].setText("UVWX"); Notify.create(getActivity(), "Actual backup code is all 'A's", Style.WARN).show(); } return true; } return super.onOptionsItemSelected(item); } void switchState(BackupCodeState state, boolean animate) { switch (state) { case STATE_UNINITIALIZED: throw new AssertionError("can't switch to uninitialized state, this is a bug!"); case STATE_DISPLAY: mTitleAnimator.setDisplayedChild(0, animate); mStatusAnimator.setDisplayedChild(0, animate); mCodeFieldsAnimator.setDisplayedChild(0, animate); break; case STATE_INPUT: mTitleAnimator.setDisplayedChild(1, animate); mStatusAnimator.setDisplayedChild(1, animate); mCodeFieldsAnimator.setDisplayedChild(1, animate); for (EditText editText : mCodeEditText) { editText.setText(""); } pushBackStackEntry(); break; case STATE_INPUT_ERROR: { mTitleAnimator.setDisplayedChild(1, false); mStatusAnimator.setDisplayedChild(2, animate); mCodeFieldsAnimator.setDisplayedChild(1, false); hideKeyboard(); if (animate) { @ColorInt int black = mCodeEditText[0].getCurrentTextColor(); @ColorInt int red = getResources().getColor(R.color.android_red_dark); animateFlashText(mCodeEditText, black, red, false); } break; } case STATE_OK: { mTitleAnimator.setDisplayedChild(2, animate); mCodeFieldsAnimator.setDisplayedChild(1, false); if (mExecuteBackupOperation) { mStatusAnimator.setDisplayedChild(3, animate); } else { mStatusAnimator.setDisplayedChild(1, animate); } hideKeyboard(); for (EditText editText : mCodeEditText) { editText.setEnabled(false); } @ColorInt int green = getResources().getColor(R.color.android_green_dark); if (animate) { @ColorInt int black = mCodeEditText[0].getCurrentTextColor(); animateFlashText(mCodeEditText, black, green, true); } else { for (TextView textView : mCodeEditText) { textView.setTextColor(green); } } popBackStackNoAction(); // special case for remote API, see RemoteBackupActivity if (!mExecuteBackupOperation) { // wait for animation to finish... final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { startBackup(); } }, 2000); } break; } } mCurrentState = state; } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.backup_code_fragment, container, false); Bundle args = getArguments(); mBackupCode = args.getString(ARG_BACKUP_CODE); mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS); mExportSecret = args.getBoolean(ARG_EXPORT_SECRET); mExecuteBackupOperation = args.getBoolean(ARG_EXECUTE_BACKUP_OPERATION, true); mCodeEditText = new EditText[6]; mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); mCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5); mCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6); { TextView[] codeDisplayText = new TextView[6]; codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1); codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2); codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3); codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4); codeDisplayText[4] = (TextView) view.findViewById(R.id.backup_code_display_5); codeDisplayText[5] = (TextView) view.findViewById(R.id.backup_code_display_6); // set backup code in code TextViews char[] backupCode = mBackupCode.toCharArray(); for (int i = 0; i < codeDisplayText.length; i++) { codeDisplayText[i].setText(backupCode, i * 5, 4); } // set background to null in TextViews - this will retain padding from EditText style! for (TextView textView : codeDisplayText) { // noinspection deprecation, setBackground(Drawable) is API level >=16 textView.setBackgroundDrawable(null); } } setupEditTextFocusNext(mCodeEditText); setupEditTextSuccessListener(mCodeEditText); mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator); mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator); mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator); View backupInput = view.findViewById(R.id.button_backup_input); backupInput.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { switchState(BackupCodeState.STATE_INPUT, true); } }); view.findViewById(R.id.button_backup_save).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mShareNotSave = false; startBackup(); } }); view.findViewById(R.id.button_backup_share).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mShareNotSave = true; startBackup(); } }); view.findViewById(R.id.button_backup_back).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { FragmentManager fragMan = getFragmentManager(); if (fragMan != null) { fragMan.popBackStack(); } } }); view.findViewById(R.id.button_faq).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showFaq(); } }); return view; } private void showFaq() { HelpActivity.startHelpActivity(getActivity(), HelpActivity.TAB_FAQ); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (savedInstanceState != null) { int savedBackStack = savedInstanceState.getInt(ARG_BACK_STACK); if (savedBackStack >= 0) { mBackStackLevel = savedBackStack; // unchecked use, we know that this one is available in onViewCreated getFragmentManager().addOnBackStackChangedListener(this); } BackupCodeState savedState = BackupCodeState.values()[savedInstanceState.getInt(ARG_CURRENT_STATE)]; switchState(savedState, false); } else if (mCurrentState == BackupCodeState.STATE_UNINITIALIZED) { switchState(BackupCodeState.STATE_DISPLAY, true); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(ARG_CURRENT_STATE, mCurrentState.ordinal()); outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel); } private void setupEditTextSuccessListener(final EditText[] backupCodes) { for (EditText backupCode : backupCodes) { backupCode.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (s.length() > 4) { throw new AssertionError("max length of each field is 4!"); } boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; boolean partIsComplete = s.length() == 4; if (!inInputState || !partIsComplete) { return; } checkIfCodeIsCorrect(); } }); } } private void checkIfCodeIsCorrect() { if (Constants.DEBUG && mDebugModeAcceptAnyCode) { switchState(BackupCodeState.STATE_OK, true); return; } StringBuilder backupCodeInput = new StringBuilder(26); for (EditText editText : mCodeEditText) { if (editText.getText().length() < 4) { return; } backupCodeInput.append(editText.getText()); backupCodeInput.append('-'); } backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); // if they don't match, do nothing if (backupCodeInput.toString().equals(mBackupCode)) { switchState(BackupCodeState.STATE_OK, true); return; } switchState(BackupCodeState.STATE_INPUT_ERROR, true); } private static void animateFlashText( final TextView[] textViews, int color1, int color2, boolean staySecondColor) { ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2); anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { for (TextView textView : textViews) { textView.setTextColor((Integer) animator.getAnimatedValue()); } } }); anim.setRepeatMode(ValueAnimator.REVERSE); anim.setRepeatCount(staySecondColor ? 4 : 5); anim.setDuration(180); anim.setInterpolator(new AccelerateInterpolator()); anim.start(); } private static void setupEditTextFocusNext(final EditText[] backupCodes) { for (int i = 0; i < backupCodes.length - 1; i++) { final int next = i + 1; backupCodes[i].addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean inserting = before < count; boolean cursorAtEnd = (start + count) == 4; if (inserting && cursorAtEnd) { backupCodes[next].requestFocus(); } } @Override public void afterTextChanged(Editable s) { } }); } } private void pushBackStackEntry() { if (mBackStackLevel != null) { return; } FragmentManager fragMan = getFragmentManager(); mBackStackLevel = fragMan.getBackStackEntryCount(); fragMan.beginTransaction().addToBackStack(BACK_STACK_INPUT).commit(); fragMan.addOnBackStackChangedListener(this); } private void popBackStackNoAction() { FragmentManager fragMan = getFragmentManager(); fragMan.removeOnBackStackChangedListener(this); fragMan.popBackStackImmediate(BACK_STACK_INPUT, FragmentManager.POP_BACK_STACK_INCLUSIVE); mBackStackLevel = null; } @Override public void onBackStackChanged() { FragmentManager fragMan = getFragmentManager(); if (mBackStackLevel != null && fragMan.getBackStackEntryCount() == mBackStackLevel) { fragMan.removeOnBackStackChangedListener(this); switchState(BackupCodeState.STATE_DISPLAY, true); mBackStackLevel = null; } } private void startBackup() { FragmentActivity activity = getActivity(); if (activity == null) { return; } String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); String filename = Constants.FILE_ENCRYPTED_BACKUP_PREFIX + date + (mExportSecret ? Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_SECRET : Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_PUBLIC); Passphrase passphrase = new Passphrase(mBackupCode); if (Constants.DEBUG && mDebugModeAcceptAnyCode) { passphrase = new Passphrase("AAAA-AAAA-AAAA-AAAA-AAAA-AAAA"); } // if we don't want to execute the actual operation outside of this activity, drop out here if (!mExecuteBackupOperation) { ((BackupActivity) getActivity()).handleBackupOperation(new CryptoInputParcel(passphrase)); return; } if (mCachedBackupUri == null) { mCachedBackupUri = TemporaryFileProvider.createFile(activity, filename, Constants.MIME_TYPE_ENCRYPTED_ALTERNATE); cryptoOperation(new CryptoInputParcel(passphrase)); return; } if (mShareNotSave) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(Constants.MIME_TYPE_ENCRYPTED_ALTERNATE); intent.putExtra(Intent.EXTRA_STREAM, mCachedBackupUri); startActivity(intent); } else { saveFile(filename, false); } } private void saveFile(final String filename, boolean overwrite) { FragmentActivity activity = getActivity(); if (activity == null) { return; } // for kitkat and above, we have the document api if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { FileHelper.saveDocument(this, filename, Constants.MIME_TYPE_ENCRYPTED_ALTERNATE, REQUEST_SAVE); return; } File file = new File(Constants.Path.APP_DIR, filename); if (!overwrite && file.exists()) { Notify.create(activity, R.string.snack_backup_exists, Style.WARN, new ActionListener() { @Override public void onAction() { saveFile(filename, true); } }, R.string.snack_btn_overwrite).show(); return; } try { FileHelper.copyUriData(activity, mCachedBackupUri, Uri.fromFile(file)); Notify.create(activity, R.string.snack_backup_saved_dir, Style.OK).show(); } catch (IOException e) { Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != REQUEST_SAVE) { super.onActivityResult(requestCode, resultCode, data); return; } if (resultCode != FragmentActivity.RESULT_OK) { return; } FragmentActivity activity = getActivity(); if (activity == null) { return; } try { Uri outputUri = data.getData(); FileHelper.copyUriData(activity, mCachedBackupUri, outputUri); Notify.create(activity, R.string.snack_backup_saved, Style.OK).show(); } catch (IOException e) { Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show(); } } @Nullable @Override public BackupKeyringParcel createOperationInput() { return new BackupKeyringParcel(mMasterKeyIds, mExportSecret, true, mCachedBackupUri); } @Override public void onCryptoOperationSuccess(ExportResult result) { startBackup(); } @Override public void onCryptoOperationError(ExportResult result) { result.createNotify(getActivity()).show(); mCachedBackupUri = null; } @Override public void onCryptoOperationCancelled() { mCachedBackupUri = null; } /** * Generate backup code using format defined in * https://github.com/open-keychain/open-keychain/wiki/Backups */ @NonNull private static String generateRandomBackupCode() { Random r = new SecureRandom(); // simple generation of a 24 character backup code StringBuilder code = new StringBuilder(28); for (int i = 0; i < 24; i++) { if (i == 4 || i == 8 || i == 12 || i == 16 || i == 20) { code.append('-'); } code.append(mBackupCodeAlphabet[r.nextInt(mBackupCodeAlphabet.length)]); } return code.toString(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 9943 Content-Disposition: inline; filename="BackupRestoreFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "e8c4196f210c099b7307366408445c25ff92ec27" /* * Copyright (C) 2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import java.util.Iterator; import android.app.Activity; import android.content.ContentResolver; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FileHelper; public class BackupRestoreFragment extends Fragment { // masterKeyId & subKeyId for multi-key export private Iterator> mIdsForRepeatAskPassphrase; private static final int REQUEST_REPEAT_PASSPHRASE = 0x00007002; private static final int REQUEST_CODE_INPUT = 0x00007003; @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.backup_restore_fragment, container, false); View backupAll = view.findViewById(R.id.backup_all); View backupPublicKeys = view.findViewById(R.id.backup_public_keys); final View restore = view.findViewById(R.id.restore); backupAll.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { exportToFile(true); } }); backupPublicKeys.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { exportToFile(false); } }); restore.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { restore(); } }); return view; } private void exportToFile(boolean includeSecretKeys) { FragmentActivity activity = getActivity(); if (activity == null) { return; } if (!includeSecretKeys) { startBackup(false); return; } new AsyncTask>>() { @Override protected ArrayList> doInBackground(ContentResolver... resolver) { ArrayList> askPassphraseIds = new ArrayList<>(); Cursor cursor = resolver[0].query( KeyRings.buildUnifiedKeyRingsUri(), new String[]{ KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET, }, KeyRings.HAS_SECRET + " != 0", null, null); try { if (cursor != null) { while (cursor.moveToNext()) { SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1)); switch (secretKeyType) { // all of these make no sense to ask case PASSPHRASE_EMPTY: case DIVERT_TO_CARD: case UNAVAILABLE: continue; case GNU_DUMMY: { Long masterKeyId = cursor.getLong(0); Long subKeyId = getFirstSubKeyWithPassphrase(masterKeyId, resolver[0]); if(subKeyId != null) { askPassphraseIds.add(new Pair<>(masterKeyId, subKeyId)); } continue; } default: { long masterKeyId = cursor.getLong(0); askPassphraseIds.add(new Pair<>(masterKeyId, masterKeyId)); } } } } } finally { if (cursor != null) { cursor.close(); } } return askPassphraseIds; } private Long getFirstSubKeyWithPassphrase(long masterKeyId, ContentResolver resolver) { Cursor cursor = resolver.query( KeychainContract.Keys.buildKeysUri(masterKeyId), new String[]{ Keys.KEY_ID, Keys.HAS_SECRET, }, Keys.HAS_SECRET + " != 0", null, null); try { if (cursor != null) { while(cursor.moveToNext()) { SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1)); switch (secretKeyType) { case PASSPHRASE_EMPTY: case DIVERT_TO_CARD: case UNAVAILABLE: return null; case GNU_DUMMY: continue; default: { return cursor.getLong(0); } } } } } finally { if (cursor != null) { cursor.close(); } } return null; } @Override protected void onPostExecute(ArrayList> askPassphraseIds) { super.onPostExecute(askPassphraseIds); FragmentActivity activity = getActivity(); if (activity == null) { return; } mIdsForRepeatAskPassphrase = askPassphraseIds.iterator(); if (mIdsForRepeatAskPassphrase.hasNext()) { startPassphraseActivity(); return; } startBackup(true); } }.execute(activity.getContentResolver()); } private void startPassphraseActivity() { Activity activity = getActivity(); if (activity == null) { return; } Intent intent = new Intent(activity, PassphraseDialogActivity.class); Pair keyPair = mIdsForRepeatAskPassphrase.next(); long masterKeyId = keyPair.first; long subKeyId = keyPair.second; RequiredInputParcel requiredInput = RequiredInputParcel.createRequiredDecryptPassphrase(masterKeyId, subKeyId); requiredInput.mSkipCaching = true; intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_REPEAT_PASSPHRASE: { if (resultCode != Activity.RESULT_OK) { return; } if (mIdsForRepeatAskPassphrase.hasNext()) { startPassphraseActivity(); return; } startBackup(true); break; } case REQUEST_CODE_INPUT: { if (resultCode != Activity.RESULT_OK || data == null) { return; } Uri uri = data.getData(); if (uri == null) { Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); return; } Intent intent = new Intent(getActivity(), DecryptActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setData(uri); startActivity(intent); break; } default: { super.onActivityResult(requestCode, resultCode, data); } } } private void startBackup(boolean exportSecret) { Intent intent = new Intent(getActivity(), BackupActivity.class); intent.putExtra(BackupActivity.EXTRA_SECRET, exportSecret); startActivity(intent); } private void restore() { FileHelper.openDocument(this, null, "*/*", false, REQUEST_CODE_INPUT); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3022 Content-Disposition: inline; filename="CertifyFingerprintActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "c5528e40bc114b7c87c322b044fa1908b086761f" /* * Copyright (C) 2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.net.Uri; import android.os.Bundle; import android.view.View; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class CertifyFingerprintActivity extends BaseActivity { protected Uri mDataUri; public static final String EXTRA_ENABLE_WORD_CONFIRM = "enable_word_confirm"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDataUri = getIntent().getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Data missing. Should be uri of key!"); finish(); return; } boolean enableWordConfirm = getIntent().getBooleanExtra(EXTRA_ENABLE_WORD_CONFIRM, false); setFullScreenDialogClose(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); startFragment(savedInstanceState, mDataUri, enableWordConfirm); } @Override protected void initLayout() { setContentView(R.layout.certify_fingerprint_activity); } private void startFragment(Bundle savedInstanceState, Uri dataUri, boolean enableWordConfirm) { // 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. if (savedInstanceState != null) { return; } // Create an instance of the fragment CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri, enableWordConfirm); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() .replace(R.id.certify_fingerprint_fragment, frag) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8609 Content-Disposition: inline; filename="CertifyFingerprintFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "85be68505e784a240153de7a3cf045f6d3b9be97" /* * Copyright (C) 2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.experimental.SentenceConfirm; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; public class CertifyFingerprintFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { static final int REQUEST_CERTIFY = 1; public static final String ARG_DATA_URI = "uri"; public static final String ARG_ENABLE_PHRASES_CONFIRM = "enable_word_confirm"; private TextView mActionYes; private TextView mFingerprint; private TextView mIntro; private TextView mHeader; private static final int LOADER_ID_UNIFIED = 0; private Uri mDataUri; private boolean mEnablePhrasesConfirm; /** * Creates new instance of this fragment */ public static CertifyFingerprintFragment newInstance(Uri dataUri, boolean enablePhrasesConfirm) { CertifyFingerprintFragment frag = new CertifyFingerprintFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); args.putBoolean(ARG_ENABLE_PHRASES_CONFIRM, enablePhrasesConfirm); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer()); TextView actionNo = (TextView) view.findViewById(R.id.certify_fingerprint_button_no); mActionYes = (TextView) view.findViewById(R.id.certify_fingerprint_button_yes); mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint); mIntro = (TextView) view.findViewById(R.id.certify_fingerprint_intro); mHeader = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint_header); actionNo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getActivity().finish(); } }); mActionYes.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { certify(mDataUri); } }); return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } mEnablePhrasesConfirm = getArguments().getBoolean(ARG_ENABLE_PHRASES_CONFIRM); if (mEnablePhrasesConfirm) { mIntro.setText(R.string.certify_fingerprint_text_phrases); mHeader.setText(R.string.section_phrases); mActionYes.setText(R.string.btn_match_phrases); } loadData(dataUri); } private void loadData(Uri dataUri) { mDataUri = dataUri; Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); } static final String[] UNIFIED_PROJECTION = new String[]{ KeyRings._ID, KeyRings.FINGERPRINT, }; static final int INDEX_UNIFIED_FINGERPRINT = 1; public Loader onCreateLoader(int id, Bundle args) { setContentShown(false); switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } default: return null; } } public void onLoadFinished(Loader loader, Cursor data) { /* TODO better error handling? May cause problems when a key is deleted, * because the notification triggers faster than the activity closes. */ // Avoid NullPointerExceptions... if (data.getCount() == 0) { return; } // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); if (mEnablePhrasesConfirm) { displayWordConfirm(fingerprintBlob); } else { displayHexConfirm(fingerprintBlob); } break; } } } setContentShown(true); } private void displayHexConfirm(byte[] fingerprintBlob) { String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); } private void displayWordConfirm(byte[] fingerprintBlob) { // String fingerprint = ExperimentalWordConfirm.getWords(getActivity(), fingerprintBlob); String fingerprint; try { fingerprint = new SentenceConfirm(getActivity()).fromBytes(fingerprintBlob, 20); } catch (IOException e) { fingerprint = "-"; Log.e(Constants.TAG, "Problem when creating sentence!", e); } mFingerprint.setTextSize(18); mFingerprint.setTypeface(Typeface.DEFAULT, Typeface.BOLD); mFingerprint.setText(fingerprint); } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { } private void certify(Uri dataUri) { long keyId = 0; try { keyId = new ProviderHelper(getActivity()) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } Intent certifyIntent = new Intent(getActivity(), CertifyKeyActivity.class); certifyIntent.putExtras(getActivity().getIntent()); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{keyId}); startActivityForResult(certifyIntent, REQUEST_CERTIFY); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { // always just pass this one through if (requestCode == REQUEST_CERTIFY) { getActivity().setResult(resultCode, data); getActivity().finish(); return; } super.onActivityResult(requestCode, resultCode, data); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1441 Content-Disposition: inline; filename="CertifyKeyActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "09149716c3c70202bb2f3595e4b66d3dc5a92799" /* * Copyright (C) 2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * Copyright (C) 2011 Senecaso * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sufficientlysecure.keychain.ui; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; /** * Signs the specified public key with the specified secret master key */ public class CertifyKeyActivity extends BaseActivity { public static final String EXTRA_RESULT = "operation_result"; // For sending masterKeyIds to MultiUserIdsFragment to display list of keys public static final String EXTRA_KEY_IDS = MultiUserIdsFragment.EXTRA_KEY_IDS ; public static final String EXTRA_CERTIFY_KEY_ID = "certify_key_id"; @Override protected void initLayout() { setContentView(R.layout.certify_key_activity); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7326 Content-Disposition: inline; filename="CertifyKeyFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "ad39ff43d1020ea1953bc68cfdb99d1dff383997" /* * Copyright (C) 2013-2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.database.MatrixCursor; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ListView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; import java.util.Date; public class CertifyKeyFragment extends CachingCryptoOperationFragment { private CheckBox mUploadKeyCheckbox; private CertifyKeySpinner mCertifyKeySpinner; private MultiUserIdsFragment mMultiUserIdsFragment; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (savedInstanceState == null) { // preselect certify key id if given long certifyKeyId = getActivity().getIntent() .getLongExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, Constants.key.none); if (certifyKeyId != Constants.key.none) { try { CachedPublicKeyRing key = (new ProviderHelper(getActivity())) .getCachedPublicKeyRing(certifyKeyId); if (key.canCertify()) { mCertifyKeySpinner.setPreSelectedKeyId(certifyKeyId); } } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "certify certify check failed", e); } } } OperationResult result = getActivity().getIntent().getParcelableExtra(CertifyKeyActivity.EXTRA_RESULT); if (result != null) { // display result from import result.createNotify(getActivity()).show(); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.certify_key_fragment, null); mCertifyKeySpinner = (CertifyKeySpinner) view.findViewById(R.id.certify_key_spinner); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.sign_key_upload_checkbox); mMultiUserIdsFragment = (MultiUserIdsFragment) getChildFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); // make certify image gray, like action icons ImageView vActionCertifyImage = (ImageView) view.findViewById(R.id.certify_key_action_certify_image); vActionCertifyImage.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); View vCertifyButton = view.findViewById(R.id.certify_key_certify_button); vCertifyButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId(); if (selectedKeyId == Constants.key.none) { Notify.create(getActivity(), getString(R.string.select_key_to_certify), Notify.Style.ERROR).show(); } else { cryptoOperation(new CryptoInputParcel(new Date())); } } }); // If this is a debug build, don't upload by default if (Constants.DEBUG) { mUploadKeyCheckbox.setChecked(false); } return view; } @Override public CertifyActionsParcel createOperationInput() { // Bail out if there is not at least one user id selected ArrayList certifyActions = mMultiUserIdsFragment.getSelectedCertifyActions(); if (certifyActions.isEmpty()) { Notify.create(getActivity(), "No identities selected!", Notify.Style.ERROR).show(); return null; } long selectedKeyId = mCertifyKeySpinner.getSelectedKeyId(); // fill values for this action CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId); actionsParcel.mCertifyActions.addAll(certifyActions); if (mUploadKeyCheckbox.isChecked()) { actionsParcel.keyServerUri = Preferences.getPreferences(getActivity()) .getPreferredKeyserver(); } // cached for next cryptoOperation loop cacheActionsParcel(actionsParcel); return actionsParcel; } @Override public void onQueuedOperationSuccess(CertifyResult result) { // protected by Queueing*Fragment Activity activity = getActivity(); Intent intent = new Intent(); intent.putExtra(CertifyResult.EXTRA_RESULT, result); activity.setResult(Activity.RESULT_OK, intent); activity.finish(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3229 Content-Disposition: inline; filename="ConsolidateDialogActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "ff5fb7cca6c09a81aa6ecb6dac4aad4b931cca2a" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; /** * We can not directly create a dialog on the application context. * This activity encapsulates a DialogFragment to emulate a dialog. */ public class ConsolidateDialogActivity extends FragmentActivity implements CryptoOperationHelper.Callback { public static final String EXTRA_CONSOLIDATE_RECOVERY = "consolidate_recovery"; private CryptoOperationHelper mConsolidateOpHelper; private boolean mRecovery; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // this activity itself has no content view (see manifest) boolean recovery = getIntent().getBooleanExtra(EXTRA_CONSOLIDATE_RECOVERY, false); consolidateRecovery(recovery); } private void consolidateRecovery(boolean recovery) { mRecovery = recovery; mConsolidateOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing); mConsolidateOpHelper.cryptoOperation(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (mConsolidateOpHelper != null) { mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data); } } @Override public ConsolidateInputParcel createOperationInput() { return new ConsolidateInputParcel(mRecovery); } @Override public void onCryptoOperationSuccess(ConsolidateResult result) { // don't care about result (for now?) ConsolidateDialogActivity.this.finish(); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(ConsolidateResult result) { // don't care about result (for now?) ConsolidateDialogActivity.this.finish(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11612 Content-Disposition: inline; filename="CreateKeyActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b719173688ac1ca9874f8bef968c836e0930478b" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.nfc.NfcAdapter; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; public class CreateKeyActivity extends BaseSecurityTokenActivity { public static final String EXTRA_NAME = "name"; public static final String EXTRA_EMAIL = "email"; public static final String EXTRA_FIRST_TIME = "first_time"; public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails"; public static final String EXTRA_PASSPHRASE = "passphrase"; public static final String EXTRA_CREATE_SECURITY_TOKEN = "create_yubi_key"; public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin"; public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin"; public static final String EXTRA_SECURITY_TOKEN_USER_ID = "nfc_user_id"; public static final String EXTRA_SECURITY_TOKEN_AID = "nfc_aid"; public static final String EXTRA_SECURITY_FINGERPRINTS = "nfc_fingerprints"; public static final String FRAGMENT_TAG = "currentFragment"; String mName; String mEmail; ArrayList mAdditionalEmails; Passphrase mPassphrase; boolean mFirstTime; boolean mCreateSecurityToken; Passphrase mSecurityTokenPin; Passphrase mSecurityTokenAdminPin; Fragment mCurrentFragment; byte[] mScannedFingerprints; byte[] mSecurityTokenAid; String mSecurityTokenUserId; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // React on NDEF_DISCOVERED from Manifest // NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { mNfcTagDispatcher.interceptIntent(getIntent()); setTitle(R.string.title_manage_my_keys); // done return; } // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { // Restore value of members from saved state mName = savedInstanceState.getString(EXTRA_NAME); mEmail = savedInstanceState.getString(EXTRA_EMAIL); mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS); mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE); mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); mCreateSecurityToken = savedInstanceState.getBoolean(EXTRA_CREATE_SECURITY_TOKEN); mSecurityTokenPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_PIN); mSecurityTokenAdminPin = savedInstanceState.getParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN); mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); } else { Intent intent = getIntent(); // Initialize members with default values for a new instance mName = intent.getStringExtra(EXTRA_NAME); mEmail = intent.getStringExtra(EXTRA_EMAIL); mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false); if (intent.hasExtra(EXTRA_SECURITY_FINGERPRINTS)) { byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_FINGERPRINTS); String nfcUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID); byte[] nfcAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID); if (containsKeys(nfcFingerprints)) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( nfcFingerprints, nfcAid, nfcUserId); loadFragment(frag, FragAction.START); setTitle(R.string.title_import_keys); } else { Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); loadFragment(frag, FragAction.START); setTitle(R.string.title_manage_my_keys); } // done return; } // normal key creation CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance(); loadFragment(frag, FragAction.START); } if (mFirstTime) { setTitle(R.string.app_name); mToolbar.setNavigationIcon(null); mToolbar.setNavigationOnClickListener(null); } else { setTitle(R.string.title_manage_my_keys); } } @Override protected void doSecurityTokenInBackground() throws IOException { if (mCurrentFragment instanceof SecurityTokenListenerFragment) { ((SecurityTokenListenerFragment) mCurrentFragment).doSecurityTokenInBackground(); return; } mScannedFingerprints = mSecurityTokenHelper.getFingerprints(); mSecurityTokenAid = mSecurityTokenHelper.getAid(); mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); } @Override protected void onSecurityTokenPostExecute() { if (mCurrentFragment instanceof SecurityTokenListenerFragment) { ((SecurityTokenListenerFragment) mCurrentFragment).onSecurityTokenPostExecute(); return; } // We don't want get back to wait activity mainly because it looks weird with otg token if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) { // hack from http://stackoverflow.com/a/11253987 CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = true; getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); CreateSecurityTokenWaitFragment.sDisableFragmentAnimations = false; } if (containsKeys(mScannedFingerprints)) { try { long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints); CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); ring.getMasterKeyId(); Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints); startActivity(intent); finish(); } catch (PgpKeyNotFoundException e) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId); loadFragment(frag, FragAction.TO_RIGHT); } } else { Fragment frag = CreateSecurityTokenBlankFragment.newInstance(); loadFragment(frag, FragAction.TO_RIGHT); } } private boolean containsKeys(byte[] scannedFingerprints) { if (scannedFingerprints == null) { return false; } // If all fingerprint bytes are 0, the card contains no keys. for (byte b : scannedFingerprints) { if (b != 0) { return true; } } return false; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(EXTRA_NAME, mName); outState.putString(EXTRA_EMAIL, mEmail); outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails); outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase); outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); outState.putBoolean(EXTRA_CREATE_SECURITY_TOKEN, mCreateSecurityToken); outState.putParcelable(EXTRA_SECURITY_TOKEN_PIN, mSecurityTokenPin); outState.putParcelable(EXTRA_SECURITY_TOKEN_ADMIN_PIN, mSecurityTokenAdminPin); } @Override protected void initLayout() { setContentView(R.layout.create_key_activity); } public enum FragAction { START, TO_RIGHT, TO_LEFT } public void loadFragment(Fragment fragment, FragAction action) { mCurrentFragment = fragment; // Add the fragment to the 'fragment_container' FrameLayout FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); switch (action) { case START: transaction.setCustomAnimations(0, 0); transaction.replace(R.id.create_key_fragment_container, fragment, FRAGMENT_TAG) .commit(); break; case TO_LEFT: getSupportFragmentManager().popBackStackImmediate(); break; case TO_RIGHT: transaction.setCustomAnimations(R.anim.frag_slide_in_from_right, R.anim.frag_slide_out_to_left, R.anim.frag_slide_in_from_left, R.anim.frag_slide_out_to_right); transaction.addToBackStack(null); transaction.replace(R.id.create_key_fragment_container, fragment, FRAGMENT_TAG) .commit(); break; } // do it immediately! getSupportFragmentManager().executePendingTransactions(); } interface SecurityTokenListenerFragment { void doSecurityTokenInBackground() throws IOException; void onSecurityTokenPostExecute(); } @Override public void finish() { if (mFirstTime) { Preferences prefs = Preferences.getPreferences(this); prefs.setFirstTime(false); Intent intent = new Intent(this, MainActivity.class); startActivity(intent); } super.finish(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13849 Content-Disposition: inline; filename="CreateKeyEmailFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b871f471c69490ab6301b1db90d647e668b1cd41" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.Fragment; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.EmailEditText; import java.util.ArrayList; import java.util.List; public class CreateKeyEmailFragment extends Fragment { private CreateKeyActivity mCreateKeyActivity; private EmailEditText mEmailEdit; private ArrayList mAdditionalEmailModels = new ArrayList<>(); private EmailAdapter mEmailAdapter; /** * Creates new instance of this fragment */ public static CreateKeyEmailFragment newInstance() { CreateKeyEmailFragment frag = new CreateKeyEmailFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } /** * Checks if text of given EditText is not empty. If it is empty an error is * set and the EditText gets the focus. * * @param editText * @return true if EditText is not empty */ private boolean isMainEmailValid(EditText editText) { if (editText.getText().length() == 0) { editText.setError(getString(R.string.create_key_empty)); editText.requestFocus(); return false; } else if (!checkEmail(editText.getText().toString(), false)){ return false; } editText.setError(null); return true; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_email_fragment, container, false); mEmailEdit = (EmailEditText) view.findViewById(R.id.create_key_email); View backButton = view.findViewById(R.id.create_key_back_button); View nextButton = view.findViewById(R.id.create_key_next_button); RecyclerView emailsRecyclerView = (RecyclerView) view.findViewById(R.id.create_key_emails); // initial values mEmailEdit.setText(mCreateKeyActivity.mEmail); // focus empty edit fields if (mCreateKeyActivity.mEmail == null) { mEmailEdit.requestFocus(); } backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } }); nextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextClicked(); } }); emailsRecyclerView.setHasFixedSize(true); emailsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); emailsRecyclerView.setItemAnimator(new DefaultItemAnimator()); if (mEmailAdapter == null) { mEmailAdapter = new EmailAdapter(mAdditionalEmailModels, new View.OnClickListener() { @Override public void onClick(View v) { addEmail(); } }); if (mCreateKeyActivity.mAdditionalEmails != null) { mEmailAdapter.addAll(mCreateKeyActivity.mAdditionalEmails); } } emailsRecyclerView.setAdapter(mEmailAdapter); return view; } /** * Checks if a given email is valid * * @param email * @param additionalEmail * @return */ private boolean checkEmail(String email, boolean additionalEmail) { if (email.isEmpty()) { Notify.create(getActivity(), getString(R.string.create_key_email_empty_email), Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return false; } // check for duplicated emails if (!additionalEmail && isEmailDuplicatedInsideAdapter(email) || additionalEmail && mEmailEdit.getText().length() > 0 && email.equals(mEmailEdit.getText().toString())) { Notify.create(getActivity(), getString(R.string.create_key_email_already_exists_text), Notify.LENGTH_LONG, Notify.Style.ERROR).show(CreateKeyEmailFragment.this); return false; } return true; } /** * Checks for duplicated emails inside the additional email adapter. * * @param email * @return */ private boolean isEmailDuplicatedInsideAdapter(String email) { //check for duplicated emails inside the adapter for (EmailAdapter.ViewModel model : mAdditionalEmailModels) { if (email.equals(model.email)) { return true; } } return false; } /** * Displays a dialog fragment for the user to input a valid email. */ private void addEmail() { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == AddEmailDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); if (checkEmail(email, true)) { // add new user id mEmailAdapter.add(email); } } } }; // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); AddEmailDialogFragment addEmailDialog = AddEmailDialogFragment.newInstance(messenger); addEmailDialog.show(getActivity().getSupportFragmentManager(), "addEmailDialog"); } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } private void nextClicked() { if (isMainEmailValid(mEmailEdit)) { // save state mCreateKeyActivity.mEmail = mEmailEdit.getText().toString(); mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); CreateKeyActivity createKeyActivity = ((CreateKeyActivity) getActivity()); if (createKeyActivity.mCreateSecurityToken) { hideKeyboard(); CreateSecurityTokenPinFragment frag = CreateSecurityTokenPinFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } else { CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } } } 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); } private ArrayList getAdditionalEmails() { ArrayList emails = new ArrayList<>(); for (EmailAdapter.ViewModel holder : mAdditionalEmailModels) { emails.add(holder.toString()); } return emails; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // save state in activity mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); } public static class EmailAdapter extends RecyclerView.Adapter { private List mDataset; private View.OnClickListener mFooterOnClickListener; private static final int TYPE_FOOTER = 0; private static final int TYPE_ITEM = 1; public static class ViewModel { String email; ViewModel(String email) { this.email = email; } @Override public String toString() { return email; } } // Provide a reference to the views for each data item // Complex data items may need more than one view per item, and // you provide access to all the views for a data item in a view holder class ViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ImageButton mDeleteButton; public ViewHolder(View itemView) { super(itemView); mTextView = (TextView) itemView.findViewById(R.id.create_key_email_item_email); mDeleteButton = (ImageButton) itemView.findViewById(R.id.create_key_email_item_delete_button); } } class FooterHolder extends RecyclerView.ViewHolder { public Button mAddButton; public FooterHolder(View itemView) { super(itemView); mAddButton = (Button) itemView.findViewById(R.id.create_key_add_email); } } // Provide a suitable constructor (depends on the kind of dataset) public EmailAdapter(List myDataset, View.OnClickListener onFooterClickListener) { mDataset = myDataset; mFooterOnClickListener = onFooterClickListener; } // Create new views (invoked by the layout manager) @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_FOOTER) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.create_key_email_list_footer, parent, false); return new FooterHolder(v); } else { //inflate your layout and pass it to view holder View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.create_key_email_list_item, parent, false); return new ViewHolder(v); } } // Replace the contents of a view (invoked by the layout manager) @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { if (holder instanceof FooterHolder) { FooterHolder thisHolder = (FooterHolder) holder; thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener); } else if (holder instanceof ViewHolder) { ViewHolder thisHolder = (ViewHolder) holder; // - get element from your dataset at this position // - replace the contents of the view with that element final ViewModel model = mDataset.get(position); thisHolder.mTextView.setText(model.email); thisHolder.mDeleteButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { remove(model); } }); } } // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { return mDataset.size() + 1; } @Override public int getItemViewType(int position) { if (isPositionFooter(position)) { return TYPE_FOOTER; } else { return TYPE_ITEM; } } private boolean isPositionFooter(int position) { return position == mDataset.size(); } public void add(String email) { mDataset.add(new ViewModel(email)); notifyItemInserted(mDataset.size() - 1); } private void addAll(ArrayList emails) { for (String email : emails) { mDataset.add(new EmailAdapter.ViewModel(email)); } } public void remove(ViewModel model) { int position = mDataset.indexOf(model); mDataset.remove(position); notifyItemRemoved(position); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 20873 Content-Disposition: inline; filename="CreateKeyFinalFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "99a4b02018708f6e8e1aa16bbe107d34635a4fa3" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; import org.bouncycastle.bcpg.sig.KeyFlags; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import java.util.Date; import java.util.Iterator; import java.util.regex.Pattern; public class CreateKeyFinalFragment extends Fragment { public static final int REQUEST_EDIT_KEY = 0x00008007; TextView mNameEdit; TextView mEmailEdit; CheckBox mUploadCheckbox; View mBackButton; View mCreateButton; View mCustomKeyLayout; Button mCustomKeyRevertButton; SaveKeyringParcel mSaveKeyringParcel; private CryptoOperationHelper mUploadOpHelper; private CryptoOperationHelper mCreateOpHelper; private CryptoOperationHelper mMoveToCardOpHelper; // queued results which may trigger delayed actions private EditKeyResult mQueuedSaveKeyResult; private OperationResult mQueuedFinishResult; private EditKeyResult mQueuedDisplayResult; // NOTE: Do not use more complicated pattern like defined in android.util.Patterns.EMAIL_ADDRESS // EMAIL_ADDRESS fails for mails with umlauts for example private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\S]+@[\\S]+\\.[a-z]+$"); public static CreateKeyFinalFragment newInstance() { CreateKeyFinalFragment frag = new CreateKeyFinalFragment(); frag.setRetainInstance(true); Bundle args = new Bundle(); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_final_fragment, container, false); mNameEdit = (TextView) view.findViewById(R.id.name); mEmailEdit = (TextView) view.findViewById(R.id.email); mUploadCheckbox = (CheckBox) view.findViewById(R.id.create_key_upload); mBackButton = view.findViewById(R.id.create_key_back_button); mCreateButton = view.findViewById(R.id.create_key_next_button); mCustomKeyLayout = view.findViewById(R.id.custom_key_layout); mCustomKeyRevertButton = (Button) view.findViewById(R.id.revert_key_configuration); CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); // set values if (createKeyActivity.mName != null) { mNameEdit.setText(createKeyActivity.mName); } else { mNameEdit.setText(getString(R.string.user_id_no_name)); } if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) { String emailText = createKeyActivity.mEmail + ", "; Iterator it = createKeyActivity.mAdditionalEmails.iterator(); while (it.hasNext()) { Object next = it.next(); emailText += next; if (it.hasNext()) { emailText += ", "; } } mEmailEdit.setText(emailText); } else { mEmailEdit.setText(createKeyActivity.mEmail); } checkEmailValidity(); mCreateButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createKey(); } }); mCustomKeyRevertButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { keyConfigRevertToDefault(); } }); mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); if (createKeyActivity != null) { createKeyActivity.loadFragment(null, FragAction.TO_LEFT); } } }); // If this is a debug build, don't upload by default if (Constants.DEBUG) { mUploadCheckbox.setChecked(false); } return view; } @Override public void onPrepareOptionsMenu(Menu menu) { CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); MenuItem editItem = menu.findItem(R.id.menu_create_key_edit); editItem.setEnabled(!createKeyActivity.mCreateSecurityToken); super.onPrepareOptionsMenu(menu); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.create_key_final, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_create_key_edit: Intent edit = new Intent(getActivity(), EditKeyActivity.class); edit.putExtra(EditKeyActivity.EXTRA_SAVE_KEYRING_PARCEL, mSaveKeyringParcel); startActivityForResult(edit, REQUEST_EDIT_KEY); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mCreateOpHelper != null) { mCreateOpHelper.handleActivityResult(requestCode, resultCode, data); } if (mMoveToCardOpHelper != null) { mMoveToCardOpHelper.handleActivityResult(requestCode, resultCode, data); } if (mUploadOpHelper != null) { mUploadOpHelper.handleActivityResult(requestCode, resultCode, data); } switch (requestCode) { case REQUEST_EDIT_KEY: { if (resultCode == Activity.RESULT_OK) { SaveKeyringParcel customKeyConfiguration = data.getParcelableExtra(EditKeyActivity.EXTRA_SAVE_KEYRING_PARCEL); keyConfigUseCustom(customKeyConfiguration); } break; } default: super.onActivityResult(requestCode, resultCode, data); } } public void keyConfigUseCustom(SaveKeyringParcel customKeyConfiguration) { mSaveKeyringParcel = customKeyConfiguration; mCustomKeyLayout.setVisibility(View.VISIBLE); } public void keyConfigRevertToDefault() { Activity activity = getActivity(); if (activity == null) { return; } mSaveKeyringParcel = createDefaultSaveKeyringParcel((CreateKeyActivity) activity); mCustomKeyLayout.setVisibility(View.GONE); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // We have a menu item to show in action bar. setHasOptionsMenu(true); if (mSaveKeyringParcel == null) { keyConfigRevertToDefault(); } // handle queued actions if (mQueuedFinishResult != null) { finishWithResult(mQueuedFinishResult); return; } if (mQueuedDisplayResult != null) { try { displayResult(mQueuedDisplayResult); } finally { // clear after operation, note that this may drop the operation if it didn't // work when called from here! mQueuedDisplayResult = null; } } if (mQueuedSaveKeyResult != null) { try { uploadKey(mQueuedSaveKeyResult); } finally { // see above mQueuedSaveKeyResult = null; } } } private static SaveKeyringParcel createDefaultSaveKeyringParcel(CreateKeyActivity createKeyActivity) { SaveKeyringParcel saveKeyringParcel = new SaveKeyringParcel(); if (createKeyActivity.mCreateSecurityToken) { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.AUTHENTICATION, 0L)); // use empty passphrase saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(new Passphrase())); } else { saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.CERTIFY_OTHER, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.SIGN_DATA, 0L)); saveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 3072, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); if (createKeyActivity.mPassphrase != null) { saveKeyringParcel.setNewUnlock(new ChangeUnlockParcel(createKeyActivity.mPassphrase)); } else { saveKeyringParcel.setNewUnlock(null); } } String userId = KeyRing.createUserId( new OpenPgpUtils.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null) ); saveKeyringParcel.mAddUserIds.add(userId); saveKeyringParcel.mChangePrimaryUserId = userId; if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) { for (String email : createKeyActivity.mAdditionalEmails) { String thisUserId = KeyRing.createUserId( new OpenPgpUtils.UserId(createKeyActivity.mName, email, null) ); saveKeyringParcel.mAddUserIds.add(thisUserId); } } return saveKeyringParcel; } private void checkEmailValidity() { CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); boolean emailsValid = true; if (!EMAIL_PATTERN.matcher(createKeyActivity.mEmail).matches()) { emailsValid = false; } if (createKeyActivity.mAdditionalEmails != null && createKeyActivity.mAdditionalEmails.size() > 0) { for (Iterator it = createKeyActivity.mAdditionalEmails.iterator(); it.hasNext(); ) { if (!EMAIL_PATTERN.matcher(it.next().toString()).matches()) { emailsValid = false; } } } if (!emailsValid) { mEmailEdit.setError(getString(R.string.create_key_final_email_valid_warning)); mEmailEdit.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mNameEdit.requestFocus(); // Workaround to remove focus from email } }); } } private void createKey() { CreateKeyActivity activity = (CreateKeyActivity) getActivity(); if (activity == null) { // this is a ui-triggered action, nvm if it fails while detached! return; } final boolean createSecurityToken = activity.mCreateSecurityToken; CryptoOperationHelper.Callback createKeyCallback = new CryptoOperationHelper.Callback() { @Override public SaveKeyringParcel createOperationInput() { return mSaveKeyringParcel; } @Override public void onCryptoOperationSuccess(EditKeyResult result) { if (createSecurityToken) { moveToCard(result); return; } if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) { // result will be displayed after upload uploadKey(result); return; } finishWithResult(result); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(EditKeyResult result) { displayResult(result); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mCreateOpHelper = new CryptoOperationHelper<>(1, this, createKeyCallback, R.string.progress_building_key); mCreateOpHelper.cryptoOperation(); } private void displayResult(EditKeyResult result) { Activity activity = getActivity(); if (activity == null) { mQueuedDisplayResult = result; return; } result.createNotify(activity).show(); } private void moveToCard(final EditKeyResult saveKeyResult) { CreateKeyActivity activity = (CreateKeyActivity) getActivity(); final SaveKeyringParcel changeKeyringParcel; CachedPublicKeyRing key = (new ProviderHelper(activity)) .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); try { changeKeyringParcel = new SaveKeyringParcel(key.getMasterKeyId(), key.getFingerprint()); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "Key that should be moved to Security Token not found in database!"); return; } // define subkeys that should be moved to the card Cursor cursor = activity.getContentResolver().query( KeychainContract.Keys.buildKeysUri(changeKeyringParcel.mMasterKeyId), new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null ); try { while (cursor != null && cursor.moveToNext()) { long subkeyId = cursor.getLong(0); changeKeyringParcel.getOrCreateSubkeyChange(subkeyId).mMoveKeyToSecurityToken = true; } } finally { if (cursor != null) { cursor.close(); } } // define new PIN and Admin PIN for the card changeKeyringParcel.mSecurityTokenPin = activity.mSecurityTokenPin; changeKeyringParcel.mSecurityTokenAdminPin = activity.mSecurityTokenAdminPin; CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public SaveKeyringParcel createOperationInput() { return changeKeyringParcel; } @Override public void onCryptoOperationSuccess(EditKeyResult result) { handleResult(result); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(EditKeyResult result) { handleResult(result); } public void handleResult(EditKeyResult result) { // merge logs of createKey with moveToCard saveKeyResult.getLog().add(result, 0); if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) { // result will be displayed after upload uploadKey(saveKeyResult); return; } finishWithResult(saveKeyResult); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mMoveToCardOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_modify); mMoveToCardOpHelper.cryptoOperation(new CryptoInputParcel(new Date())); } private void uploadKey(final EditKeyResult saveKeyResult) { Activity activity = getActivity(); // if the activity is gone at this point, there is nothing we can do! if (activity == null) { mQueuedSaveKeyResult = saveKeyResult; return; } // set data uri as path to keyring final long masterKeyId = saveKeyResult.mMasterKeyId; // upload to favorite keyserver final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver(); CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public UploadKeyringParcel createOperationInput() { return new UploadKeyringParcel(keyserver, masterKeyId); } @Override public void onCryptoOperationSuccess(UploadResult result) { handleResult(result); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(UploadResult result) { handleResult(result); } public void handleResult(UploadResult result) { saveKeyResult.getLog().add(result, 0); finishWithResult(saveKeyResult); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mUploadOpHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } public void finishWithResult(OperationResult result) { Activity activity = getActivity(); if (activity == null) { mQueuedFinishResult = result; return; } Intent data = new Intent(); data.putExtra(OperationResult.EXTRA_RESULT, result); activity.setResult(Activity.RESULT_OK, data); activity.finish(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3167 Content-Disposition: inline; filename="CreateKeyNameFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "3332b9cf926ab51efe7068b719c7afcf1744a622" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.widget.NameEditText; public class CreateKeyNameFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; NameEditText mNameEdit; View mBackButton; View mNextButton; /** * Creates new instance of this fragment */ public static CreateKeyNameFragment newInstance() { CreateKeyNameFragment frag = new CreateKeyNameFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_name_fragment, container, false); mNameEdit = (NameEditText) view.findViewById(R.id.create_key_name); mBackButton = view.findViewById(R.id.create_key_back_button); mNextButton = view.findViewById(R.id.create_key_next_button); // initial values mNameEdit.setText(mCreateKeyActivity.mName); // focus empty edit fields if (mCreateKeyActivity.mName == null) { mNameEdit.requestFocus(); } mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } }); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextClicked(); } }); return view; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } private void nextClicked() { // save state mCreateKeyActivity.mName = mNameEdit.getText().length() == 0 ? null : mNameEdit.getText().toString(); CreateKeyEmailFragment frag = CreateKeyEmailFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7769 Content-Disposition: inline; filename="CreateKeyPassphraseFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "d858fd6ec807d360dd027531d5bbcd86da5117df" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.text.Editable; import android.text.TextWatcher; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.EditText; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.widget.PassphraseEditText; import org.sufficientlysecure.keychain.util.Passphrase; public class CreateKeyPassphraseFragment extends Fragment { // view CreateKeyActivity mCreateKeyActivity; PassphraseEditText mPassphraseEdit; EditText mPassphraseEditAgain; CheckBox mShowPassphrase; View mBackButton; View mNextButton; /** * Creates new instance of this fragment */ public static CreateKeyPassphraseFragment newInstance() { CreateKeyPassphraseFragment frag = new CreateKeyPassphraseFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } /** * Checks if text of given EditText is not empty. If it is empty an error is * set and the EditText gets the focus. * * @param context * @param editText * @return true if EditText is not empty */ private static boolean isEditTextNotEmpty(Context context, EditText editText) { boolean output = true; if (editText.getText().length() == 0) { editText.setError(context.getString(R.string.create_key_empty)); editText.requestFocus(); output = false; } else { editText.setError(null); } return output; } private static boolean areEditTextsEqual(EditText editText1, EditText editText2) { Passphrase p1 = new Passphrase(editText1); Passphrase p2 = new Passphrase(editText2); return (p1.equals(p2)); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_passphrase_fragment, container, false); mPassphraseEdit = (PassphraseEditText) view.findViewById(R.id.create_key_passphrase); mPassphraseEditAgain = (EditText) view.findViewById(R.id.create_key_passphrase_again); mShowPassphrase = (CheckBox) view.findViewById(R.id.create_key_show_passphrase); mBackButton = view.findViewById(R.id.create_key_back_button); mNextButton = view.findViewById(R.id.create_key_next_button); // initial values // TODO: using String here is unsafe... if (mCreateKeyActivity.mPassphrase != null) { mPassphraseEdit.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe()); mPassphraseEditAgain.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe()); } mPassphraseEdit.requestFocus(); mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { back(); } }); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextClicked(); } }); mShowPassphrase.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { mPassphraseEdit.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); mPassphraseEditAgain.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); } else { mPassphraseEdit.setTransformationMethod(PasswordTransformationMethod.getInstance()); mPassphraseEditAgain.setTransformationMethod(PasswordTransformationMethod.getInstance()); } } }); TextWatcher textWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (!isEditTextNotEmpty(getActivity(), mPassphraseEdit)) { mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); return; } if (areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) { mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_ok, 0); } else { mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_bad, 0); } } @Override public void afterTextChanged(Editable s) { } }; mPassphraseEdit.addTextChangedListener(textWatcher); mPassphraseEditAgain.addTextChangedListener(textWatcher); return view; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } private void back() { hideKeyboard(); mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } private void nextClicked() { if (isEditTextNotEmpty(getActivity(), mPassphraseEdit)) { if (!areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) { mPassphraseEditAgain.setError(getActivity().getApplicationContext().getString(R.string.create_key_passphrases_not_equal)); mPassphraseEditAgain.requestFocus(); return; } mPassphraseEditAgain.setError(null); // save state mCreateKeyActivity.mPassphrase = new Passphrase(mPassphraseEdit); CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); hideKeyboard(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } } 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); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5012 Content-Disposition: inline; filename="CreateKeyStartFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "c62ec97e7bc60ce431589d5d264be67e6fc1b6ad" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; public class CreateKeyStartFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mCreateKey; View mImportKey; View mSecurityToken; TextView mSkipOrCancel; public static final int REQUEST_CODE_IMPORT_KEY = 0x00007012; /** * Creates new instance of this fragment */ public static CreateKeyStartFragment newInstance() { CreateKeyStartFragment frag = new CreateKeyStartFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_key_start_fragment, container, false); mCreateKey = view.findViewById(R.id.create_key_create_key_button); mImportKey = view.findViewById(R.id.create_key_import_button); mSecurityToken = view.findViewById(R.id.create_key_security_token_button); mSkipOrCancel = (TextView) view.findViewById(R.id.create_key_cancel); if (mCreateKeyActivity.mFirstTime) { mSkipOrCancel.setText(R.string.first_time_skip); } else { mSkipOrCancel.setText(R.string.btn_do_not_save); } mCreateKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); mSecurityToken.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { CreateSecurityTokenWaitFragment frag = new CreateSecurityTokenWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); mImportKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(mCreateKeyActivity, ImportKeysActivity.class); intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); startActivityForResult(intent, REQUEST_CODE_IMPORT_KEY); } }); mSkipOrCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!mCreateKeyActivity.mFirstTime) { mCreateKeyActivity.setResult(Activity.RESULT_CANCELED); } mCreateKeyActivity.finish(); } }); return view; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_IMPORT_KEY) { if (resultCode == Activity.RESULT_OK) { if (mCreateKeyActivity.mFirstTime) { Preferences prefs = Preferences.getPreferences(mCreateKeyActivity); prefs.setFirstTime(false); mCreateKeyActivity.finish(); } else { // just finish activity and return data mCreateKeyActivity.setResult(Activity.RESULT_OK, data); mCreateKeyActivity.finish(); } } } else { Log.e(Constants.TAG, "No valid request code!"); } } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2997 Content-Disposition: inline; filename="CreateSecurityTokenBlankFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "08441c199e1148d64b11c3e516c171f8a2d28506" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; public class CreateSecurityTokenBlankFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; View mNextButton; /** * Creates new instance of this fragment */ public static CreateSecurityTokenBlankFragment newInstance() { CreateSecurityTokenBlankFragment frag = new CreateSecurityTokenBlankFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_yubi_key_blank_fragment, container, false); mBackButton = view.findViewById(R.id.create_key_back_button); mNextButton = view.findViewById(R.id.create_key_next_button); mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (getFragmentManager().getBackStackEntryCount() == 0) { getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } else { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } } }); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextClicked(); } }); return view; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } private void nextClicked() { mCreateKeyActivity.mCreateSecurityToken = true; CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11476 Content-Disposition: inline; filename="CreateSecurityTokenImportResetFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "6f35fdd38d38bcf830e1105a69a9fa3a3b181df9" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.RadioButton; import android.widget.TextView; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListenerFragment; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; public class CreateSecurityTokenImportResetFragment extends QueueingCryptoOperationFragment implements SecurityTokenListenerFragment { private static final int REQUEST_CODE_RESET = 0x00005001; private static final String ARG_FINGERPRINTS = "fingerprint"; public static final String ARG_AID = "aid"; public static final String ARG_USER_ID = "user_ids"; CreateKeyActivity mCreateKeyActivity; private byte[] mTokenFingerprints; private byte[] mTokenAid; private String mTokenUserId; private String mTokenFingerprint; private ImportKeysListFragment mListFragment; private TextView vSerNo; private TextView vUserId; private TextView mNextButton; private RadioButton mRadioImport; private RadioButton mRadioReset; private View mResetWarning; // for CryptoOperationFragment key import private String mKeyserver; private ArrayList mKeyList; public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { CreateSecurityTokenImportResetFragment frag = new CreateSecurityTokenImportResetFragment(); Bundle args = new Bundle(); args.putByteArray(ARG_FINGERPRINTS, scannedFingerprints); args.putByteArray(ARG_AID, nfcAid); args.putString(ARG_USER_ID, userId); frag.setArguments(args); return frag; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); mTokenFingerprints = args.getByteArray(ARG_FINGERPRINTS); mTokenAid = args.getByteArray(ARG_AID); mTokenUserId = args.getString(ARG_USER_ID); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); mTokenFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_security_token_import_reset_fragment, container, false); vSerNo = (TextView) view.findViewById(R.id.token_serno); vUserId = (TextView) view.findViewById(R.id.token_userid); mNextButton = (TextView) view.findViewById(R.id.create_key_next_button); mRadioImport = (RadioButton) view.findViewById(R.id.token_decision_import); mRadioReset = (RadioButton) view.findViewById(R.id.token_decision_reset); mResetWarning = view.findViewById(R.id.token_import_reset_warning); View mBackButton = view.findViewById(R.id.create_key_back_button); mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (getFragmentManager().getBackStackEntryCount() == 0) { getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } else { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } } }); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mRadioReset.isChecked()) { resetCard(); } else { importKey(); } } }); mListFragment = ImportKeysListFragment.newInstance(null, null, "0x" + mTokenFingerprint, true, null); mRadioImport.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { mNextButton.setText(R.string.btn_import); mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_key_plus_grey600_24dp, 0); mNextButton.setVisibility(View.VISIBLE); mResetWarning.setVisibility(View.GONE); getFragmentManager().beginTransaction() .replace(R.id.security_token_import_fragment, mListFragment, "token_import") .commit(); getFragmentManager().executePendingTransactions(); refreshSearch(); } } }); mRadioReset.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { mNextButton.setText(R.string.btn_reset); mNextButton.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_close_grey_24dp, 0); mNextButton.setVisibility(View.VISIBLE); mResetWarning.setVisibility(View.VISIBLE); getFragmentManager().beginTransaction() .remove(mListFragment) .commit(); } } }); setData(); return view; } @Override public void onSaveInstanceState(Bundle args) { super.onSaveInstanceState(args); args.putByteArray(ARG_FINGERPRINTS, mTokenFingerprints); args.putByteArray(ARG_AID, mTokenAid); args.putString(ARG_USER_ID, mTokenUserId); } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } public void setData() { String serno = Hex.toHexString(mTokenAid, 10, 4); vSerNo.setText(getString(R.string.security_token_serial_no, serno)); if (!mTokenUserId.isEmpty()) { vUserId.setText(getString(R.string.security_token_key_holder, mTokenUserId)); } else { vUserId.setText(getString(R.string.security_token_key_holder_not_set)); } } public void refreshSearch() { mListFragment.loadNew(new ImportKeysListFragment.CloudLoaderState("0x" + mTokenFingerprint, Preferences.getPreferences(getActivity()).getCloudSearchPrefs())); } public void importKey() { ArrayList keyList = new ArrayList<>(); keyList.add(new ParcelableKeyRing(mTokenFingerprint, null)); mKeyList = keyList; mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); super.setProgressMessageResource(R.string.progress_importing); super.cryptoOperation(); } public void resetCard() { Intent intent = new Intent(getActivity(), SecurityTokenOperationActivity.class); RequiredInputParcel resetP = RequiredInputParcel.createSecurityTokenReset(); intent.putExtra(SecurityTokenOperationActivity.EXTRA_REQUIRED_INPUT, resetP); intent.putExtra(SecurityTokenOperationActivity.EXTRA_CRYPTO_INPUT, new CryptoInputParcel()); startActivityForResult(intent, REQUEST_CODE_RESET); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_RESET && resultCode == Activity.RESULT_OK) { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); return; } super.onActivityResult(requestCode, resultCode, data); } @Override public void doSecurityTokenInBackground() throws IOException { mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints(); mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid(); mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); mTokenFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); } @Override public void onSecurityTokenPostExecute() { setData(); } @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); } @Override public void onQueuedOperationSuccess(ImportKeyResult result) { long[] masterKeyIds = result.getImportedMasterKeyIds(); if (masterKeyIds.length == 0) { super.onCryptoOperationError(result); return; } // null-protected from Queueing*Fragment Activity activity = getActivity(); Intent intent = new Intent(activity, ViewKeyActivity.class); // use the imported masterKeyId, not the one from the token, because // that one might* just have been a subkey of the imported key intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); intent.putExtra(ViewKeyActivity.EXTRA_DISPLAY_RESULT, result); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mTokenAid); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mTokenFingerprints); startActivity(intent); activity.finish(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7206 Content-Disposition: inline; filename="CreateSecurityTokenPinFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "45cf7a6656a4dd70a979f8e892c3d8e3e7618c17" /* * Copyright (C) 2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.util.Passphrase; import java.security.SecureRandom; import java.util.Arrays; import java.util.HashSet; public class CreateSecurityTokenPinFragment extends Fragment { // view CreateKeyActivity mCreateKeyActivity; EditText mPin; EditText mPinRepeat; TextView mAdminPin; View mBackButton; View mNextButton; private static HashSet sPinBlacklist = new HashSet<>(Arrays.asList( "000000", "111111", "222222", "333333", "444444", "555555", "666666", "777777", "888888", "999999", "123456", "XXXXXX" )); /** * Creates new instance of this fragment */ public static CreateSecurityTokenPinFragment newInstance() { CreateSecurityTokenPinFragment frag = new CreateSecurityTokenPinFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } /** * Checks if text of given EditText is not empty. If it is empty an error is * set and the EditText gets the focus. * * @return true if EditText is not empty */ private static boolean isEditTextNotEmpty(Context context, EditText editText) { boolean output = true; if (editText.getText().length() == 0) { editText.setError(context.getString(R.string.create_key_empty)); editText.requestFocus(); output = false; } else { editText.setError(null); } return output; } private static boolean areEditTextsEqual(EditText editText1, EditText editText2) { Passphrase p1 = new Passphrase(editText1); Passphrase p2 = new Passphrase(editText2); return (p1.equals(p2)); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false); mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin); mPinRepeat = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat); mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin); mBackButton = view.findViewById(R.id.create_key_back_button); mNextButton = view.findViewById(R.id.create_key_next_button); if (mCreateKeyActivity.mSecurityTokenPin == null) { new AsyncTask() { @Override protected Passphrase doInBackground(Void... unused) { SecureRandom secureRandom = new SecureRandom(); // min = 8, we choose 8 String adminPin = "" + secureRandom.nextInt(9) + secureRandom.nextInt(9) + secureRandom.nextInt(9) + secureRandom.nextInt(9) + secureRandom.nextInt(9) + secureRandom.nextInt(9) + secureRandom.nextInt(9) + secureRandom.nextInt(9); return new Passphrase(adminPin); } @Override protected void onPostExecute(Passphrase adminPin) { mCreateKeyActivity.mSecurityTokenAdminPin = adminPin; mAdminPin.setText(mCreateKeyActivity.mSecurityTokenAdminPin.toStringUnsafe()); } }.execute(); } else { mAdminPin.setText(mCreateKeyActivity.mSecurityTokenAdminPin.toStringUnsafe()); } mPin.requestFocus(); mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { back(); } }); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { nextClicked(); } }); return view; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } private void back() { hideKeyboard(); mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } private void nextClicked() { if (isEditTextNotEmpty(getActivity(), mPin)) { if (!areEditTextsEqual(mPin, mPinRepeat)) { mPinRepeat.setError(getString(R.string.create_key_passphrases_not_equal)); mPinRepeat.requestFocus(); return; } if (mPin.getText().toString().length() < 6) { mPin.setError(getString(R.string.create_key_yubi_key_pin_too_short)); mPin.requestFocus(); return; } if (sPinBlacklist.contains(mPin.getText().toString())) { mPin.setError(getString(R.string.create_key_yubi_key_pin_insecure)); mPin.requestFocus(); return; } mCreateKeyActivity.mSecurityTokenPin = new Passphrase(mPin.getText().toString()); CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); hideKeyboard(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } } 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); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2861 Content-Disposition: inline; filename="CreateSecurityTokenWaitFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "78250274106785208c854c818b1713bead65c4d8" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; public class CreateSecurityTokenWaitFragment extends Fragment { public static boolean sDisableFragmentAnimations = false; CreateKeyActivity mCreateKeyActivity; View mBackButton; @Override public void onActivityCreated(@Nullable final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (this.getActivity() instanceof BaseSecurityTokenActivity) { ((BaseSecurityTokenActivity) this.getActivity()).checkDeviceConnection(); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false); mBackButton = view.findViewById(R.id.create_key_back_button); mBackButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); } }); return view; } @Override public void onAttach(Context context) { super.onAttach(context); mCreateKeyActivity = (CreateKeyActivity) getActivity(); } /** * hack from http://stackoverflow.com/a/11253987 */ @Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if (sDisableFragmentAnimations) { Animation a = new Animation() {}; a.setDuration(0); return a; } return super.onCreateAnimation(transit, enter, nextAnim); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8122 Content-Disposition: inline; filename="DecryptActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "cf7a0b1d76d87ceb56470a4fb9b4359a5cacabee" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.widget.Toast; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class DecryptActivity extends BaseActivity { /* Intents */ public static final String ACTION_DECRYPT_FROM_CLIPBOARD = "DECRYPT_DATA_CLIPBOARD"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setFullScreenDialogClose(Activity.RESULT_CANCELED, false); // Handle intent actions handleActions(savedInstanceState, getIntent()); } @Override protected void initLayout() { setContentView(R.layout.decrypt_files_activity); } /** * Handles all actions with this intent */ private void handleActions(Bundle savedInstanceState, Intent intent) { // No need to initialize fragments if we are just being restored if (savedInstanceState != null) { return; } ArrayList uris = new ArrayList<>(); String action = intent.getAction(); if (action == null) { Toast.makeText(this, "Error: No action specified!", Toast.LENGTH_LONG).show(); setResult(Activity.RESULT_CANCELED); finish(); return; } // depending on the data source, we may or may not be able to delete the original file boolean canDelete = false; try { switch (action) { case Intent.ACTION_SEND: { // When sending to Keychain Decrypt via share menu // Binary via content provider (could also be files) // override uri to get stream from send if (intent.hasExtra(Intent.EXTRA_STREAM)) { uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); } else if (intent.hasExtra(Intent.EXTRA_TEXT)) { String text = intent.getStringExtra(Intent.EXTRA_TEXT); Uri uri = readToTempFile(text); if (uri != null) { uris.add(uri); } } break; } case Intent.ACTION_SEND_MULTIPLE: { if (intent.hasExtra(Intent.EXTRA_STREAM)) { uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } else if (intent.hasExtra(Intent.EXTRA_TEXT)) { for (String text : intent.getStringArrayListExtra(Intent.EXTRA_TEXT)) { Uri uri = readToTempFile(text); if (uri != null) { uris.add(uri); } } } break; } case ACTION_DECRYPT_FROM_CLIPBOARD: { ClipboardManager clipMan = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); if (clipMan == null) { break; } ClipData clip = clipMan.getPrimaryClip(); if (clip == null) { break; } // check if data is available as uri Uri uri = null; for (int i = 0; i < clip.getItemCount(); i++) { ClipData.Item item = clip.getItemAt(i); Uri itemUri = item.getUri(); if (itemUri != null) { uri = itemUri; break; } } // otherwise, coerce to text (almost always possible) and work from there if (uri == null) { String text = clip.getItemAt(0).coerceToText(this).toString(); uri = readToTempFile(text); } if (uri != null) { uris.add(uri); } break; } // for everything else, just work on the intent data case Intent.ACTION_VIEW: canDelete = true; case OpenKeychainIntents.DECRYPT_DATA: default: Uri uri = intent.getData(); if (uri != null) { if ("com.android.email.attachmentprovider".equals(uri.getHost())) { Toast.makeText(this, R.string.error_reading_aosp, Toast.LENGTH_LONG).show(); finish(); return; } uris.add(uri); } } } catch (IOException e) { Toast.makeText(this, R.string.error_reading_text, Toast.LENGTH_LONG).show(); finish(); return; } // Definitely need a data uri with the decrypt_data intent if (uris.isEmpty()) { Toast.makeText(this, "No data to decrypt!", Toast.LENGTH_LONG).show(); setResult(Activity.RESULT_CANCELED); finish(); return; } displayListFragment(uris, canDelete); } @Nullable public Uri readToTempFile(String text) throws IOException { Uri tempFile = TemporaryFileProvider.createFile(this); OutputStream outStream = getContentResolver().openOutputStream(tempFile); if (outStream == null) { return null; } // clean up ascii armored message, fixing newlines and stuff String cleanedText = PgpHelper.getPgpMessageContent(text); if (cleanedText == null) { return null; } // if cleanup didn't work, just try the raw data outStream.write(cleanedText.getBytes()); outStream.close(); return tempFile; } public void displayListFragment(ArrayList inputUris, boolean canDelete) { DecryptListFragment frag = DecryptListFragment.newInstance(inputUris, canDelete); FragmentManager fragMan = getSupportFragmentManager(); FragmentTransaction trans = fragMan.beginTransaction(); trans.replace(R.id.decrypt_files_fragment_container, frag); // if there already is a fragment, allow going back to that. otherwise, we're top level! if (fragMan.getFragments() != null && !fragMan.getFragments().isEmpty()) { trans.addToBackStack("list"); } trans.commit(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 19004 Content-Disposition: inline; filename="DecryptFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b15985fc7e727915c5f0ba89154cd451046b3e0a" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Preferences; public abstract class DecryptFragment extends Fragment implements LoaderManager.LoaderCallbacks { public static final int LOADER_ID_UNIFIED = 0; public static final String ARG_DECRYPT_VERIFY_RESULT = "decrypt_verify_result"; protected LinearLayout mResultLayout; protected ImageView mEncryptionIcon; protected TextView mEncryptionText; protected ImageView mSignatureIcon; protected TextView mSignatureText; protected View mSignatureLayout; protected TextView mSignatureName; protected TextView mSignatureEmail; protected TextView mSignatureAction; private OpenPgpSignatureResult mSignatureResult; private DecryptVerifyResult mDecryptVerifyResult; private ViewAnimator mOverlayAnimator; private CryptoOperationHelper mImportOpHelper; @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // NOTE: These views are inside the activity! mResultLayout = (LinearLayout) getActivity().findViewById(R.id.result_main_layout); mResultLayout.setVisibility(View.GONE); 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); // Overlay mOverlayAnimator = (ViewAnimator) view; Button vErrorOverlayButton = (Button) view.findViewById(R.id.decrypt_error_overlay_button); vErrorOverlayButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mOverlayAnimator.setDisplayedChild(0); } }); } private void showErrorOverlay(boolean overlay) { int child = overlay ? 1 : 0; if (mOverlayAnimator.getDisplayedChild() != child) { mOverlayAnimator.setDisplayedChild(child); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(ARG_DECRYPT_VERIFY_RESULT, mDecryptVerifyResult); } @Override public void onViewStateRestored(Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); if (savedInstanceState == null) { return; } DecryptVerifyResult result = savedInstanceState.getParcelable(ARG_DECRYPT_VERIFY_RESULT); if (result != null) { loadVerifyResult(result); } } private void lookupUnknownKey(long unknownKeyId) { final ArrayList keyList; final String keyserver; // search config keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); { ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); ArrayList selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); keyList = selectedEntries; } CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(keyList, keyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { result.createNotify(getActivity()).show(); getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, DecryptFragment.this); } @Override public void onCryptoOperationCancelled() { // do nothing } @Override public void onCryptoOperationError(ImportKeyResult result) { result.createNotify(getActivity()).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mImportOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing); mImportOpHelper.cryptoOperation(); } private void showKey(long keyId) { try { Intent viewKeyIntent = new Intent(getActivity(), ViewKeyActivity.class); long masterKeyId = new ProviderHelper(getActivity()).getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(keyId) ).getMasterKeyId(); viewKeyIntent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); startActivity(viewKeyIntent); } catch (PgpKeyNotFoundException e) { Notify.create(getActivity(), R.string.error_key_not_found, Style.ERROR); } } protected void loadVerifyResult(DecryptVerifyResult decryptVerifyResult) { mDecryptVerifyResult = decryptVerifyResult; mSignatureResult = decryptVerifyResult.getSignatureResult(); OpenPgpDecryptionResult decryptionResult = decryptVerifyResult.getDecryptionResult(); mResultLayout.setVisibility(View.VISIBLE); switch (decryptionResult.getResult()) { case OpenPgpDecryptionResult.RESULT_ENCRYPTED: { mEncryptionText.setText(R.string.decrypt_result_encrypted); KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.ENCRYPTED); break; } case OpenPgpDecryptionResult.RESULT_INSECURE: { mEncryptionText.setText(R.string.decrypt_result_insecure); KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.INSECURE); break; } default: case OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED: { mEncryptionText.setText(R.string.decrypt_result_not_encrypted); KeyFormattingUtils.setStatusImage(getActivity(), mEncryptionIcon, mEncryptionText, State.NOT_ENCRYPTED); break; } } if (mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_NO_SIGNATURE) { // no signature setSignatureLayoutVisibility(View.GONE); mSignatureText.setText(R.string.decrypt_result_no_signature); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.NOT_SIGNED); getLoaderManager().destroyLoader(LOADER_ID_UNIFIED); showErrorOverlay(false); onVerifyLoaded(true); } else { // signature present // after loader is restarted signature results are checked getLoaderManager().restartLoader(LOADER_ID_UNIFIED, null, this); } } private void setSignatureLayoutVisibility(int visibility) { mSignatureLayout.setVisibility(visibility); } private void setShowAction(final long signatureKeyId) { mSignatureAction.setText(R.string.decrypt_result_action_show); mSignatureAction.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_vpn_key_grey_24dp, 0); mSignatureLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showKey(signatureKeyId); } }); } // These are the rows that we will retrieve. static final String[] UNIFIED_PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, }; @SuppressWarnings("unused") static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_VERIFIED = 3; static final int INDEX_HAS_ANY_SECRET = 4; @Override public Loader onCreateLoader(int id, Bundle args) { if (id != LOADER_ID_UNIFIED) { return null; } Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri( mSignatureResult.getKeyId()); return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } @Override public void onLoadFinished(Loader loader, Cursor data) { if (loader.getId() != LOADER_ID_UNIFIED) { return; } // If the key is unknown, show it as such if (data.getCount() == 0 || !data.moveToFirst()) { showUnknownKeyStatus(); return; } long signatureKeyId = mSignatureResult.getKeyId(); String userId = data.getString(INDEX_USER_ID); OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); if (userIdSplit.name != null) { mSignatureName.setText(userIdSplit.name); } else { mSignatureName.setText(R.string.user_id_no_name); } if (userIdSplit.email != null) { mSignatureEmail.setText(userIdSplit.email); } else { mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( getActivity(), mSignatureResult.getKeyId())); } // NOTE: Don't use revoked and expired fields from database, they don't show // revoked/expired subkeys boolean isRevoked = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED; boolean isExpired = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED; boolean isInsecure = mSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_INVALID_INSECURE; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; boolean isYours = data.getInt(INDEX_HAS_ANY_SECRET) != 0; if (isRevoked) { mSignatureText.setText(R.string.decrypt_result_signature_revoked_key); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.REVOKED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); onVerifyLoaded(true); } else if (isExpired) { mSignatureText.setText(R.string.decrypt_result_signature_expired_key); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.EXPIRED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else if (isInsecure) { mSignatureText.setText(R.string.decrypt_result_insecure_cryptography); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.INSECURE); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else if (isYours) { mSignatureText.setText(R.string.decrypt_result_signature_secret); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else if (isVerified) { mSignatureText.setText(R.string.decrypt_result_signature_certified); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.VERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } else { mSignatureText.setText(R.string.decrypt_result_signature_uncertified); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.UNVERIFIED); setSignatureLayoutVisibility(View.VISIBLE); setShowAction(signatureKeyId); showErrorOverlay(false); onVerifyLoaded(true); } } @Override public void onLoaderReset(Loader loader) { if (loader.getId() != LOADER_ID_UNIFIED) { return; } setSignatureLayoutVisibility(View.GONE); } private void showUnknownKeyStatus() { final long signatureKeyId = mSignatureResult.getKeyId(); int result = mSignatureResult.getResult(); if (result != OpenPgpSignatureResult.RESULT_KEY_MISSING && result != OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE) { Log.e(Constants.TAG, "got missing status for non-missing key, shouldn't happen!"); } String userId = mSignatureResult.getPrimaryUserId(); OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); if (userIdSplit.name != null) { mSignatureName.setText(userIdSplit.name); } else { mSignatureName.setText(R.string.user_id_no_name); } if (userIdSplit.email != null) { mSignatureEmail.setText(userIdSplit.email); } else { mSignatureEmail.setText(KeyFormattingUtils.beautifyKeyIdWithPrefix( getActivity(), mSignatureResult.getKeyId())); } switch (mSignatureResult.getResult()) { case OpenPgpSignatureResult.RESULT_KEY_MISSING: { mSignatureText.setText(R.string.decrypt_result_signature_missing_key); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.UNKNOWN_KEY); setSignatureLayoutVisibility(View.VISIBLE); mSignatureAction.setText(R.string.decrypt_result_action_Lookup); mSignatureAction .setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_file_download_grey_24dp, 0); mSignatureLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { lookupUnknownKey(signatureKeyId); } }); showErrorOverlay(false); onVerifyLoaded(true); break; } case OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE: { mSignatureText.setText(R.string.decrypt_result_invalid_signature); KeyFormattingUtils.setStatusImage(getActivity(), mSignatureIcon, mSignatureText, State.INVALID); setSignatureLayoutVisibility(View.GONE); showErrorOverlay(true); onVerifyLoaded(false); break; } } } protected abstract void onVerifyLoaded(boolean hideErrorOverlay); public void startDisplayLogActivity() { Activity activity = getActivity(); if (activity == null) { return; } Intent intent = new Intent(activity, LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, mDecryptVerifyResult); activity.startActivity(intent); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { mImportOpHelper.handleActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 49063 Content-Disposition: inline; filename="DecryptListFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "79bf55d79681a0c052b266b6f9171300f73db043" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.content.ClipDescription; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.LabeledIntent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.webkit.MimeTypeMap; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.PopupMenu.OnDismissListener; import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewAnimator; import com.cocosw.bottomsheet.BottomSheet; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.InputDataParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; // this import NEEDS to be above the ViewModel AND SubViewHolder one, or it won't compile! (as of 16.09.15) import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.ViewHolder.SubViewHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; 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.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableHashMap; import org.sufficientlysecure.keychain.util.Preferences; /** Displays a list of decrypted inputs. * * This class has a complex control flow to manage its input URIs. Each URI * which is in mInputUris is also in exactly one of mPendingInputUris, * mCancelledInputUris, mCurrentInputUri, or a key in mInputDataResults. * * Processing of URIs happens using a looping approach: * - There is always exactly one method running which works on mCurrentInputUri * - Processing starts in cryptoOperation(), which pops a new mCurrentInputUri * from the list of mPendingInputUris. * - Once a mCurrentInputUri is finished processing, it should be set to null and * control handed back to cryptoOperation() * - Control flow can move through asynchronous calls, and resume in callbacks * like onActivityResult() or onPermissionRequestResult(). * */ public class DecryptListFragment extends QueueingCryptoOperationFragment implements OnMenuItemClickListener { public static final String ARG_INPUT_URIS = "input_uris"; public static final String ARG_OUTPUT_URIS = "output_uris"; public static final String ARG_CANCELLED_URIS = "cancelled_uris"; public static final String ARG_RESULTS = "results"; public static final String ARG_CAN_DELETE = "can_delete"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; private ArrayList mInputUris; private HashMap mInputDataResults; private ArrayList mPendingInputUris; private ArrayList mCancelledInputUris; private Uri mCurrentInputUri; private boolean mCanDelete; private DecryptFilesAdapter mAdapter; private Uri mCurrentSaveFileUri; /** * Creates new instance of this fragment */ public static DecryptListFragment newInstance(@NonNull ArrayList uris, boolean canDelete) { DecryptListFragment frag = new DecryptListFragment(); Bundle args = new Bundle(); args.putParcelableArrayList(ARG_INPUT_URIS, uris); args.putBoolean(ARG_CAN_DELETE, canDelete); frag.setArguments(args); return frag; } public DecryptListFragment() { super(null); } /** * Inflate the layout for this fragment */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_files_list_fragment, container, false); RecyclerView vFilesList = (RecyclerView) view.findViewById(R.id.decrypted_files_list); vFilesList.addItemDecoration(new SpacesItemDecoration( FormattingUtils.dpToPx(getActivity(), 4))); vFilesList.setHasFixedSize(true); // TODO make this a grid, for tablets! vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); vFilesList.setItemAnimator(new DefaultItemAnimator() { @Override public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { return true; } }); mAdapter = new DecryptFilesAdapter(); vFilesList.setAdapter(mAdapter); return view; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris); HashMap results = new HashMap<>(mInputUris.size()); for (Uri uri : mInputUris) { if (mPendingInputUris.contains(uri)) { continue; } InputDataResult result = mAdapter.getItemResult(uri); if (result != null) { results.put(uri, result); } } outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results)); outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults)); outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); outState.putBoolean(ARG_CAN_DELETE, mCanDelete); // this does not save mCurrentInputUri - if anything is being // processed at fragment recreation time, the operation in // progress will be lost! } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); ArrayList inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); ArrayList cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); ParcelableHashMap results = args.getParcelable(ARG_RESULTS); mCanDelete = args.getBoolean(ARG_CAN_DELETE, false); displayInputUris(inputUris, cancelledUris, results != null ? results.getMap() : null ); } private void displayInputUris( ArrayList inputUris, ArrayList cancelledUris, HashMap results) { mInputUris = inputUris; mCurrentInputUri = null; mInputDataResults = results != null ? results : new HashMap(inputUris.size()); mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList(); mPendingInputUris = new ArrayList<>(); for (final Uri uri : inputUris) { mAdapter.add(uri); boolean uriIsCancelled = mCancelledInputUris.contains(uri); if (uriIsCancelled) { mAdapter.setCancelled(uri, true); continue; } boolean uriHasResult = results != null && results.containsKey(uri); if (uriHasResult) { processResult(uri); continue; } mPendingInputUris.add(uri); } // check if there are any pending input uris cryptoOperation(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { Uri saveUri = data.getData(); saveFile(saveUri); mCurrentInputUri = null; } return; } default: { super.onActivityResult(requestCode, resultCode, data); } } } @TargetApi(VERSION_CODES.KITKAT) private void saveFileDialog(InputDataResult result, int index) { Activity activity = getActivity(); if (activity == null) { return; } OpenPgpMetadata metadata = result.mMetadata.get(index); mCurrentSaveFileUri = result.getOutputUris().get(index); String filename = metadata.getFilename(); if (TextUtils.isEmpty(filename)) { String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType()); filename = "decrypted" + (ext != null ? "."+ext : ""); } // requires >=kitkat FileHelper.saveDocument(this, filename, metadata.getMimeType(), REQUEST_CODE_OUTPUT); } private void saveFile(Uri saveUri) { if (mCurrentSaveFileUri == null) { return; } Uri decryptedFileUri = mCurrentSaveFileUri; mCurrentInputUri = null; hideKeyboard(); Activity activity = getActivity(); if (activity == null) { return; } try { FileHelper.copyUriData(activity, decryptedFileUri, saveUri); Notify.create(activity, R.string.file_saved, Style.OK).show(); } catch (IOException e) { Log.e(Constants.TAG, "error saving file", e); Notify.create(activity, R.string.error_saving_file, Style.ERROR).show(); } } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { mAdapter.setProgress(mCurrentInputUri, progress, max, msg); return true; } @Override public void onQueuedOperationError(InputDataResult result) { final Uri uri = mCurrentInputUri; mCurrentInputUri = null; Activity activity = getActivity(); if (activity != null && "com.fsck.k9.attachmentprovider".equals(uri.getHost())) { Toast.makeText(getActivity(), R.string.error_reading_k9, Toast.LENGTH_LONG).show(); } mAdapter.addResult(uri, result); cryptoOperation(); } @Override public void onQueuedOperationSuccess(InputDataResult result) { Uri uri = mCurrentInputUri; mCurrentInputUri = null; Activity activity = getActivity(); boolean isSingleInput = mInputDataResults.isEmpty() && mPendingInputUris.isEmpty(); if (isSingleInput) { // there is always at least one mMetadata object, so we know this is >= 1 already boolean isSingleMetadata = result.mMetadata.size() == 1; OpenPgpMetadata metadata = result.mMetadata.get(0); boolean isText = "text/plain".equals(metadata.getMimeType()); boolean isOverSized = metadata.getOriginalSize() > Constants.TEXT_LENGTH_LIMIT; if (isSingleMetadata && isText && !isOverSized) { Intent displayTextIntent = new Intent(activity, DisplayTextActivity.class) .setDataAndType(result.mOutputUris.get(0), "text/plain") .putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult) .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata); activity.startActivity(displayTextIntent); activity.finish(); return; } } mInputDataResults.put(uri, result); processResult(uri); cryptoOperation(); } @Override public void onCryptoOperationCancelled() { super.onCryptoOperationCancelled(); final Uri uri = mCurrentInputUri; mCurrentInputUri = null; mCancelledInputUris.add(uri); mAdapter.setCancelled(uri, true); cryptoOperation(); } HashMap mIconCache = new HashMap<>(); private void processResult(final Uri uri) { new AsyncTask() { @Override protected Void doInBackground(Void... params) { InputDataResult result = mInputDataResults.get(uri); Context context = getActivity(); if (context == null) { return null; } for (int i = 0; i < result.getOutputUris().size(); i++) { Uri outputUri = result.getOutputUris().get(i); if (mIconCache.containsKey(outputUri)) { continue; } OpenPgpMetadata metadata = result.mMetadata.get(i); String type = metadata.getMimeType(); Drawable icon = null; if (ClipDescription.compareMimeTypes(type, "text/plain")) { // noinspection deprecation, this should be called from Context, but not available in minSdk icon = getResources().getDrawable(R.drawable.ic_chat_black_24dp); } else if (ClipDescription.compareMimeTypes(type, "application/octet-stream")) { // icons for this are just confusing // noinspection deprecation, this should be called from Context, but not available in minSdk icon = getResources().getDrawable(R.drawable.ic_doc_generic_am); } else if (ClipDescription.compareMimeTypes(type, Constants.MIME_TYPE_KEYS)) { // noinspection deprecation, this should be called from Context, but not available in minSdk icon = getResources().getDrawable(R.drawable.ic_key_plus_grey600_24dp); } else if (ClipDescription.compareMimeTypes(type, "image/*")) { int px = FormattingUtils.dpToPx(context, 32); Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px)); icon = new BitmapDrawable(context.getResources(), bitmap); } if (icon == null) { final Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, type); final List matches = context.getPackageManager().queryIntentActivities(intent, 0); // noinspection LoopStatementThatDoesntLoop for (ResolveInfo match : matches) { icon = match.loadIcon(getActivity().getPackageManager()); break; } } if (icon != null) { mIconCache.put(outputUri, icon); } } return null; } @Override protected void onPostExecute(Void v) { InputDataResult result = mInputDataResults.get(uri); mAdapter.addResult(uri, result); } }.execute(); } public void retryUri(Uri uri) { // never interrupt running operations! if (mCurrentInputUri != null) { return; } // un-cancel this one mCancelledInputUris.remove(uri); mInputDataResults.remove(uri); mPendingInputUris.add(uri); mAdapter.resetItemData(uri); // check if there are any pending input uris cryptoOperation(); } public void displayBottomSheet(final InputDataResult result, final int index) { Activity activity = getActivity(); if (activity == null) { return; } new BottomSheet.Builder(activity).sheet(R.menu.decrypt_bottom_sheet).listener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.decrypt_open: displayWithViewIntent(result, index, false, true); break; case R.id.decrypt_share: displayWithViewIntent(result, index, true, true); break; case R.id.decrypt_save: // only inside the menu xml for Android >= 4.4 saveFileDialog(result, index); break; } return false; } }).grid().show(); } public void displayWithViewIntent(InputDataResult result, int index, boolean share, boolean forceChooser) { Activity activity = getActivity(); if (activity == null) { return; } Uri outputUri = result.getOutputUris().get(index); OpenPgpMetadata metadata = result.mMetadata.get(index); // text/plain is a special case where we extract the uri content into // the EXTRA_TEXT extra ourselves, and display a chooser which includes // OpenKeychain's internal viewer if ("text/plain".equals(metadata.getMimeType())) { if (share) { try { String plaintext = FileHelper.readTextFromUri(activity, outputUri, null); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, plaintext); Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_share)); startActivity(chooserIntent); } catch (IOException e) { Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show(); } return; } Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, "text/plain"); if (forceChooser) { LabeledIntent internalIntent = new LabeledIntent( new Intent(intent) .setClass(activity, DisplayTextActivity.class) .putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult) .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata), BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher); Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] { internalIntent }); startActivity(chooserIntent); } else { intent.setClass(activity, DisplayTextActivity.class); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult); intent.putExtra(DisplayTextActivity.EXTRA_METADATA, metadata); startActivity(intent); } } else { Intent intent; if (share) { intent = new Intent(Intent.ACTION_SEND); intent.setType(metadata.getMimeType()); intent.putExtra(Intent.EXTRA_STREAM, outputUri); } else { intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, metadata.getMimeType()); if (!forceChooser && Constants.MIME_TYPE_KEYS.equals(metadata.getMimeType())) { // bind Intent to this OpenKeychain, don't allow other apps to intercept here! intent.setPackage(getActivity().getPackageName()); } } intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); chooserIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (!share && ClipDescription.compareMimeTypes(metadata.getMimeType(), "text/*")) { LabeledIntent internalIntent = new LabeledIntent( new Intent(intent) .setClass(activity, DisplayTextActivity.class) .putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult) .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata), BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] { internalIntent }); } startActivity(chooserIntent); } } @Override public InputDataParcel createOperationInput() { Activity activity = getActivity(); if (activity == null) { return null; } if (mCurrentInputUri == null) { if (mPendingInputUris.isEmpty()) { // nothing left to do return null; } mCurrentInputUri = mPendingInputUris.remove(0); } Log.d(Constants.TAG, "mCurrentInputUri=" + mCurrentInputUri); if ( ! checkAndRequestReadPermission(activity, mCurrentInputUri)) { return null; } PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel() .setAllowSymmetricDecryption(true); return new InputDataParcel(mCurrentInputUri, decryptInput); } /** * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. * * This method returns true on Android < 6, or if permission is already granted. It * requests the permission and returns false otherwise, taking over responsibility * for mCurrentInputUri. * * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html */ private boolean checkAndRequestReadPermission(Activity activity, final Uri uri) { if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return true; } // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html if (Build.VERSION.SDK_INT < VERSION_CODES.M) { return true; } if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { return true; } requestPermissions( new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } boolean permissionWasGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; if (permissionWasGranted) { // permission granted -> retry all cancelled file uris Iterator it = mCancelledInputUris.iterator(); while (it.hasNext()) { Uri uri = it.next(); if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { continue; } it.remove(); mPendingInputUris.add(uri); mAdapter.setCancelled(uri, false); } } else { // permission denied -> cancel current, and all pending file uris mCancelledInputUris.add(mCurrentInputUri); mAdapter.setCancelled(mCurrentInputUri, true); mCurrentInputUri = null; Iterator it = mPendingInputUris.iterator(); while (it.hasNext()) { Uri uri = it.next(); if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { continue; } it.remove(); mCancelledInputUris.add(uri); mAdapter.setCancelled(uri, true); } } // hand control flow back cryptoOperation(); } @Override public boolean onMenuItemClick(MenuItem menuItem) { if (mAdapter.mMenuClickedModel == null || !mAdapter.mMenuClickedModel.hasResult()) { return false; } // don't process menu items until all items are done! if (!mPendingInputUris.isEmpty()) { return true; } Activity activity = getActivity(); if (activity == null) { return false; } ViewModel model = mAdapter.mMenuClickedModel; switch (menuItem.getItemId()) { case R.id.view_log: Intent intent = new Intent(activity, LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult); activity.startActivity(intent); return true; case R.id.decrypt_delete: deleteFile(activity, model.mInputUri); return true; } return false; } private void lookupUnknownKey(final Uri inputUri, long unknownKeyId) { final ArrayList keyList; final String keyserver; // search config keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); { ParcelableKeyRing keyEntry = new ParcelableKeyRing(null, KeyFormattingUtils.convertKeyIdToHex(unknownKeyId)); ArrayList selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); keyList = selectedEntries; } CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(keyList, keyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { retryUri(inputUri); } @Override public void onCryptoOperationCancelled() { mAdapter.setProcessingKeyLookup(inputUri, false); } @Override public void onCryptoOperationError(ImportKeyResult result) { result.createNotify(getActivity()).show(); mAdapter.setProcessingKeyLookup(inputUri, false); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mAdapter.setProcessingKeyLookup(inputUri, true); CryptoOperationHelper importOpHelper = new CryptoOperationHelper<>(2, this, callback, null); importOpHelper.cryptoOperation(); } private void deleteFile(Activity activity, Uri uri) { // we can only ever delete a file once, if we got this far either it's gone or it will never work mCanDelete = false; try { int deleted = FileHelper.deleteFileSecurely(activity, uri); if (deleted > 0) { Notify.create(activity, R.string.file_delete_ok, Style.OK).show(); } else { Notify.create(activity, R.string.file_delete_none, Style.WARN).show(); } } catch (Exception e) { Log.e(Constants.TAG, "exception deleting file", e); Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); } } public class DecryptFilesAdapter extends RecyclerView.Adapter { private ArrayList mDataset; private ViewModel mMenuClickedModel; public class ViewModel { Uri mInputUri; InputDataResult mResult; int mProgress, mMax; String mProgressMsg; OnClickListener mCancelled; boolean mProcessingKeyLookup; ViewModel(Uri uri) { mInputUri = uri; mProgress = 0; mMax = 100; mCancelled = null; } void setResult(InputDataResult result) { mResult = result; } boolean hasResult() { return mResult != null; } void setCancelled(OnClickListener retryListener) { mCancelled = retryListener; } void setProgress(int progress, int max, String msg) { if (msg != null) { mProgressMsg = msg; } mProgress = progress; mMax = max; } void setProcessingKeyLookup(boolean processingKeyLookup) { mProcessingKeyLookup = processingKeyLookup; } // Depends on inputUri only @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ViewModel viewModel = (ViewModel) o; if (mInputUri == null) { return viewModel.mInputUri == null; } return mInputUri.equals(viewModel.mInputUri); } // Depends on inputUri only @Override public int hashCode() { return mResult != null ? mResult.hashCode() : 0; } @Override public String toString() { return mResult.toString(); } } // Provide a suitable constructor (depends on the kind of dataset) public DecryptFilesAdapter() { mDataset = new ArrayList<>(); } // Create new views (invoked by the layout manager) @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //inflate your layout and pass it to view holder View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.decrypt_list_entry, parent, false); return new ViewHolder(v); } // Replace the contents of a view (invoked by the layout manager) @Override public void onBindViewHolder(ViewHolder holder, final int position) { // - get element from your dataset at this position // - replace the contents of the view with that element final ViewModel model = mDataset.get(position); if (model.mCancelled != null) { bindItemCancelled(holder, model); return; } if (!model.hasResult()) { bindItemProgress(holder, model); return; } if (model.mResult.success()) { bindItemSuccess(holder, model); } else { bindItemFailure(holder, model); } } private void bindItemCancelled(ViewHolder holder, ViewModel model) { holder.vAnimator.setDisplayedChild(3); holder.vCancelledRetry.setOnClickListener(model.mCancelled); } private void bindItemProgress(ViewHolder holder, ViewModel model) { holder.vAnimator.setDisplayedChild(0); holder.vProgress.setProgress(model.mProgress); holder.vProgress.setMax(model.mMax); if (model.mProgressMsg != null) { holder.vProgressMsg.setText(model.mProgressMsg); } } private void bindItemSuccess(ViewHolder holder, final ViewModel model) { holder.vAnimator.setDisplayedChild(1); KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult, model.mProcessingKeyLookup); int numFiles = model.mResult.getOutputUris().size(); holder.resizeFileList(numFiles, LayoutInflater.from(getActivity())); for (int i = 0; i < numFiles; i++) { Uri outputUri = model.mResult.getOutputUris().get(i); OpenPgpMetadata metadata = model.mResult.mMetadata.get(i); SubViewHolder fileHolder = holder.mFileHolderList.get(i); String filename; if (metadata == null) { filename = getString(R.string.filename_unknown); } else if ( ! TextUtils.isEmpty(metadata.getFilename())) { filename = metadata.getFilename(); } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), Constants.MIME_TYPE_KEYS)) { filename = getString(R.string.filename_keys); } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), "text/plain")) { filename = getString(R.string.filename_unknown_text); } else { filename = getString(R.string.filename_unknown); } fileHolder.vFilename.setText(filename); long size = metadata == null ? 0 : metadata.getOriginalSize(); if (size == -1 || size == 0) { fileHolder.vFilesize.setText(""); } else { fileHolder.vFilesize.setText(FileHelper.readableFileSize(size)); } if (mIconCache.containsKey(outputUri)) { fileHolder.vThumbnail.setImageDrawable(mIconCache.get(outputUri)); } else { fileHolder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); } // save index closure-style :) final int idx = i; fileHolder.vFile.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View view) { if (model.mResult.success()) { displayBottomSheet(model.mResult, idx); return true; } return false; } }); fileHolder.vFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (model.mResult.success()) { displayWithViewIntent(model.mResult, idx, false, false); } } }); } OpenPgpSignatureResult sigResult = model.mResult.mDecryptVerifyResult.getSignatureResult(); if (sigResult != null) { final long keyId = sigResult.getKeyId(); if (sigResult.getResult() != OpenPgpSignatureResult.RESULT_KEY_MISSING) { holder.vSignatureLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Activity activity = getActivity(); if (activity == null) { return; } Intent intent = new Intent(activity, ViewKeyActivity.class); intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); activity.startActivity(intent); } }); } else { holder.vSignatureLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { lookupUnknownKey(model.mInputUri, keyId); } }); } } holder.vContextMenu.setTag(model); holder.vContextMenu.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Activity activity = getActivity(); if (activity == null) { return; } mMenuClickedModel = model; PopupMenu menu = new PopupMenu(activity, view); menu.inflate(R.menu.decrypt_item_context_menu); menu.setOnMenuItemClickListener(DecryptListFragment.this); menu.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(PopupMenu popupMenu) { mMenuClickedModel = null; } }); menu.getMenu().findItem(R.id.decrypt_delete).setEnabled(mCanDelete); menu.show(); } }); } private void bindItemFailure(ViewHolder holder, final ViewModel model) { holder.vAnimator.setDisplayedChild(2); holder.vErrorMsg.setText(model.mResult.getLog().getLast().mType.getMsgId()); holder.vErrorViewLog.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Activity activity = getActivity(); if (activity == null) { return; } Intent intent = new Intent(activity, LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult); activity.startActivity(intent); } }); } // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { return mDataset.size(); } public InputDataResult getItemResult(Uri uri) { ViewModel model = new ViewModel(uri); int pos = mDataset.indexOf(model); if (pos == -1) { return null; } model = mDataset.get(pos); return model.mResult; } public void add(Uri uri) { ViewModel newModel = new ViewModel(uri); mDataset.add(newModel); notifyItemInserted(mDataset.size()); } public void setProgress(Uri uri, int progress, int max, String msg) { ViewModel newModel = new ViewModel(uri); int pos = mDataset.indexOf(newModel); mDataset.get(pos).setProgress(progress, max, msg); notifyItemChanged(pos); } public void setCancelled(final Uri uri, boolean isCancelled) { ViewModel newModel = new ViewModel(uri); int pos = mDataset.indexOf(newModel); if (isCancelled) { mDataset.get(pos).setCancelled(new OnClickListener() { @Override public void onClick(View v) { retryUri(uri); } }); } else { mDataset.get(pos).setCancelled(null); } notifyItemChanged(pos); } public void setProcessingKeyLookup(Uri uri, boolean processingKeyLookup) { ViewModel newModel = new ViewModel(uri); int pos = mDataset.indexOf(newModel); mDataset.get(pos).setProcessingKeyLookup(processingKeyLookup); notifyItemChanged(pos); } public void addResult(Uri uri, InputDataResult result) { ViewModel model = new ViewModel(uri); int pos = mDataset.indexOf(model); model = mDataset.get(pos); model.setResult(result); notifyItemChanged(pos); } public void resetItemData(Uri uri) { ViewModel model = new ViewModel(uri); int pos = mDataset.indexOf(model); model = mDataset.get(pos); model.setResult(null); model.setCancelled(null); model.setProcessingKeyLookup(false); notifyItemChanged(pos); } } // Provide a reference to the views for each data item // Complex data items may need more than one view per item, and // you provide access to all the views for a data item in a view holder public static class ViewHolder extends RecyclerView.ViewHolder implements StatusHolder { public ViewAnimator vAnimator; public ProgressBar vProgress; public TextView vProgressMsg; public ImageView vEncStatusIcon; public TextView vEncStatusText; public ImageView vSigStatusIcon; public TextView vSigStatusText; public View vSignatureLayout; public TextView vSignatureName; public TextView vSignatureMail; public ViewAnimator vSignatureAction; public View vContextMenu; public TextView vErrorMsg; public ImageView vErrorViewLog; public ImageView vCancelledRetry; public LinearLayout vFileList; public static class SubViewHolder { public View vFile; public TextView vFilename; public TextView vFilesize; public ImageView vThumbnail; public SubViewHolder(View itemView) { vFile = itemView.findViewById(R.id.file); vFilename = (TextView) itemView.findViewById(R.id.filename); vFilesize = (TextView) itemView.findViewById(R.id.filesize); vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); } } public ArrayList mFileHolderList = new ArrayList<>(); private int mCurrentFileListSize = 0; public ViewHolder(View itemView) { super(itemView); vAnimator = (ViewAnimator) itemView.findViewById(R.id.view_animator); vProgress = (ProgressBar) itemView.findViewById(R.id.progress); vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg); vEncStatusIcon = (ImageView) itemView.findViewById(R.id.result_encryption_icon); vEncStatusText = (TextView) itemView.findViewById(R.id.result_encryption_text); vSigStatusIcon = (ImageView) itemView.findViewById(R.id.result_signature_icon); vSigStatusText = (TextView) itemView.findViewById(R.id.result_signature_text); vSignatureLayout = itemView.findViewById(R.id.result_signature_layout); vSignatureName = (TextView) itemView.findViewById(R.id.result_signature_name); vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); vSignatureAction = (ViewAnimator) itemView.findViewById(R.id.result_signature_action); vFileList = (LinearLayout) itemView.findViewById(R.id.file_list); for (int i = 0; i < vFileList.getChildCount(); i++) { mFileHolderList.add(new SubViewHolder(vFileList.getChildAt(i))); mCurrentFileListSize += 1; } vContextMenu = itemView.findViewById(R.id.context_menu); vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg); vErrorViewLog = (ImageView) itemView.findViewById(R.id.result_error_log); vCancelledRetry = (ImageView) itemView.findViewById(R.id.cancel_retry); } public void resizeFileList(int size, LayoutInflater inflater) { int childCount = vFileList.getChildCount(); // if we require more children, create them while (childCount < size) { View v = inflater.inflate(R.layout.decrypt_list_file_item, null); vFileList.addView(v); mFileHolderList.add(new SubViewHolder(v)); childCount += 1; } while (size < mCurrentFileListSize) { mCurrentFileListSize -= 1; vFileList.getChildAt(mCurrentFileListSize).setVisibility(View.GONE); } while (size > mCurrentFileListSize) { vFileList.getChildAt(mCurrentFileListSize).setVisibility(View.VISIBLE); mCurrentFileListSize += 1; } } @Override public ImageView getEncryptionStatusIcon() { return vEncStatusIcon; } @Override public TextView getEncryptionStatusText() { return vEncStatusText; } @Override public ImageView getSignatureStatusIcon() { return vSigStatusIcon; } @Override public TextView getSignatureStatusText() { return vSigStatusText; } @Override public View getSignatureLayout() { return vSignatureLayout; } @Override public ViewAnimator getSignatureAction() { return vSignatureAction; } @Override public TextView getSignatureUserName() { return vSignatureName; } @Override public TextView getSignatureUserEmail() { return vSignatureMail; } @Override public boolean hasEncrypt() { return true; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 17317 Content-Disposition: inline; filename="DeleteKeyDialogActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "a32dc175c9469c8eb20db99dc8bce5e94fc36550" /* * Copyright (C) 2013-2015 Dominik Schürmann * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2015 Adithya Abraham Philip * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; import android.widget.Spinner; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.RevokeResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; import java.util.HashMap; public class DeleteKeyDialogActivity extends FragmentActivity { public static final String EXTRA_DELETE_MASTER_KEY_IDS = "extra_delete_master_key_ids"; public static final String EXTRA_HAS_SECRET = "extra_has_secret"; public static final String EXTRA_KEYSERVER = "extra_keyserver"; private CryptoOperationHelper mDeleteOpHelper; private CryptoOperationHelper mRevokeOpHelper; private long[] mMasterKeyIds; private boolean mHasSecret; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDeleteOpHelper = new CryptoOperationHelper<>(1, DeleteKeyDialogActivity.this, getDeletionCallback(), R.string.progress_deleting); mRevokeOpHelper = new CryptoOperationHelper<>(2, this, getRevocationCallback(), R.string.progress_revoking_uploading); mMasterKeyIds = getIntent().getLongArrayExtra(EXTRA_DELETE_MASTER_KEY_IDS); mHasSecret = getIntent().getBooleanExtra(EXTRA_HAS_SECRET, false); if (mMasterKeyIds.length > 1 && mHasSecret) { // secret keys can only be deleted individually OperationResult.OperationLog log = new OperationResult.OperationLog(); log.add(OperationResult.LogType.MSG_DEL_ERROR_MULTI_SECRET, 0); returnResult(new DeleteResult(OperationResult.RESULT_ERROR, log, 0, mMasterKeyIds.length)); } if (mMasterKeyIds.length == 1 && mHasSecret) { // if mMasterKeyIds.length == 0 we let the DeleteOperation respond try { HashMap data = new ProviderHelper(this).getUnifiedData( mMasterKeyIds[0], new String[]{ KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED }, new int[]{ ProviderHelper.FIELD_TYPE_STRING, ProviderHelper.FIELD_TYPE_INTEGER } ); String name; OpenPgpUtils.UserId mainUserId = KeyRing.splitUserId( (String) data.get(KeychainContract.KeyRings.USER_ID)); if (mainUserId.name != null) { name = mainUserId.name; } else { name = getString(R.string.user_id_no_name); } if ((long) data.get(KeychainContract.KeyRings.IS_REVOKED) > 0) { showNormalDeleteDialog(); } else { showRevokeDeleteDialog(name); } } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "Secret key to delete not found at DeleteKeyDialogActivity for " + mMasterKeyIds[0], e); finish(); } } else { showNormalDeleteDialog(); } } private void showNormalDeleteDialog() { DeleteKeyDialogFragment deleteKeyDialogFragment = DeleteKeyDialogFragment.newInstance(mMasterKeyIds, mHasSecret); deleteKeyDialogFragment.show(getSupportFragmentManager(), "deleteKeyDialog"); } private void showRevokeDeleteDialog(String keyname) { RevokeDeleteDialogFragment fragment = RevokeDeleteDialogFragment.newInstance(keyname); fragment.show(getSupportFragmentManager(), "deleteRevokeDialog"); } private void startRevocationOperation() { mRevokeOpHelper.cryptoOperation(new CryptoInputParcel(new Date(), false)); } private void startDeletionOperation() { mDeleteOpHelper.cryptoOperation(); } private CryptoOperationHelper.Callback getRevocationCallback() { return new CryptoOperationHelper.Callback() { @Override public RevokeKeyringParcel createOperationInput() { return new RevokeKeyringParcel(mMasterKeyIds[0], true, getIntent().getStringExtra(EXTRA_KEYSERVER)); } @Override public void onCryptoOperationSuccess(RevokeResult result) { returnResult(result); } @Override public void onCryptoOperationCancelled() { setResult(RESULT_CANCELED); finish(); } @Override public void onCryptoOperationError(RevokeResult result) { returnResult(result); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; } private CryptoOperationHelper.Callback getDeletionCallback() { return new CryptoOperationHelper.Callback() { @Override public DeleteKeyringParcel createOperationInput() { return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret); } @Override public void onCryptoOperationSuccess(DeleteResult result) { returnResult(result); } @Override public void onCryptoOperationCancelled() { setResult(RESULT_CANCELED); finish(); } @Override public void onCryptoOperationError(DeleteResult result) { returnResult(result); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; } private void returnResult(OperationResult result) { Intent intent = new Intent(); intent.putExtra(OperationResult.EXTRA_RESULT, result); setResult(RESULT_OK, intent); finish(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data); mRevokeOpHelper.handleActivityResult(requestCode, resultCode, data); } public static class DeleteKeyDialogFragment extends DialogFragment { private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids"; private static final String ARG_HAS_SECRET = "has_secret"; private TextView mMainMessage; private View mInflateView; /** * Creates new instance of this delete file dialog fragment */ public static DeleteKeyDialogFragment newInstance(long[] masterKeyIds, boolean hasSecret) { DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); Bundle args = new Bundle(); args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds); args.putBoolean(ARG_HAS_SECRET, hasSecret); frag.setArguments(args); return frag; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); final boolean hasSecret = getArguments().getBoolean(ARG_HAS_SECRET); ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); // Setup custom View to display in AlertDialog LayoutInflater inflater = LayoutInflater.from(theme); mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null); builder.setView(mInflateView); mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage); // If only a single key has been selected if (masterKeyIds.length == 1) { long masterKeyId = masterKeyIds[0]; try { HashMap data = new ProviderHelper(activity).getUnifiedData( masterKeyId, new String[]{ KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.HAS_ANY_SECRET }, new int[]{ ProviderHelper.FIELD_TYPE_STRING, ProviderHelper.FIELD_TYPE_INTEGER } ); String name; OpenPgpUtils.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeychainContract.KeyRings.USER_ID)); if (mainUserId.name != null) { name = mainUserId.name; } else { name = getString(R.string.user_id_no_name); } if (hasSecret) { // show title only for secret key deletions, // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior builder.setTitle(getString(R.string.title_delete_secret_key, name)); mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); } else { mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); } } catch (ProviderHelper.NotFoundException e) { dismiss(); return null; } } else { mMainMessage.setText(R.string.key_deletion_confirmation_multi); } builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ((DeleteKeyDialogActivity) getActivity()).startDeletionOperation(); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); return builder.show(); } @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); getActivity().setResult(RESULT_CANCELED); getActivity().finish(); } } public static class RevokeDeleteDialogFragment extends DialogFragment { public static final String ARG_KEY_NAME = "arg_key_name"; public static RevokeDeleteDialogFragment newInstance(String keyName) { Bundle args = new Bundle(); args.putString(ARG_KEY_NAME, keyName); RevokeDeleteDialogFragment frag = new RevokeDeleteDialogFragment(); frag.setArguments(args); return frag; } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); final String CHOICE_REVOKE = getString(R.string.del_rev_dialog_choice_rev_upload); final String CHOICE_DELETE = getString(R.string.del_rev_dialog_choice_delete); ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); builder.setTitle(getString(R.string.del_rev_dialog_title, getArguments().get(ARG_KEY_NAME))); LayoutInflater inflater = LayoutInflater.from(theme); View view = inflater.inflate(R.layout.del_rev_dialog, null); builder.setView(view); final Spinner spinner = (Spinner) view.findViewById(R.id.spinner); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); builder.setPositiveButton(R.string.del_rev_dialog_btn_revoke, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String choice = spinner.getSelectedItem().toString(); if (choice.equals(CHOICE_REVOKE)) { ((DeleteKeyDialogActivity) activity) .startRevocationOperation(); } else if (choice.equals(CHOICE_DELETE)) { ((DeleteKeyDialogActivity) activity) .showNormalDeleteDialog(); } else { throw new AssertionError( "Unsupported delete type in RevokeDeleteDialogFragment"); } } }); final AlertDialog alertDialog = builder.show(); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View view, int pos, long id) { String choice = parent.getItemAtPosition(pos).toString(); if (choice.equals(CHOICE_REVOKE)) { alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) .setText(R.string.del_rev_dialog_btn_revoke); } else if (choice.equals(CHOICE_DELETE)) { alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) .setText(R.string.del_rev_dialog_btn_delete); } else { throw new AssertionError( "Unsupported delete type in RevokeDeleteDialogFragment"); } } public void onNothingSelected(AdapterView parent) { } }); return alertDialog; } @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); getActivity().setResult(RESULT_CANCELED); getActivity().finish(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3790 Content-Disposition: inline; filename="DisplayTextActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "2fa1aa1c2c6031002cfd6a1b2ba5d42013a58a35" /* * Copyright (C) 2012-2015 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.widget.Toast; import org.openintents.openpgp.OpenPgpMetadata; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; public class DisplayTextActivity extends BaseActivity { public static final String EXTRA_RESULT = "result"; public static final String EXTRA_METADATA = "metadata"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setFullScreenDialogClose(Activity.RESULT_CANCELED, false); // Handle intent actions handleActions(savedInstanceState, getIntent()); } @Override protected void initLayout() { setContentView(R.layout.decrypt_text_activity); } /** * Handles all actions with this intent */ private void handleActions(Bundle savedInstanceState, Intent intent) { if (savedInstanceState != null) { return; } DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_RESULT); OpenPgpMetadata metadata = intent.getParcelableExtra(EXTRA_METADATA); String plaintext; try { plaintext = FileHelper.readTextFromUri(this, intent.getData(), metadata.getCharset()); } catch (IOException e) { Toast.makeText(this, R.string.error_preparing_data, Toast.LENGTH_LONG).show(); return; } if (plaintext != null) { if (plaintext.length() > Constants.TEXT_LENGTH_LIMIT) { plaintext = plaintext.substring(0, Constants.TEXT_LENGTH_LIMIT); Notify.create(this, R.string.snack_text_too_long, Style.WARN).show(); } loadFragment(plaintext, result); } else { Toast.makeText(this, R.string.error_invalid_data, Toast.LENGTH_LONG).show(); finish(); } } private void loadFragment(String plaintext, DecryptVerifyResult result) { // Create an instance of the fragment Fragment frag = DisplayTextFragment.newInstance(plaintext, result); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() .replace(R.id.decrypt_text_fragment_container, frag) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5029 Content-Disposition: inline; filename="DisplayTextFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "97f7231688ca799949477bb64e90856e9fa1b564" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; public class DisplayTextFragment extends DecryptFragment { public static final String ARG_PLAINTEXT = "plaintext"; // view private TextView mText; // model (no state to persist though, that's all in arguments!) private boolean mShowMenuOptions = false; public static DisplayTextFragment newInstance(String plaintext, DecryptVerifyResult result) { DisplayTextFragment frag = new DisplayTextFragment(); Bundle args = new Bundle(); args.putString(ARG_PLAINTEXT, plaintext); args.putParcelable(ARG_DECRYPT_VERIFY_RESULT, result); frag.setArguments(args); return frag; } private Intent createSendIntent(String text) { Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, text); sendIntent.setType("text/plain"); return sendIntent; } private void copyToClipboard(String text) { Activity activity = getActivity(); if (activity == null) { return; } ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); if (clipMan == null) { Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); return; } clipMan.setPrimaryClip(ClipData.newPlainText(Constants.CLIPBOARD_LABEL, text)); Notify.create(activity, R.string.text_copied_to_clipboard, Style.OK).show(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false); mText = (TextView) view.findViewById(R.id.decrypt_text_plaintext); return view; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Bundle args = getArguments(); String plaintext = args.getString(ARG_PLAINTEXT); DecryptVerifyResult result = args.getParcelable(ARG_DECRYPT_VERIFY_RESULT); // display signature result in activity mText.setText(plaintext); loadVerifyResult(result); } @Override protected void onVerifyLoaded(boolean hideErrorOverlay) { mShowMenuOptions = hideErrorOverlay; getActivity().supportInvalidateOptionsMenu(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); if (mShowMenuOptions) { inflater.inflate(R.menu.decrypt_menu, menu); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.decrypt_share: { startActivity(Intent.createChooser(createSendIntent(mText.getText().toString()), getString(R.string.title_share_message))); break; } case R.id.decrypt_copy: { copyToClipboard(mText.getText().toString()); break; } case R.id.decrypt_view_log: { startDisplayLogActivity(); break; } default: { return super.onOptionsItemSelected(item); } } return true; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2217 Content-Disposition: inline; filename="EditIdentitiesActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "cbe025eeaa3d3d8113e5985bad2a651b90ca73db" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.net.Uri; import android.os.Bundle; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class EditIdentitiesActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Uri dataUri = getIntent().getData(); if (dataUri == null) { Log.e(Constants.TAG, "Either a key Uri or EXTRA_SAVE_KEYRING_PARCEL is required!"); finish(); return; } loadFragment(savedInstanceState, dataUri); } @Override protected void initLayout() { setContentView(R.layout.edit_identities_activity); } private void loadFragment(Bundle savedInstanceState, Uri dataUri) { // 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. if (savedInstanceState != null) { return; } EditIdentitiesFragment editIdentitiesFragment = EditIdentitiesFragment.newInstance(dataUri); getSupportFragmentManager().beginTransaction() .replace(R.id.edit_key_fragment_container, editIdentitiesFragment) .commit(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 17259 Content-Disposition: inline; filename="EditIdentitiesFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "aec0bb7119fb6e7dae44ac35e9b6d92cceb3cfbf" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.CheckBox; import android.widget.ListView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; public class EditIdentitiesFragment extends Fragment implements LoaderManager.LoaderCallbacks { public static final String ARG_DATA_URI = "uri"; private CheckBox mUploadKeyCheckbox; private ListView mUserIdsList; private ListView mUserIdsAddedList; private View mAddUserId; private static final int LOADER_ID_USER_IDS = 0; private UserIdsAdapter mUserIdsAdapter; private UserIdsAddedAdapter mUserIdsAddedAdapter; private Uri mDataUri; private SaveKeyringParcel mSaveKeyringParcel; private CryptoOperationHelper mEditOpHelper; private CryptoOperationHelper mUploadOpHelper; private String mPrimaryUserId; /** * Creates new instance of this fragment */ public static EditIdentitiesFragment newInstance(Uri dataUri) { EditIdentitiesFragment frag = new EditIdentitiesFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.edit_identities_fragment, null); mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.edit_identities_upload_checkbox); mUserIdsList = (ListView) view.findViewById(R.id.edit_identities_user_ids); mUserIdsAddedList = (ListView) view.findViewById(R.id.edit_identities_user_ids_added); mAddUserId = view.findViewById(R.id.edit_identities_add_user_id); // If this is a debug build, don't upload by default if (Constants.DEBUG) { mUploadKeyCheckbox.setChecked(false); } return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ((EditIdentitiesActivity) getActivity()).setFullScreenDialogDoneClose( R.string.btn_save, new OnClickListener() { @Override public void onClick(View v) { editKey(); } }, new OnClickListener() { @Override public void onClick(View v) { getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } }); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Either a key Uri is required!"); getActivity().finish(); return; } initView(); loadData(dataUri); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mEditOpHelper != null) { mEditOpHelper.handleActivityResult(requestCode, resultCode, data); } if (mUploadOpHelper != null) { mUploadOpHelper.handleActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } private void loadData(Uri dataUri) { mDataUri = dataUri; Log.i(Constants.TAG, "mDataUri: " + mDataUri); // load the secret key ring. we do verify here that the passphrase is correct, so cached won't do try { Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); CachedPublicKeyRing keyRing = new ProviderHelper(getActivity()).getCachedPublicKeyRing(secretUri); long masterKeyId = keyRing.getMasterKeyId(); // check if this is a master secret key we can work with switch (keyRing.getSecretKeyType(masterKeyId)) { case GNU_DUMMY: finishWithError(LogType.MSG_EK_ERROR_DUMMY); return; } mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint()); mPrimaryUserId = keyRing.getPrimaryUserIdWithFallback(); } catch (PgpKeyNotFoundException | NotFoundException e) { finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND); return; } // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditIdentitiesFragment.this); mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); mUserIdsAdapter.setEditMode(mSaveKeyringParcel); mUserIdsList.setAdapter(mUserIdsAdapter); // TODO: SaveParcel from savedInstance?! mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, false); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); } private void initView() { mAddUserId.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { addUserId(); } }); mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { editUserId(position); } }); } public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_USER_IDS: { Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); } default: return null; } } public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { case LOADER_ID_USER_IDS: { mUserIdsAdapter.swapCursor(data); break; } } } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { switch (loader.getId()) { case LOADER_ID_USER_IDS: { mUserIdsAdapter.swapCursor(null); break; } } } private void editUserId(final int position) { final String userId = mUserIdsAdapter.getUserId(position); final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); final boolean isRevokedPending = mUserIdsAdapter.getIsRevokedPending(position); Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID: // toggle if (mSaveKeyringParcel.mChangePrimaryUserId != null && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { mSaveKeyringParcel.mChangePrimaryUserId = null; } else { mSaveKeyringParcel.mChangePrimaryUserId = userId; } break; case EditUserIdDialogFragment.MESSAGE_REVOKE: // toggle if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) { mSaveKeyringParcel.mRevokeUserIds.remove(userId); } else { mSaveKeyringParcel.mRevokeUserIds.add(userId); // not possible to revoke and change to primary user id if (mSaveKeyringParcel.mChangePrimaryUserId != null && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { mSaveKeyringParcel.mChangePrimaryUserId = null; } } break; } getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad(); } }; // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditUserIdDialogFragment dialogFragment = EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); } }); } private void addUserId() { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); // add new user id mUserIdsAddedAdapter.add(data .getString(AddUserIdDialogFragment.MESSAGE_DATA_USER_ID)); } } }; // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); // pre-fill out primary name String predefinedName = KeyRing.splitUserId(mPrimaryUserId).name; AddUserIdDialogFragment addUserIdDialog = AddUserIdDialogFragment.newInstance(messenger, predefinedName, false); addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); } private void editKey() { EditIdentitiesActivity activity = (EditIdentitiesActivity) getActivity(); if (activity == null) { // this is a ui-triggered action, nvm if it fails while detached! return; } CryptoOperationHelper.Callback editKeyCallback = new CryptoOperationHelper.Callback() { @Override public SaveKeyringParcel createOperationInput() { return mSaveKeyringParcel; } @Override public void onCryptoOperationSuccess(EditKeyResult result) { if (mUploadKeyCheckbox.isChecked()) { // result will be displayed after upload uploadKey(result); return; } finishWithResult(result); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(EditKeyResult result) { displayResult(result); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mEditOpHelper = new CryptoOperationHelper<>(1, this, editKeyCallback, R.string.progress_building_key); mEditOpHelper.cryptoOperation(); } private void uploadKey(final EditKeyResult editKeyResult) { Activity activity = getActivity(); // if the activity is gone at this point, there is nothing we can do! if (activity == null) { return; } if (editKeyResult.mMasterKeyId == null) { throw new AssertionError("A successful edit key result must include a master key id!"); } final long masterKeyId = editKeyResult.mMasterKeyId; // upload to favorite keyserver final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver(); CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public UploadKeyringParcel createOperationInput() { return new UploadKeyringParcel(keyserver, masterKeyId); } @Override public void onCryptoOperationSuccess(UploadResult result) { handleResult(result); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(UploadResult result) { displayResult(result); } public void handleResult(UploadResult result) { editKeyResult.getLog().add(result, 0); finishWithResult(editKeyResult); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mUploadOpHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } private void displayResult(OperationResult result) { Activity activity = getActivity(); if (activity == null) { return; } result.createNotify(activity).show(); } void finishWithError(LogType reason) { SingletonResult errorResult = new SingletonResult(SingletonResult.RESULT_ERROR, reason); finishWithResult(errorResult); } public void finishWithResult(OperationResult result) { Activity activity = getActivity(); if (activity == null) { return; } Intent data = new Intent(); data.putExtra(OperationResult.EXTRA_RESULT, result); activity.setResult(Activity.RESULT_OK, data); activity.finish(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2986 Content-Disposition: inline; filename="EditKeyActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b607ba9f44de97ae5035cdfd20f0b0cf46b83509" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.net.Uri; import android.os.Bundle; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Log; public class EditKeyActivity extends BaseActivity { public static final String EXTRA_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; private EditKeyFragment mEditKeyFragment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Uri dataUri = getIntent().getData(); SaveKeyringParcel saveKeyringParcel = getIntent().getParcelableExtra(EXTRA_SAVE_KEYRING_PARCEL); if (dataUri == null && saveKeyringParcel == null) { Log.e(Constants.TAG, "Either a key Uri or EXTRA_SAVE_KEYRING_PARCEL is required!"); finish(); return; } loadFragment(savedInstanceState, dataUri, saveKeyringParcel); } @Override protected void initLayout() { setContentView(R.layout.edit_key_activity); } private void loadFragment(Bundle savedInstanceState, Uri dataUri, SaveKeyringParcel saveKeyringParcel) { // 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. if (savedInstanceState != null) { return; } // Create an instance of the fragment if (dataUri != null) { mEditKeyFragment = EditKeyFragment.newInstance(dataUri); } else { mEditKeyFragment = EditKeyFragment.newInstance(saveKeyringParcel); } // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() .replace(R.id.edit_key_fragment_container, mEditKeyFragment) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 26701 Content-Disposition: inline; filename="EditKeyFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "80fea7b23089c096b93bf7b9de9e47faa06cbdb3" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import java.util.Date; public class EditKeyFragment extends QueueingCryptoOperationFragment implements LoaderManager.LoaderCallbacks { public static final String ARG_DATA_URI = "uri"; public static final String ARG_SAVE_KEYRING_PARCEL = "save_keyring_parcel"; private ListView mUserIdsList; private ListView mSubkeysList; private ListView mUserIdsAddedList; private ListView mSubkeysAddedList; private View mChangePassphrase; private View mAddUserId; private View mAddSubkey; private static final int LOADER_ID_USER_IDS = 0; private static final int LOADER_ID_SUBKEYS = 1; // cursor adapter private UserIdsAdapter mUserIdsAdapter; private SubkeysAdapter mSubkeysAdapter; // array adapter private UserIdsAddedAdapter mUserIdsAddedAdapter; private SubkeysAddedAdapter mSubkeysAddedAdapter; private Uri mDataUri; private SaveKeyringParcel mSaveKeyringParcel; private String mPrimaryUserId; /** * Creates new instance of this fragment */ public static EditKeyFragment newInstance(Uri dataUri) { EditKeyFragment frag = new EditKeyFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); frag.setArguments(args); return frag; } public static EditKeyFragment newInstance(SaveKeyringParcel saveKeyringParcel) { EditKeyFragment frag = new EditKeyFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_SAVE_KEYRING_PARCEL, saveKeyringParcel); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.edit_key_fragment, superContainer, false); mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids); mSubkeysList = (ListView) view.findViewById(R.id.edit_key_keys); mUserIdsAddedList = (ListView) view.findViewById(R.id.edit_key_user_ids_added); mSubkeysAddedList = (ListView) view.findViewById(R.id.edit_key_subkeys_added); mChangePassphrase = view.findViewById(R.id.edit_key_action_change_passphrase); mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id); mAddSubkey = view.findViewById(R.id.edit_key_action_add_key); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ((EditKeyActivity) getActivity()).setFullScreenDialogDoneClose( R.string.btn_save, new OnClickListener() { @Override public void onClick(View v) { // if we are working on an Uri, save directly if (mDataUri == null) { returnKeyringParcel(); } else { cryptoOperation(new CryptoInputParcel(new Date())); } } }, new OnClickListener() { @Override public void onClick(View v) { getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } }); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); SaveKeyringParcel saveKeyringParcel = getArguments().getParcelable(ARG_SAVE_KEYRING_PARCEL); if (dataUri == null && saveKeyringParcel == null) { Log.e(Constants.TAG, "Either a key Uri or ARG_SAVE_KEYRING_PARCEL is required!"); getActivity().finish(); return; } initView(); if (dataUri != null) { loadData(dataUri); } else { loadSaveKeyringParcel(saveKeyringParcel); } } private void loadSaveKeyringParcel(SaveKeyringParcel saveKeyringParcel) { mSaveKeyringParcel = saveKeyringParcel; mPrimaryUserId = saveKeyringParcel.mChangePrimaryUserId; mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, true); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, true); mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); } private void loadData(Uri dataUri) { mDataUri = dataUri; Log.i(Constants.TAG, "mDataUri: " + mDataUri); // load the secret key ring. we do verify here that the passphrase is correct, so cached won't do try { Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); CachedPublicKeyRing keyRing = new ProviderHelper(getActivity()).getCachedPublicKeyRing(secretUri); long masterKeyId = keyRing.getMasterKeyId(); // check if this is a master secret key we can work with switch (keyRing.getSecretKeyType(masterKeyId)) { case GNU_DUMMY: finishWithError(LogType.MSG_EK_ERROR_DUMMY); return; } mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint()); mPrimaryUserId = keyRing.getPrimaryUserIdWithFallback(); } catch (PgpKeyNotFoundException | NotFoundException e) { finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND); return; } // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this); getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this); mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); mUserIdsAdapter.setEditMode(mSaveKeyringParcel); mUserIdsList.setAdapter(mUserIdsAdapter); // TODO: SaveParcel from savedInstance?! mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, false); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); mSubkeysAdapter.setEditMode(mSaveKeyringParcel); mSubkeysList.setAdapter(mSubkeysAdapter); mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, false); mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); } private void initView() { mChangePassphrase.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { changePassphrase(); } }); mAddUserId.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addUserId(); } }); mAddSubkey.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { addSubkey(); } }); mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { editSubkey(position); } }); mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { editUserId(position); } }); } public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_USER_IDS: { Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); } case LOADER_ID_SUBKEYS: { Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri); return new CursorLoader(getActivity(), baseUri, SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); } default: return null; } } public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { case LOADER_ID_USER_IDS: mUserIdsAdapter.swapCursor(data); break; case LOADER_ID_SUBKEYS: mSubkeysAdapter.swapCursor(data); break; } } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { switch (loader.getId()) { case LOADER_ID_USER_IDS: mUserIdsAdapter.swapCursor(null); break; case LOADER_ID_SUBKEYS: mSubkeysAdapter.swapCursor(null); break; } } private void changePassphrase() { // Intent passIntent = new Intent(getActivity(), PassphraseWizardActivity.class); // passIntent.setAction(PassphraseWizardActivity.CREATE_METHOD); // startActivityForResult(passIntent, 12); // Message is received after passphrase is cached Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); // cache new returned passphrase! mSaveKeyringParcel.setNewUnlock(new ChangeUnlockParcel( (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE))); } } }; // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance( messenger, R.string.title_change_passphrase); setPassphraseDialog.show(getActivity().getSupportFragmentManager(), "setPassphraseDialog"); } private void editUserId(final int position) { final String userId = mUserIdsAdapter.getUserId(position); final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); final boolean isRevokedPending = mUserIdsAdapter.getIsRevokedPending(position); Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID: // toggle if (mSaveKeyringParcel.mChangePrimaryUserId != null && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { mSaveKeyringParcel.mChangePrimaryUserId = null; } else { mSaveKeyringParcel.mChangePrimaryUserId = userId; } break; case EditUserIdDialogFragment.MESSAGE_REVOKE: // toggle if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) { mSaveKeyringParcel.mRevokeUserIds.remove(userId); } else { mSaveKeyringParcel.mRevokeUserIds.add(userId); // not possible to revoke and change to primary user id if (mSaveKeyringParcel.mChangePrimaryUserId != null && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { mSaveKeyringParcel.mChangePrimaryUserId = null; } } break; } getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad(); } }; // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditUserIdDialogFragment dialogFragment = EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); } }); } private void editSubkey(final int position) { final long keyId = mSubkeysAdapter.getKeyId(position); Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY: editSubkeyExpiry(position); break; case EditSubkeyDialogFragment.MESSAGE_REVOKE: // toggle if (mSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) { mSaveKeyringParcel.mRevokeSubKeys.remove(keyId); } else { mSaveKeyringParcel.mRevokeSubKeys.add(keyId); } break; case EditSubkeyDialogFragment.MESSAGE_STRIP: { SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); if (secretKeyType == SecretKeyType.GNU_DUMMY) { // Key is already stripped; this is a no-op. break; } SubkeyChange change = mSaveKeyringParcel.getSubkeyChange(keyId); if (change == null) { mSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false)); break; } // toggle change.mDummyStrip = !change.mDummyStrip; if (change.mDummyStrip && change.mMoveKeyToSecurityToken) { // User had chosen to divert key, but now wants to strip it instead. change.mMoveKeyToSecurityToken = false; } break; } case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: { SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || secretKeyType == SecretKeyType.GNU_DUMMY) { Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR) .show(); break; } int algorithm = mSubkeysAdapter.getAlgorithm(position); if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) { Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR) .show(); break; } if (mSubkeysAdapter.getKeySize(position) != 2048) { Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR) .show(); break; } SubkeyChange change; change = mSaveKeyringParcel.getSubkeyChange(keyId); if (change == null) { mSaveKeyringParcel.mChangeSubKeys.add( new SubkeyChange(keyId, false, true) ); break; } // toggle change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken; if (change.mMoveKeyToSecurityToken && change.mDummyStrip) { // User had chosen to strip key, but now wants to divert it. change.mDummyStrip = false; } break; } } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); } }; // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditSubkeyDialogFragment dialogFragment = EditSubkeyDialogFragment.newInstance(messenger); dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyDialog"); } }); } private void editSubkeyExpiry(final int position) { final long keyId = mSubkeysAdapter.getKeyId(position); final Long creationDate = mSubkeysAdapter.getCreationDate(position); final Long expiryDate = mSubkeysAdapter.getExpiryDate(position); Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY: mSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = (Long) message.getData().getSerializable( EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY); break; } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); } }; // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditSubkeyExpiryDialogFragment dialogFragment = EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate); dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog"); } }); } private void addUserId() { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); // add new user id mUserIdsAddedAdapter.add(data .getString(AddUserIdDialogFragment.MESSAGE_DATA_USER_ID)); } } }; // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); // pre-fill out primary name String predefinedName = KeyRing.splitUserId(mPrimaryUserId).name; AddUserIdDialogFragment addUserIdDialog = AddUserIdDialogFragment.newInstance(messenger, predefinedName, true); addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); } private void addSubkey() { // new subkey will never be a masterkey, as masterkey cannot be removed AddSubkeyDialogFragment addSubkeyDialogFragment = AddSubkeyDialogFragment.newInstance(false); addSubkeyDialogFragment .setOnAlgorithmSelectedListener( new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { @Override public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { mSubkeysAddedAdapter.add(newSubkey); } } ); addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog"); } protected void returnKeyringParcel() { if (mSaveKeyringParcel.mAddUserIds.size() == 0) { Notify.create(getActivity(), R.string.edit_key_error_add_identity, Notify.Style.ERROR).show(); return; } if (mSaveKeyringParcel.mAddSubKeys.size() == 0) { Notify.create(getActivity(), R.string.edit_key_error_add_subkey, Notify.Style.ERROR).show(); return; } // use first user id as primary mSaveKeyringParcel.mChangePrimaryUserId = mSaveKeyringParcel.mAddUserIds.get(0); Intent returnIntent = new Intent(); returnIntent.putExtra(EditKeyActivity.EXTRA_SAVE_KEYRING_PARCEL, mSaveKeyringParcel); getActivity().setResult(Activity.RESULT_OK, returnIntent); getActivity().finish(); } /** * Closes this activity, returning a result parcel with a single error log entry. */ void finishWithError(LogType reason) { // Prepare an intent with an EXTRA_RESULT Intent intent = new Intent(); intent.putExtra(OperationResult.EXTRA_RESULT, new SingletonResult(SingletonResult.RESULT_ERROR, reason)); // Finish with result getActivity().setResult(Activity.RESULT_OK, intent); getActivity().finish(); } @Override public SaveKeyringParcel createOperationInput() { return mSaveKeyringParcel; } @Override public void onQueuedOperationSuccess(OperationResult result) { // null-protected from Queueing*Fragment Activity activity = getActivity(); // if good -> finish, return result to showkey and display there! Intent intent = new Intent(); intent.putExtra(OperationResult.EXTRA_RESULT, result); activity.setResult(Activity.RESULT_OK, intent); activity.finish(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3852 Content-Disposition: inline; filename="EncryptActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "4361705f9a379b0d075376f680655c01fe128c22" /* * Copyright (C) 2015 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.view.Menu; import android.view.MenuItem; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class EncryptActivity extends BaseActivity { // preselect ids, for internal use public static final String EXTRA_SIGNATURE_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_ID"; public static final String EXTRA_ENCRYPTION_KEY_IDS = Constants.EXTRA_PREFIX + "EXTRA_SIGNATURE_KEY_IDS"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras == null) { extras = new Bundle(); } if (savedInstanceState == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // preselect keys given by intent long signingKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID); long[] encryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS); Fragment modeFragment = EncryptModeAsymmetricFragment.newInstance(signingKeyId, encryptionKeyIds); transaction.replace(R.id.encrypt_mode_container, modeFragment); transaction.commit(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.check_use_symmetric: { item.setChecked(!item.isChecked()); setModeFragment(item.isChecked()); return true; } default: { return super.onOptionsItemSelected(item); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.encrypt_activity, menu); Fragment frag = getSupportFragmentManager().findFragmentById(R.id.encrypt_mode_container); boolean isSymmetric = frag instanceof EncryptModeSymmetricFragment; menu.findItem(R.id.check_use_symmetric).setChecked(isSymmetric); return super.onCreateOptionsMenu(menu); } private void setModeFragment(boolean symmetric) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.encrypt_mode_container, symmetric ? EncryptModeSymmetricFragment.newInstance() : EncryptModeAsymmetricFragment.newInstance(0, null) ); // doesn't matter if the user doesn't look at the activity transaction.commitAllowingStateLoss(); } public EncryptModeFragment getModeFragment() { return (EncryptModeFragment) getSupportFragmentManager().findFragmentById(R.id.encrypt_mode_container); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6247 Content-Disposition: inline; filename="EncryptDecryptFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "89ea6165b6042af125c2f8237ee36408aa0379a8" /* * Copyright (C) 2014-2015 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.util.regex.Matcher; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker; import org.sufficientlysecure.keychain.util.FileHelper; public class EncryptDecryptFragment extends Fragment { View mClipboardIcon; private static final int REQUEST_CODE_INPUT = 0x00007003; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_decrypt_fragment, container, false); View mEncryptFile = view.findViewById(R.id.encrypt_files); View mEncryptText = view.findViewById(R.id.encrypt_text); View mDecryptFile = view.findViewById(R.id.decrypt_files); View mDecryptFromClipboard = view.findViewById(R.id.decrypt_from_clipboard); mClipboardIcon = view.findViewById(R.id.clipboard_icon); mEncryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent encrypt = new Intent(getActivity(), EncryptFilesActivity.class); startActivity(encrypt); } }); mEncryptText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent encrypt = new Intent(getActivity(), EncryptTextActivity.class); startActivity(encrypt); } }); mDecryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FileHelper.openDocument(EncryptDecryptFragment.this, null, "*/*", false, REQUEST_CODE_INPUT); } }); mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { decryptFromClipboard(); } }); return view; } private void decryptFromClipboard() { Activity activity = getActivity(); if (activity == null) { return; } final CharSequence clipboardText = ClipboardReflection.getClipboardText(activity); if (clipboardText == null || TextUtils.isEmpty(clipboardText)) { Notify.create(activity, R.string.error_clipboard_empty, Style.ERROR).show(); return; } Intent clipboardDecrypt = new Intent(getActivity(), DecryptActivity.class); clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD); startActivityForResult(clipboardDecrypt, 0); } @Override public void onResume() { super.onResume(); // get text from clipboard final CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); // if it's null, nothing to do here /o/ if (clipboardText == null) { return; } new AsyncTask() { @Override protected Boolean doInBackground(String... clipboardText) { // see if it looks like a pgp thing Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText[0]); boolean animate = matcher.matches(); // see if it looks like another pgp thing if (!animate) { matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText[0]); animate = matcher.matches(); } return animate; } @Override protected void onPostExecute(Boolean animate) { super.onPostExecute(animate); // if so, animate the clipboard icon just a bit~ if (animate) { SubtleAttentionSeeker.tada(mClipboardIcon, 1.5f).start(); } } }.execute(clipboardText.toString()); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != REQUEST_CODE_INPUT) { return; } if (resultCode == Activity.RESULT_OK && data != null) { Uri uri = data.getData(); if (uri == null) { Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR).show(); return; } Intent intent = new Intent(getActivity(), DecryptActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setData(uri); startActivity(intent); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2872 Content-Disposition: inline; filename="EncryptFilesActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "1367879842902cbc92bfbdb755c0f15a3f05ac90" /* * Copyright (C) 2012-2015 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentTransaction; import android.view.View; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import java.util.ArrayList; public class EncryptFilesActivity extends EncryptActivity { // Intents public static final String ACTION_ENCRYPT_DATA = OpenKeychainIntents.ENCRYPT_DATA; // enables ASCII Armor for file encryption when uri is given public static final String EXTRA_ASCII_ARMOR = OpenKeychainIntents.ENCRYPT_EXTRA_ASCII_ARMOR; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setFullScreenDialogClose(Activity.RESULT_OK, false); Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); ArrayList uris = new ArrayList<>(); if (intent.getData() != null) { uris.add(intent.getData()); } // When sending to OpenKeychain Encrypt via share menu if (Intent.ACTION_SEND.equals(action) && type != null) { // Files via content provider, override uri and action uris.clear(); uris.add(intent.getParcelableExtra(Intent.EXTRA_STREAM)); } if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) { uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } if (savedInstanceState == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); EncryptFilesFragment encryptFragment = EncryptFilesFragment.newInstance(uris); transaction.replace(R.id.encrypt_file_container, encryptFragment); transaction.commit(); } } @Override protected void initLayout() { setContentView(R.layout.encrypt_files_activity); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 37018 Content-Disposition: inline; filename="EncryptFilesFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "258aebadf9e87d5cce995a2c13ccf2133145b46c" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; public class EncryptFilesFragment extends CachingCryptoOperationFragment { public static final String ARG_DELETE_AFTER_ENCRYPT = "delete_after_encrypt"; public static final String ARG_ENCRYPT_FILENAMES = "encrypt_filenames"; public static final String ARG_USE_COMPRESSION = "use_compression"; public static final String ARG_USE_ASCII_ARMOR = "use_ascii_armor"; public static final String ARG_URIS = "uris"; public static final int REQUEST_CODE_INPUT = 0x00007003; private static final int REQUEST_CODE_OUTPUT = 0x00007007; private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; private boolean mUseArmor; private boolean mUseCompression; private boolean mDeleteAfterEncrypt; private boolean mEncryptFilenames; private boolean mHiddenRecipients = false; private ArrayList mPendingInputUris; private AfterEncryptAction mAfterEncryptAction; private enum AfterEncryptAction { SAVE, SHARE, COPY; } private ArrayList mOutputUris; private RecyclerView mSelectedFiles; FilesAdapter mFilesAdapter; /** * Creates new instance of this fragment */ public static EncryptFilesFragment newInstance(ArrayList uris) { EncryptFilesFragment frag = new EncryptFilesFragment(); Bundle args = new Bundle(); args.putParcelableArrayList(ARG_URIS, uris); frag.setArguments(args); return frag; } @Override public void onAttach(Activity activity) { super.onAttach(activity); if ( ! (activity instanceof EncryptActivity) ) { throw new AssertionError(activity + " must inherit from EncryptionActivity"); } } /** * Inflate the layout for this fragment */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_files_fragment, container, false); mSelectedFiles = (RecyclerView) view.findViewById(R.id.selected_files_list); mSelectedFiles.addItemDecoration(new SpacesItemDecoration( FormattingUtils.dpToPx(getActivity(), 4))); mSelectedFiles.setHasFixedSize(true); mSelectedFiles.setLayoutManager(new LinearLayoutManager(getActivity())); mSelectedFiles.setItemAnimator(new DefaultItemAnimator()); mFilesAdapter = new FilesAdapter(getActivity(), new View.OnClickListener() { @Override public void onClick(View v) { addInputUri(); } }); mSelectedFiles.setAdapter(mFilesAdapter); Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; mPendingInputUris = new ArrayList<>(); ArrayList inputUris = args.getParcelableArrayList(ARG_URIS); if (inputUris != null) { mPendingInputUris.addAll(inputUris); processPendingInputUris(); } return view; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(ARG_DELETE_AFTER_ENCRYPT, mDeleteAfterEncrypt); outState.putBoolean(ARG_USE_ASCII_ARMOR, mUseArmor); outState.putBoolean(ARG_USE_COMPRESSION, mUseCompression); outState.putBoolean(ARG_ENCRYPT_FILENAMES, mEncryptFilenames); outState.putParcelableArrayList(ARG_URIS, mFilesAdapter.getAsArrayList()); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Preferences prefs = Preferences.getPreferences(getActivity()); Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; mDeleteAfterEncrypt = args.getBoolean(ARG_DELETE_AFTER_ENCRYPT, false); if (args.containsKey(ARG_USE_ASCII_ARMOR)) { mUseArmor = args.getBoolean(ARG_USE_ASCII_ARMOR, false); } else { mUseArmor = prefs.getUseArmor(); } if (args.containsKey(ARG_USE_COMPRESSION)) { mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true); } else { mUseCompression = prefs.getFilesUseCompression(); } if (args.containsKey(ARG_ENCRYPT_FILENAMES)) { mEncryptFilenames = args.getBoolean(ARG_ENCRYPT_FILENAMES, true); } else { mEncryptFilenames = prefs.getEncryptFilenames(); } setHasOptionsMenu(true); } private void addInputUri() { FileHelper.openDocument(EncryptFilesFragment.this, mFilesAdapter.getModelCount() == 0 ? null : mFilesAdapter.getModelItem(mFilesAdapter.getModelCount() - 1).inputUri, "*/*", true, REQUEST_CODE_INPUT); } public void addInputUri(Intent data) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { mPendingInputUris.add(data.getData()); } else { if (data.getClipData() != null && data.getClipData().getItemCount() > 0) { for (int i = 0; i < data.getClipData().getItemCount(); i++) { Uri uri = data.getClipData().getItemAt(i).getUri(); if (uri != null) { mPendingInputUris.add(uri); } } } else { // fallback, try old method to get single uri mPendingInputUris.add(data.getData()); } } // process pending uris processPendingInputUris(); } private void processPendingInputUris() { Iterator it = mPendingInputUris.iterator(); while (it.hasNext()) { Uri inputUri = it.next(); if ( ! checkAndRequestReadPermission(inputUri)) { // break out, don't process other uris and don't remove this one from queue break; } try { mFilesAdapter.add(inputUri); } catch (IOException e) { String fileName = FileHelper.getFilename(getActivity(), inputUri); Notify.create(getActivity(), getActivity().getString(R.string.error_file_added_already, fileName), Notify.Style.ERROR).show(this); } // remove from pending input uris it.remove(); } mSelectedFiles.requestFocus(); } /** * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. * * This method returns true on Android < 6, or if permission is already granted. It * requests the permission and returns false otherwise. * * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html */ private boolean checkAndRequestReadPermission(final Uri uri) { if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return true; } // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html if (Build.VERSION.SDK_INT < VERSION_CODES.M) { return true; } if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { return true; } requestPermissions( new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } boolean permissionWasGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; if (permissionWasGranted) { // permission granted -> restart processing uris processPendingInputUris(); } else { Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } } @TargetApi(VERSION_CODES.KITKAT) private void showOutputFileDialog() { if (mFilesAdapter.getModelCount() != 1) { throw new IllegalStateException(); } FilesAdapter.ViewModel model = mFilesAdapter.getModelItem(0); String targetName = (mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); FileHelper.saveDocument(this, targetName, REQUEST_CODE_OUTPUT); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.encrypt_file_fragment, menu); menu.findItem(R.id.check_delete_after_encrypt).setChecked(mDeleteAfterEncrypt); menu.findItem(R.id.check_use_armor).setChecked(mUseArmor); menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression); menu.findItem(R.id.check_encrypt_filenames).setChecked(mEncryptFilenames); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.encrypt_save: { hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.SAVE; cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_share: { hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.SHARE; cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_copy: { hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.COPY; cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.check_use_armor: { toggleUseArmor(item, !item.isChecked()); break; } case R.id.check_delete_after_encrypt: { item.setChecked(!item.isChecked()); mDeleteAfterEncrypt = item.isChecked(); break; } case R.id.check_enable_compression: { toggleEnableCompression(item, !item.isChecked()); break; } case R.id.check_encrypt_filenames: { toggleEncryptFilenamesCheck(item, !item.isChecked()); break; } // case R.id.check_hidden_recipients: { // mHiddenRecipients = item.isChecked(); // notifyUpdate(); // break; // } default: { return super.onOptionsItemSelected(item); } } return true; } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); // Show save only on Android >= 4.4 (Document Provider) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { MenuItem save = menu.findItem(R.id.encrypt_save); save.setVisible(false); } } public void toggleUseArmor(MenuItem item, final boolean useArmor) { mUseArmor = useArmor; item.setChecked(useArmor); Notify.create(getActivity(), useArmor ? R.string.snack_armor_on : R.string.snack_armor_off, Notify.LENGTH_LONG, Style.OK, new ActionListener() { @Override public void onAction() { Preferences.getPreferences(getActivity()).setUseArmor(useArmor); Notify.create(getActivity(), useArmor ? R.string.snack_armor_on : R.string.snack_armor_off, Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved) .show(EncryptFilesFragment.this, false); } }, R.string.btn_save_default).show(this); } public void toggleEnableCompression(MenuItem item, final boolean compress) { mUseCompression = compress; item.setChecked(compress); Notify.create(getActivity(), compress ? R.string.snack_compression_on : R.string.snack_compression_off, Notify.LENGTH_LONG, Style.OK, new ActionListener() { @Override public void onAction() { Preferences.getPreferences(getActivity()).setFilesUseCompression(compress); Notify.create(getActivity(), compress ? R.string.snack_compression_on : R.string.snack_compression_off, Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved) .show(EncryptFilesFragment.this, false); } }, R.string.btn_save_default).show(this); } public void toggleEncryptFilenamesCheck(MenuItem item, final boolean encryptFilenames) { mEncryptFilenames = encryptFilenames; item.setChecked(encryptFilenames); Notify.create(getActivity(), encryptFilenames ? R.string.snack_encrypt_filenames_on : R.string.snack_encrypt_filenames_off, Notify.LENGTH_LONG, Style.OK, new ActionListener() { @Override public void onAction() { Preferences.getPreferences(getActivity()).setEncryptFilenames(encryptFilenames); Notify.create(getActivity(), encryptFilenames ? R.string.snack_encrypt_filenames_on : R.string.snack_encrypt_filenames_off, Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved) .show(EncryptFilesFragment.this, false); } }, R.string.btn_save_default).show(this); } @Override public void onQueuedOperationSuccess(final SignEncryptResult result) { super.onQueuedOperationSuccess(result); hideKeyboard(); // protected by Queueing*Fragment FragmentActivity activity = getActivity(); if (mDeleteAfterEncrypt) { // TODO make behavior coherent here DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment.newInstance(mFilesAdapter.getAsArrayList()); deleteFileDialog.setOnDeletedListener(new DeleteFileDialogFragment.OnDeletedListener() { @Override public void onDeleted() { if (mAfterEncryptAction == AfterEncryptAction.SHARE) { // Share encrypted message/file startActivity(Intent.createChooser(createSendIntent(), getString(R.string.title_share_file))); } else { Activity activity = getActivity(); if (activity == null) { // it's gone, there's nothing we can do here return; } // Save encrypted file result.createNotify(activity).show(); } } }); deleteFileDialog.show(activity.getSupportFragmentManager(), "deleteDialog"); } else { switch (mAfterEncryptAction) { case SHARE: // Share encrypted message/file startActivity(Intent.createChooser(createSendIntent(), getString(R.string.title_share_file))); break; case COPY: ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); if (clipMan == null) { Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); break; } ClipData clip = new ClipData(getString(R.string.label_clip_title), // make available as application/pgp-encrypted new String[] { "text/plain" }, new ClipData.Item(mOutputUris.get(0)) ); clipMan.setPrimaryClip(clip); result.createNotify(activity).show(); break; case SAVE: // Encrypted file was saved already, just show notification result.createNotify(activity).show(); break; } } } // prepares mOutputUris, either directly and returns false, or indirectly // which returns true and will call cryptoOperation after mOutputUris has // been set at a later point. private boolean prepareOutputStreams() { switch (mAfterEncryptAction) { default: case SHARE: mOutputUris = new ArrayList<>(); int filenameCounter = 1; for (FilesAdapter.ViewModel model : mFilesAdapter.mDataset) { String targetName = (mEncryptFilenames ? String.valueOf(filenameCounter) : FileHelper.getFilename(getActivity(), model.inputUri)) + (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN); mOutputUris.add(TemporaryFileProvider.createFile(getActivity(), targetName)); filenameCounter++; } return false; case SAVE: if (mFilesAdapter.getModelCount() > 1) { Notify.create(getActivity(), R.string.error_multi_files, Notify.Style.ERROR).show(this); return true; } showOutputFileDialog(); return true; case COPY: // nothing to do here, but make sure if (mFilesAdapter.getModelCount() > 1) { Notify.create(getActivity(), R.string.error_multi_clipboard, Notify.Style.ERROR).show(this); return true; } mOutputUris = new ArrayList<>(); String targetName = (mEncryptFilenames ? String.valueOf(1) : FileHelper.getFilename(getActivity(), mFilesAdapter.getModelItem(0).inputUri)) + Constants.FILE_EXTENSION_ASC; mOutputUris.add(TemporaryFileProvider.createFile(getActivity(), targetName, "text/plain")); return false; } } public SignEncryptParcel createOperationInput() { SignEncryptParcel actionsParcel = getCachedActionsParcel(); // we have three cases here: nothing cached, cached except output, fully cached if (actionsParcel == null) { // clear output uris for now, they will be created by prepareOutputStreams later mOutputUris = null; actionsParcel = createIncompleteCryptoInput(); // this is null if invalid, just return in that case if (actionsParcel == null) { return null; } cacheActionsParcel(actionsParcel); } // if it's incomplete, prepare output streams if (actionsParcel.isIncomplete()) { // if this is still null, prepare output streams again if (mOutputUris == null) { // this may interrupt the flow, and call us again from onActivityResult if (prepareOutputStreams()) { return null; } } actionsParcel.addOutputUris(mOutputUris); cacheActionsParcel(actionsParcel); } return actionsParcel; } protected SignEncryptParcel createIncompleteCryptoInput() { if (mFilesAdapter.getModelCount() == 0) { Notify.create(getActivity(), R.string.error_no_file_selected, Notify.Style.ERROR).show(this); return null; } // fill values for this action PgpSignEncryptData data = new PgpSignEncryptData(); if (mUseCompression) { data.setCompressionAlgorithm( PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT); } else { data.setCompressionAlgorithm( PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED); } data.setHiddenRecipients(mHiddenRecipients); data.setEnableAsciiArmorOutput(mAfterEncryptAction == AfterEncryptAction.COPY || mUseArmor); data.setSymmetricEncryptionAlgorithm( PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); data.setSignatureHashAlgorithm( PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); EncryptActivity encryptActivity = (EncryptActivity) getActivity(); EncryptModeFragment modeFragment = encryptActivity.getModeFragment(); if (modeFragment.isAsymmetric()) { long[] encryptionKeyIds = modeFragment.getAsymmetricEncryptionKeyIds(); long signingKeyId = modeFragment.getAsymmetricSigningKeyId(); boolean gotEncryptionKeys = (encryptionKeyIds != null && encryptionKeyIds.length > 0); if (!gotEncryptionKeys && signingKeyId != 0) { Notify.create(getActivity(), R.string.error_detached_signature, Notify.Style.ERROR).show(this); return null; } if (!gotEncryptionKeys) { Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR).show(this); return null; } data.setEncryptionMasterKeyIds(encryptionKeyIds); data.setSignatureMasterKeyId(signingKeyId); } else { Passphrase passphrase = modeFragment.getSymmetricPassphrase(); if (passphrase == null) { Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR) .show(this); return null; } if (passphrase.isEmpty()) { Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR) .show(this); return null; } data.setSymmetricPassphrase(passphrase); } SignEncryptParcel parcel = new SignEncryptParcel(data); parcel.addInputUris(mFilesAdapter.getAsArrayList()); return parcel; } private Intent createSendIntent() { Intent sendIntent; // file if (mOutputUris.size() == 1) { sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris.get(0)); } else { sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); sendIntent.putExtra(Intent.EXTRA_STREAM, mOutputUris); } sendIntent.setType(Constants.MIME_TYPE_ENCRYPTED_ALTERNATE); EncryptActivity modeInterface = (EncryptActivity) getActivity(); EncryptModeFragment modeFragment = modeInterface.getModeFragment(); if (!modeFragment.isAsymmetric()) { return sendIntent; } String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds(); if (encryptionUserIds == null) { return sendIntent; } Set users = new HashSet<>(); for (String user : encryptionUserIds) { OpenPgpUtils.UserId userId = KeyRing.splitUserId(user); if (userId.email != null) { users.add(userId.email); } } // pass trough email addresses as extra for email applications sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); return sendIntent; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_INPUT: { if (resultCode == Activity.RESULT_OK && data != null) { addInputUri(data); } return; } case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { mOutputUris = new ArrayList<>(1); mOutputUris.add(data.getData()); // make sure this is correct at this point mAfterEncryptAction = AfterEncryptAction.SAVE; cryptoOperation(new CryptoInputParcel(new Date())); } else if (resultCode == Activity.RESULT_CANCELED) { onCryptoOperationCancelled(); } return; } default: { super.onActivityResult(requestCode, resultCode, data); break; } } } public static class FilesAdapter extends RecyclerView.Adapter { private Activity mActivity; private List mDataset; private View.OnClickListener mFooterOnClickListener; private static final int TYPE_FOOTER = 0; private static final int TYPE_ITEM = 1; public static class ViewModel { Uri inputUri; Bitmap thumbnail; String filename; long fileSize; ViewModel(Context context, Uri inputUri) { this.inputUri = inputUri; int px = FormattingUtils.dpToPx(context, 48); this.thumbnail = FileHelper.getThumbnail(context, inputUri, new Point(px, px)); this.filename = FileHelper.getFilename(context, inputUri); this.fileSize = FileHelper.getFileSize(context, inputUri); } /** * Depends on inputUri only */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ViewModel viewModel = (ViewModel) o; return !(inputUri != null ? !inputUri.equals(viewModel.inputUri) : viewModel.inputUri != null); } /** * Depends on inputUri only */ @Override public int hashCode() { return inputUri != null ? inputUri.hashCode() : 0; } @Override public String toString() { return inputUri.toString(); } } // Provide a reference to the views for each data item // Complex data items may need more than one view per item, and // you provide access to all the views for a data item in a view holder class ViewHolder extends RecyclerView.ViewHolder { public TextView filename; public TextView fileSize; public View removeButton; public ImageView thumbnail; public ViewHolder(View itemView) { super(itemView); filename = (TextView) itemView.findViewById(R.id.filename); fileSize = (TextView) itemView.findViewById(R.id.filesize); removeButton = itemView.findViewById(R.id.action_remove_file_from_list); thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); } } class FooterHolder extends RecyclerView.ViewHolder { public Button mAddButton; public FooterHolder(View itemView) { super(itemView); mAddButton = (Button) itemView.findViewById(R.id.file_list_entry_add); } } // Provide a suitable constructor (depends on the kind of dataset) public FilesAdapter(Activity activity, View.OnClickListener onFooterClickListener) { mActivity = activity; mDataset = new ArrayList<>(); mFooterOnClickListener = onFooterClickListener; } // Create new views (invoked by the layout manager) @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_FOOTER) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.file_list_entry_add, parent, false); return new FooterHolder(v); } else { //inflate your layout and pass it to view holder View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.file_list_entry, parent, false); return new ViewHolder(v); } } // Replace the contents of a view (invoked by the layout manager) @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { if (holder instanceof FooterHolder) { FooterHolder thisHolder = (FooterHolder) holder; thisHolder.mAddButton.setOnClickListener(mFooterOnClickListener); } else if (holder instanceof ViewHolder) { ViewHolder thisHolder = (ViewHolder) holder; // - get element from your dataset at this position // - replace the contents of the view with that element final ViewModel model = mDataset.get(position); thisHolder.filename.setText(model.filename); if (model.fileSize == -1) { thisHolder.fileSize.setText(""); } else { thisHolder.fileSize.setText(FileHelper.readableFileSize(model.fileSize)); } thisHolder.removeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { remove(model); } }); if (model.thumbnail != null) { thisHolder.thumbnail.setImageBitmap(model.thumbnail); } else { thisHolder.thumbnail.setImageResource(R.drawable.ic_doc_generic_am); } } } // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { // one extra for the footer! return mDataset.size() +1; } @Override public int getItemViewType(int position) { if (isPositionFooter(position)) { return TYPE_FOOTER; } else { return TYPE_ITEM; } } private boolean isPositionFooter(int position) { return position == mDataset.size(); } public void add(Uri inputUri) throws IOException { ViewModel newModel = new ViewModel(mActivity, inputUri); if (mDataset.contains(newModel)) { throw new IOException("Already added!"); } mDataset.add(newModel); notifyItemInserted(mDataset.size() - 1); } public void addAll(ArrayList inputUris) { if (inputUris != null) { int startIndex = mDataset.size(); for (Uri inputUri : inputUris) { ViewModel newModel = new ViewModel(mActivity, inputUri); if (mDataset.contains(newModel)) { Log.e(Constants.TAG, "Skipped duplicate " + inputUri); } else { mDataset.add(newModel); } } notifyItemRangeInserted(startIndex, mDataset.size() - startIndex); } } public int getModelCount() { return mDataset.size(); } public ViewModel getModelItem(int position) { return mDataset.get(position); } public void remove(ViewModel model) { int position = mDataset.indexOf(model); mDataset.remove(position); notifyItemRemoved(position); } public ArrayList getAsArrayList() { ArrayList uris = new ArrayList<>(); for (ViewModel model : mDataset) { uris.add(model.inputUri); } return uris; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7908 Content-Disposition: inline; filename="EncryptModeAsymmetricFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b2b85ec14b66bad59ba9e26a5a6e27ba8355ac7c" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ViewAnimator; import com.tokenautocomplete.TokenCompleteTextView; import com.tokenautocomplete.TokenCompleteTextView.TokenListener; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem; import org.sufficientlysecure.keychain.ui.widget.EncryptKeyCompletionView; import org.sufficientlysecure.keychain.ui.widget.KeySpinner; import org.sufficientlysecure.keychain.ui.widget.KeySpinner.OnKeyChangedListener; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class EncryptModeAsymmetricFragment extends EncryptModeFragment { ProviderHelper mProviderHelper; private KeySpinner mSignKeySpinner; private EncryptKeyCompletionView mEncryptKeyView; public static final String ARG_SINGATURE_KEY_ID = "signature_key_id"; public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids"; /** * Creates new instance of this fragment */ public static EncryptModeAsymmetricFragment newInstance(long signatureKey, long[] encryptionKeyIds) { EncryptModeAsymmetricFragment frag = new EncryptModeAsymmetricFragment(); Bundle args = new Bundle(); args.putLong(ARG_SINGATURE_KEY_ID, signatureKey); args.putLongArray(ARG_ENCRYPTION_KEY_IDS, encryptionKeyIds); frag.setArguments(args); return frag; } /** * Inflate the layout for this fragment */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false); mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign); mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list); mEncryptKeyView.setThreshold(1); // Start working from first character final ViewAnimator vSignatureIcon = (ViewAnimator) view.findViewById(R.id.result_signature_icon); mSignKeySpinner.setOnKeyChangedListener(new OnKeyChangedListener() { @Override public void onKeyChanged(long masterKeyId) { int child = masterKeyId != Constants.key.none ? 1 : 0; if (vSignatureIcon.getDisplayedChild() != child) { vSignatureIcon.setDisplayedChild(child); } } }); final ViewAnimator vEncryptionIcon = (ViewAnimator) view.findViewById(R.id.result_encryption_icon); mEncryptKeyView.setTokenListener(new TokenListener() { @Override public void onTokenAdded(KeyItem o) { if (vEncryptionIcon.getDisplayedChild() != 1) { vEncryptionIcon.setDisplayedChild(1); } } @Override public void onTokenRemoved(KeyItem o) { int child = mEncryptKeyView.getObjects().isEmpty() ? 0 : 1; if (vEncryptionIcon.getDisplayedChild() != child) { vEncryptionIcon.setDisplayedChild(child); } } }); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mProviderHelper = new ProviderHelper(getActivity()); // preselect keys given, from state or arguments if (savedInstanceState == null) { Long signatureKeyId = getArguments().getLong(ARG_SINGATURE_KEY_ID); if (signatureKeyId == Constants.key.none) { signatureKeyId = null; } long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS); preselectKeys(signatureKeyId, encryptionKeyIds); } } /** * If an Intent gives a signatureMasterKeyId and/or encryptionMasterKeyIds, preselect those! */ private void preselectKeys(Long signatureKeyId, long[] encryptionKeyIds) { if (signatureKeyId != null) { try { CachedPublicKeyRing keyring = mProviderHelper.getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingUri(signatureKeyId)); if (keyring.hasAnySecret()) { mSignKeySpinner.setPreSelectedKeyId(signatureKeyId); } } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } } if (encryptionKeyIds != null) { for (long preselectedId : encryptionKeyIds) { try { CanonicalizedPublicKeyRing ring = mProviderHelper.getCanonicalizedPublicKeyRing(preselectedId); mEncryptKeyView.addObject(new KeyItem(ring)); } catch (NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } } // This is to work-around a rendering bug in TokenCompleteTextView mEncryptKeyView.requestFocus(); } } @Override public boolean isAsymmetric() { return true; } @Override public long getAsymmetricSigningKeyId() { return mSignKeySpinner.getSelectedKeyId(); } @Override public long[] getAsymmetricEncryptionKeyIds() { List keyIds = new ArrayList<>(); for (Object object : mEncryptKeyView.getObjects()) { if (object instanceof KeyItem) { keyIds.add(((KeyItem) object).mKeyId); } } long[] keyIdsArr = new long[keyIds.size()]; Iterator iterator = keyIds.iterator(); for (int i = 0; i < keyIds.size(); i++) { keyIdsArr[i] = iterator.next(); } return keyIdsArr; } @Override public String[] getAsymmetricEncryptionUserIds() { List userIds = new ArrayList<>(); for (Object object : mEncryptKeyView.getObjects()) { if (object instanceof KeyItem) { userIds.add(((KeyItem) object).mUserIdFull); } } return userIds.toArray(new String[userIds.size()]); } @Override public Passphrase getSymmetricPassphrase() { throw new UnsupportedOperationException("should never happen, this is a programming error!"); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 490 Content-Disposition: inline; filename="EncryptModeFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "0b96726546a15b084d909e8f9cbe96c467d7b7f0" package org.sufficientlysecure.keychain.ui; import android.support.v4.app.Fragment; import org.sufficientlysecure.keychain.util.Passphrase; public abstract class EncryptModeFragment extends Fragment { public abstract boolean isAsymmetric(); public abstract long getAsymmetricSigningKeyId(); public abstract long[] getAsymmetricEncryptionKeyIds(); public abstract String[] getAsymmetricEncryptionUserIds(); public abstract Passphrase getSymmetricPassphrase(); } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3045 Content-Disposition: inline; filename="EncryptModeSymmetricFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b92a73731efc3e1d9f50f0b87867c3c2e41fbb91" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Passphrase; public class EncryptModeSymmetricFragment extends EncryptModeFragment { private EditText mPassphrase; private EditText mPassphraseAgain; /** * Creates new instance of this fragment */ public static EncryptModeSymmetricFragment newInstance() { EncryptModeSymmetricFragment frag = new EncryptModeSymmetricFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_symmetric_fragment, container, false); mPassphrase = (EditText) view.findViewById(R.id.passphrase); mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain); return view; } @Override public boolean isAsymmetric() { return false; } @Override public long getAsymmetricSigningKeyId() { throw new UnsupportedOperationException("should never happen, this is a programming error!"); } @Override public long[] getAsymmetricEncryptionKeyIds() { throw new UnsupportedOperationException("should never happen, this is a programming error!"); } @Override public String[] getAsymmetricEncryptionUserIds() { throw new UnsupportedOperationException("should never happen, this is a programming error!"); } @Override public Passphrase getSymmetricPassphrase() { Passphrase p1 = null, p2 = null; try { p1 = new Passphrase(mPassphrase.getText()); p2 = new Passphrase(mPassphraseAgain.getText()); if (!p1.equals(p2)) { return null; } return new Passphrase(mPassphrase.getText()); } finally { if (p1 != null) { p1.removeFromMemory(); } if (p2 != null) { p2.removeFromMemory(); } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5680 Content-Disposition: inline; filename="EncryptTextActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "50dbc9a8b030274e97830435e72189978d1a1b3a" /* * Copyright (C) 2012-2015 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentTransaction; import android.widget.Toast; import org.apache.james.mime4j.util.MimeUtil; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; public class EncryptTextActivity extends EncryptActivity { /* Intents */ public static final String ACTION_ENCRYPT_TEXT = OpenKeychainIntents.ENCRYPT_TEXT; /* EXTRA keys for input */ public static final String EXTRA_TEXT = OpenKeychainIntents.ENCRYPT_EXTRA_TEXT; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setFullScreenDialogClose(Activity.RESULT_OK, false); Intent intent = getIntent(); String action = intent.getAction(); Bundle extras = intent.getExtras(); String type = intent.getType(); if (extras == null) { extras = new Bundle(); } String textData = extras.getString(EXTRA_TEXT); boolean returnProcessText = false; // When sending to OpenKeychain Encrypt via share menu if (Intent.ACTION_SEND.equals(action) && type != null) { Log.logDebugBundle(extras, "extras"); // When sending to OpenKeychain Encrypt via share menu if ( ! MimeUtil.isSameMimeType("text/plain", type)) { Toast.makeText(this, R.string.toast_wrong_mimetype, Toast.LENGTH_LONG).show(); finish(); return; } String sharedText; if (extras.containsKey(Intent.EXTRA_TEXT)) { sharedText = extras.getString(Intent.EXTRA_TEXT); } else if (extras.containsKey(Intent.EXTRA_STREAM)) { try { sharedText = FileHelper.readTextFromUri(this, extras.getParcelable(Intent.EXTRA_STREAM), null); } catch (IOException e) { Toast.makeText(this, R.string.error_preparing_data, Toast.LENGTH_LONG).show(); finish(); return; } } else { Toast.makeText(this, R.string.toast_no_text, Toast.LENGTH_LONG).show(); finish(); return; } if (sharedText != null) { if (sharedText.length() > Constants.TEXT_LENGTH_LIMIT) { sharedText = sharedText.substring(0, Constants.TEXT_LENGTH_LIMIT); Notify.create(this, R.string.snack_shared_text_too_long, Style.WARN).show(); } // handle like normal text encryption, override action and extras to later // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions textData = sharedText; } } // Android 6, PROCESS_TEXT Intent if (Intent.ACTION_PROCESS_TEXT.equals(action) && type != null) { String sharedText = null; if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT)) { sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT); returnProcessText = true; } else if (extras.containsKey(Intent.EXTRA_PROCESS_TEXT_READONLY)) { sharedText = extras.getString(Intent.EXTRA_PROCESS_TEXT_READONLY); } if (sharedText != null) { if (sharedText.length() > Constants.TEXT_LENGTH_LIMIT) { sharedText = sharedText.substring(0, Constants.TEXT_LENGTH_LIMIT); Notify.create(this, R.string.snack_shared_text_too_long, Style.WARN).show(); } // handle like normal text encryption, override action and extras to later // executeServiceMethod ACTION_ENCRYPT_TEXT in main actions textData = sharedText; } } if (textData == null) { textData = ""; } if (savedInstanceState == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); EncryptTextFragment encryptFragment = EncryptTextFragment.newInstance(textData, returnProcessText); transaction.replace(R.id.encrypt_text_container, encryptFragment); transaction.commit(); } } @Override protected void initLayout() { setContentView(R.layout.encrypt_text_activity); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13473 Content-Disposition: inline; filename="EncryptTextFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "e02184873d97820a0072e7a7e982106cad35135e" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants; import org.sufficientlysecure.keychain.pgp.PgpSignEncryptData; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import java.util.Date; import java.util.HashSet; import java.util.Set; public class EncryptTextFragment extends CachingCryptoOperationFragment { public static final String ARG_TEXT = "text"; public static final String ARG_USE_COMPRESSION = "use_compression"; public static final String ARG_RETURN_PROCESS_TEXT = "return_process_text"; private boolean mShareAfterEncrypt; private boolean mReturnProcessTextAfterEncrypt; private boolean mUseCompression; private boolean mHiddenRecipients = false; private String mMessage = ""; /** * Creates new instance of this fragment */ public static EncryptTextFragment newInstance(String text, boolean returnProcessTextAfterEncrypt) { EncryptTextFragment frag = new EncryptTextFragment(); Bundle args = new Bundle(); args.putString(ARG_TEXT, text); args.putBoolean(ARG_RETURN_PROCESS_TEXT, returnProcessTextAfterEncrypt); frag.setArguments(args); return frag; } @Override public void onAttach(Activity activity) { super.onAttach(activity); if ( ! (activity instanceof EncryptActivity) ) { throw new AssertionError(activity + " must inherit from EncryptionActivity"); } } /** * Inflate the layout for this fragment */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.encrypt_text_fragment, container, false); TextView textView = (TextView) view.findViewById(R.id.encrypt_text_text); textView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { mMessage = s.toString(); } }); // set initial text if (mMessage != null) { textView.setText(mMessage); } return view; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(ARG_USE_COMPRESSION, mUseCompression); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { mMessage = getArguments().getString(ARG_TEXT); mReturnProcessTextAfterEncrypt = getArguments().getBoolean(ARG_RETURN_PROCESS_TEXT, false); } Preferences prefs = Preferences.getPreferences(getActivity()); Bundle args = savedInstanceState == null ? getArguments() : savedInstanceState; mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true); if (args.containsKey(ARG_USE_COMPRESSION)) { mUseCompression = args.getBoolean(ARG_USE_COMPRESSION, true); } else { mUseCompression = prefs.getTextUseCompression(); } setHasOptionsMenu(true); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.encrypt_text_fragment, menu); menu.findItem(R.id.check_enable_compression).setChecked(mUseCompression); if (mReturnProcessTextAfterEncrypt) { menu.findItem(R.id.encrypt_paste).setVisible(true); menu.findItem(R.id.encrypt_copy).setVisible(false); menu.findItem(R.id.encrypt_share).setVisible(false); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.check_enable_compression: { toggleEnableCompression(item, !item.isChecked()); break; } // case R.id.check_hidden_recipients: { // mHiddenRecipients = item.isChecked(); // notifyUpdate(); // break; // } case R.id.encrypt_copy: { hideKeyboard(); mShareAfterEncrypt = false; cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_share: { hideKeyboard(); mShareAfterEncrypt = true; cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_paste: { hideKeyboard(); cryptoOperation(new CryptoInputParcel(new Date())); break; } default: { return super.onOptionsItemSelected(item); } } return true; } public void toggleEnableCompression(MenuItem item, final boolean compress) { mUseCompression = compress; item.setChecked(compress); Notify.create(getActivity(), compress ? R.string.snack_compression_on : R.string.snack_compression_off, Notify.LENGTH_LONG, Style.OK, new ActionListener() { @Override public void onAction() { Preferences.getPreferences(getActivity()).setTextUseCompression(compress); Notify.create(getActivity(), compress ? R.string.snack_compression_on : R.string.snack_compression_off, Notify.LENGTH_SHORT, Style.OK, null, R.string.btn_saved) .show(EncryptTextFragment.this, false); } }, R.string.btn_save_default).show(this); } public SignEncryptParcel createOperationInput() { if (mMessage == null || mMessage.isEmpty()) { Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR) .show(this); return null; } // fill values for this action PgpSignEncryptData data = new PgpSignEncryptData(); data.setCleartextSignature(true); if (mUseCompression) { data.setCompressionAlgorithm( PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT); } else { data.setCompressionAlgorithm( PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED); } data.setHiddenRecipients(mHiddenRecipients); data.setSymmetricEncryptionAlgorithm( PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); data.setSignatureHashAlgorithm( PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT); // Always use armor for messages data.setEnableAsciiArmorOutput(true); EncryptActivity modeInterface = (EncryptActivity) getActivity(); EncryptModeFragment modeFragment = modeInterface.getModeFragment(); if (modeFragment.isAsymmetric()) { long[] encryptionKeyIds = modeFragment.getAsymmetricEncryptionKeyIds(); long signingKeyId = modeFragment.getAsymmetricSigningKeyId(); boolean gotEncryptionKeys = (encryptionKeyIds != null && encryptionKeyIds.length > 0); if (!gotEncryptionKeys && signingKeyId == Constants.key.none) { Notify.create(getActivity(), R.string.error_no_encryption_or_signature_key, Notify.Style.ERROR) .show(this); return null; } data.setEncryptionMasterKeyIds(encryptionKeyIds); data.setSignatureMasterKeyId(signingKeyId); } else { Passphrase passphrase = modeFragment.getSymmetricPassphrase(); if (passphrase == null) { Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR) .show(this); return null; } if (passphrase.isEmpty()) { Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR) .show(this); return null; } data.setSymmetricPassphrase(passphrase); } SignEncryptParcel parcel = new SignEncryptParcel(data); parcel.setBytes(mMessage.getBytes()); return parcel; } private void copyToClipboard(SignEncryptResult result) { Activity activity = getActivity(); if (activity == null) { return; } ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); if (clipMan == null) { Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); return; } ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, new String(result.getResultBytes())); clipMan.setPrimaryClip(clip); result.createNotify(activity).show(); } private Intent createSendIntent(byte[] resultBytes) { Intent sendIntent; sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType(Constants.MIME_TYPE_TEXT); sendIntent.putExtra(Intent.EXTRA_TEXT, new String(resultBytes)); EncryptActivity modeInterface = (EncryptActivity) getActivity(); EncryptModeFragment modeFragment = modeInterface.getModeFragment(); if (!modeFragment.isAsymmetric()) { return sendIntent; } String[] encryptionUserIds = modeFragment.getAsymmetricEncryptionUserIds(); if (encryptionUserIds == null) { return sendIntent; } Set users = new HashSet<>(); for (String user : encryptionUserIds) { OpenPgpUtils.UserId userId = KeyRing.splitUserId(user); if (userId.email != null) { users.add(userId.email); } } // pass trough email addresses as extra for email applications sendIntent.putExtra(Intent.EXTRA_EMAIL, users.toArray(new String[users.size()])); return sendIntent; } @Override public void onQueuedOperationSuccess(SignEncryptResult result) { super.onQueuedOperationSuccess(result); hideKeyboard(); if (mShareAfterEncrypt) { // Share encrypted message/file startActivity(Intent.createChooser(createSendIntent(result.getResultBytes()), getString(R.string.title_share_message))); } else if (mReturnProcessTextAfterEncrypt) { Intent resultIntent = new Intent(); resultIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, new String(result.getResultBytes())); getActivity().setResult(Activity.RESULT_OK, resultIntent); getActivity().finish(); } else { // Copy to clipboard copyToClipboard(result); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2988 Content-Disposition: inline; filename="HelpAboutFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "7a1e167bb4fa98ea7508c61ef879f5df96afbf06" /* * Copyright (C) 2012-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import org.markdown4j.Markdown4jProcessor; import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; public class HelpAboutFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.help_about_fragment, container, false); TextView versionText = (TextView) view.findViewById(R.id.help_about_version); versionText.setText(getString(R.string.help_about_version) + " " + getVersion()); HtmlTextView aboutTextView = (HtmlTextView) view.findViewById(R.id.help_about_text); // load markdown from raw resource try { String html = new Markdown4jProcessor().process( getActivity().getResources().openRawResource(R.raw.help_about)); aboutTextView.setHtmlFromString(html, new HtmlTextView.LocalImageGetter()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } return view; } /** * Get the current package version. * * @return The current version. */ private String getVersion() { String result = ""; try { PackageManager manager = getActivity().getPackageManager(); PackageInfo info = manager.getPackageInfo(getActivity().getPackageName(), 0); result = String.format("%s (%s)", info.versionName, info.versionCode); } catch (NameNotFoundException e) { Log.w(Constants.TAG, "Unable to get application version: " + e.getMessage()); result = "Unable to get application version."; } return result; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6664 Content-Disposition: inline; filename="HelpActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "c67e974404999df1b1aeed5b9e3ffcfae5f7fbe7" /* * Copyright (C) 2012-2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.view.View; import com.astuetz.PagerSlidingTabStrip; import org.sufficientlysecure.donations.DonationsFragment; import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class HelpActivity extends BaseActivity { public static final String EXTRA_SELECTED_TAB = "selected_tab"; public static final int TAB_START = 0; public static final int TAB_CONFIRM = 1; public static final int TAB_FAQ = 2; public static final int TAB_DONATE = 3; public static final int TAB_CHANGELOG = 4; public static final int TAB_ABOUT = 5; // Google Play private static final String[] GOOGLE_PLAY_CATALOG = new String[]{"keychain.donation.1", "keychain.donation.2", "keychain.donation.3", "keychain.donation.5", "keychain.donation.10", "keychain.donation.50", "keychain.donation.100"}; private PagerTabStripAdapter mTabsAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); ViewPager viewPager = (ViewPager) findViewById(R.id.pager); PagerSlidingTabStrip slidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.sliding_tab_layout); mTabsAdapter = new PagerTabStripAdapter(this); viewPager.setAdapter(mTabsAdapter); int selectedTab = TAB_START; Intent intent = getIntent(); if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) { selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB); } Bundle startBundle = new Bundle(); startBundle.putInt(HelpMarkdownFragment.ARG_MARKDOWN_RES, R.raw.help_start); mTabsAdapter.addTab(HelpMarkdownFragment.class, startBundle, getString(R.string.help_tab_start)); Bundle certificationBundle = new Bundle(); certificationBundle.putInt(HelpMarkdownFragment.ARG_MARKDOWN_RES, R.raw.help_certification); mTabsAdapter.addTab(HelpMarkdownFragment.class, certificationBundle, getString(R.string.help_tab_wot)); Bundle faqBundle = new Bundle(); faqBundle.putInt(HelpMarkdownFragment.ARG_MARKDOWN_RES, R.raw.help_faq); mTabsAdapter.addTab(HelpMarkdownFragment.class, faqBundle, getString(R.string.help_tab_faq)); Bundle donationsBundle = new Bundle(); donationsBundle.putBoolean(DonationsFragment.ARG_DEBUG, Constants.DEBUG); if (BuildConfig.DONATIONS_GOOGLE) { donationsBundle.putBoolean(DonationsFragment.ARG_GOOGLE_ENABLED, true); donationsBundle.putString(DonationsFragment.ARG_GOOGLE_PUBKEY, BuildConfig.GOOGLE_PLAY_PUBKEY); donationsBundle.putStringArray(DonationsFragment.ARG_GOOGLE_CATALOG, GOOGLE_PLAY_CATALOG); donationsBundle.putStringArray(DonationsFragment.ARG_GOOGLE_CATALOG_VALUES, getResources().getStringArray(R.array.help_donation_google_catalog_values)); } else { donationsBundle.putBoolean(DonationsFragment.ARG_PAYPAL_ENABLED, true); donationsBundle.putString(DonationsFragment.ARG_PAYPAL_CURRENCY_CODE, BuildConfig.PAYPAL_CURRENCY_CODE); donationsBundle.putString(DonationsFragment.ARG_PAYPAL_USER, BuildConfig.PAYPAL_USER); donationsBundle.putString(DonationsFragment.ARG_PAYPAL_ITEM_NAME, getString(R.string.help_donation_paypal_item)); donationsBundle.putBoolean(DonationsFragment.ARG_BITCOIN_ENABLED, true); donationsBundle.putString(DonationsFragment.ARG_BITCOIN_ADDRESS, BuildConfig.BITCOIN_ADDRESS); } mTabsAdapter.addTab(DonationsFragment.class, donationsBundle, getString(R.string.help_tab_donations)); Bundle changelogBundle = new Bundle(); changelogBundle.putInt(HelpMarkdownFragment.ARG_MARKDOWN_RES, R.raw.help_changelog); mTabsAdapter.addTab(HelpMarkdownFragment.class, changelogBundle, getString(R.string.help_tab_changelog)); mTabsAdapter.addTab(HelpAboutFragment.class, null, getString(R.string.help_tab_about)); // NOTE: must be after adding the tabs! slidingTabLayout.setViewPager(viewPager); // switch to tab selected by extra viewPager.setCurrentItem(selectedTab); } @Override protected void initLayout() { setContentView(R.layout.help_activity); } public static void startHelpActivity(Context context, int code) { Intent intent = new Intent(context, HelpActivity.class); intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, code); context.startActivity(intent); } /** * Needed for Google Play In-app Billing. It uses startIntentSenderForResult(). The result is not propagated to * the Fragment like in startActivityForResult(). Thus we need to propagate manually to our Fragment. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Fragment fragment = mTabsAdapter.getRegisteredFragment(TAB_DONATE); if (fragment != null) { fragment.onActivityResult(requestCode, resultCode, data); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2752 Content-Disposition: inline; filename="HelpMarkdownFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "15098b8d6a1da441cd13288ac892c954adae2745" /* * Copyright (C) 2012-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ScrollView; import org.markdown4j.Markdown4jProcessor; import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; public class HelpMarkdownFragment extends Fragment { public static final String ARG_MARKDOWN_RES = "htmlFile"; /** * Create a new instance of HelpHtmlFragment, providing "htmlFile" as an argument. */ static HelpMarkdownFragment newInstance(int markdownRes) { HelpMarkdownFragment f = new HelpMarkdownFragment(); // Supply html raw file input as an argument. Bundle args = new Bundle(); args.putInt(ARG_MARKDOWN_RES, markdownRes); f.setArguments(args); return f; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { int mHtmlFile = getArguments().getInt(ARG_MARKDOWN_RES); ScrollView scroller = new ScrollView(getActivity()); HtmlTextView text = new HtmlTextView(getActivity()); // padding int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getActivity() .getResources().getDisplayMetrics()); text.setPadding(padding, padding, padding, 0); scroller.addView(text); // load markdown from raw resource try { String html = new Markdown4jProcessor().process( getActivity().getResources().openRawResource(mHtmlFile)); text.setHtmlFromString(html, new HtmlTextView.LocalImageGetter()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } return scroller; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 20053 Content-Disposition: inline; filename="ImportKeysActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "7d2d30c3537f94519c88ef38c904e6db9a6112a2" /* * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2011 Senecaso * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; public class ImportKeysActivity extends BaseActivity implements CryptoOperationHelper.Callback { public static final String ACTION_IMPORT_KEY = OpenKeychainIntents.IMPORT_KEY; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER; public static final String ACTION_IMPORT_KEY_FROM_FACEBOOK = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_FACEBOOK"; public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN_RESULT"; public static final String ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN = Constants.INTENT_PREFIX + "IMPORT_KEY_FROM_FILE_AND_RETURN"; // 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 public static final String EXTRA_KEY_BYTES = OpenKeychainIntents.IMPORT_EXTRA_KEY_EXTRA_KEY_BYTES; // only used by ACTION_IMPORT_KEY_FROM_KEYSERVER public static final String EXTRA_QUERY = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER_EXTRA_QUERY; public static final String EXTRA_KEY_ID = Constants.EXTRA_PREFIX + "EXTRA_KEY_ID"; public static final String EXTRA_FINGERPRINT = OpenKeychainIntents.IMPORT_KEY_FROM_KEYSERVER_EXTRA_FINGERPRINT; public static final String TAG_FRAG_LIST = "frag_list"; public static final String TAG_FRAG_TOP = "frag_top"; // for CryptoOperationHelper.Callback private String mKeyserver; private ArrayList mKeyList; private CryptoOperationHelper mOperationHelper; private boolean mFreshIntent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // we're started with a new Intent that needs to be handled by onResumeFragments mFreshIntent = true; setFullScreenDialogClose(Activity.RESULT_CANCELED, true); findViewById(R.id.import_import).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { importSelectedKeys(); } }); } @Override protected void initLayout() { setContentView(R.layout.import_keys_activity); } @Override protected void onResumeFragments() { super.onResumeFragments(); if (mFreshIntent) { handleActions(getIntent()); // we've consumed this Intent, we don't want to repeat the action it represents // every time the activity is resumed mFreshIntent = false; } } protected void handleActions(@NonNull Intent intent) { String action = intent.getAction(); Bundle extras = intent.getExtras(); Uri dataUri = intent.getData(); String scheme = intent.getScheme(); if (extras == null) { extras = new Bundle(); } if (Intent.ACTION_VIEW.equals(action)) { if (FacebookKeyserver.isFacebookHost(dataUri)) { action = ACTION_IMPORT_KEY_FROM_FACEBOOK; } else if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { action = ACTION_SEARCH_KEYSERVER_FROM_URL; } else if ("openpgp4fpr".equalsIgnoreCase(scheme)) { action = ACTION_IMPORT_KEY_FROM_KEYSERVER; extras.putString(EXTRA_FINGERPRINT, dataUri.getSchemeSpecificPart()); } else { // Android's Action when opening file associated to Keychain (see AndroidManifest.xml) // delegate action to ACTION_IMPORT_KEY action = ACTION_IMPORT_KEY; } } if (action == null) { // -> switch to default below action = ""; } switch (action) { case ACTION_IMPORT_KEY: { if (dataUri != null) { // action: directly load data startListFragment(null, dataUri, null, null); } else if (extras.containsKey(EXTRA_KEY_BYTES)) { byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES); // action: directly load data startListFragment(importData, null, null, null); } else { startTopFileFragment(); startListFragment(null, null, null, null); } break; } case ACTION_IMPORT_KEY_FROM_KEYSERVER: case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT: { if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) { /* simple search based on query or key id */ String query = null; if (extras.containsKey(EXTRA_QUERY)) { query = extras.getString(EXTRA_QUERY); } else if (extras.containsKey(EXTRA_KEY_ID)) { long keyId = extras.getLong(EXTRA_KEY_ID, 0); if (keyId != 0) { query = KeyFormattingUtils.convertKeyIdToHex(keyId); } } if (query != null && query.length() > 0) { // display keyserver fragment with query startTopCloudFragment(query, false, null); // action: search immediately startListFragment(null, null, query, null); } else { Log.e(Constants.TAG, "Query is empty!"); return; } } else if (extras.containsKey(EXTRA_FINGERPRINT)) { /* * search based on fingerprint, here we can enforce a check in the end * if the right key has been downloaded */ String fingerprint = extras.getString(EXTRA_FINGERPRINT); if (isFingerprintValid(fingerprint)) { String query = "0x" + fingerprint; // display keyserver fragment with query startTopCloudFragment(query, true, null); // action: search immediately startListFragment(null, null, query, null); } } else { Log.e(Constants.TAG, "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " + "'fingerprint' extra!" ); return; } break; } case ACTION_IMPORT_KEY_FROM_FACEBOOK: { String fbUsername = FacebookKeyserver.getUsernameFromUri(dataUri); Preferences.CloudSearchPrefs cloudSearchPrefs = new Preferences.CloudSearchPrefs(false, true, true, null); // we allow our users to edit the query if they wish startTopCloudFragment(fbUsername, false, cloudSearchPrefs); // search immediately startListFragment(null, null, fbUsername, cloudSearchPrefs); break; } case ACTION_SEARCH_KEYSERVER_FROM_URL: { // need to process URL to get search query and keyserver authority String query = dataUri.getQueryParameter("search"); // 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(); } Preferences.CloudSearchPrefs cloudSearchPrefs = new Preferences.CloudSearchPrefs( true, true, true, dataUri.getAuthority()); // we allow our users to edit the query if they wish startTopCloudFragment(query, false, cloudSearchPrefs); // search immediately (if query is not null) startListFragment(null, null, query, cloudSearchPrefs); break; } case ACTION_IMPORT_KEY_FROM_FILE: case ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN: { // NOTE: this only displays the appropriate fragment, no actions are taken startTopFileFragment(); startListFragment(null, null, null, null); break; } default: { startTopCloudFragment(null, false, null); startListFragment(null, null, null, null); break; } } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // the only thing we need to take care of for restoring state is // that the top layout is shown iff it contains a fragment Fragment topFragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_TOP); boolean hasTopFragment = topFragment != null; findViewById(R.id.import_keys_top_layout).setVisibility(hasTopFragment ? View.VISIBLE : View.GONE); } /** * Shows the list of keys to be imported. * If the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately * load content. * * @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 cloudSearchPrefs search specifications to use. If null will retrieve from user's * preferences. */ private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, Preferences.CloudSearchPrefs cloudSearchPrefs) { Fragment listFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs); getSupportFragmentManager().beginTransaction() .replace(R.id.import_keys_list_container, listFragment, TAG_FRAG_LIST) .commit(); } private void startTopFileFragment() { findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); Fragment importFileFragment = ImportKeysFileFragment.newInstance(); getSupportFragmentManager().beginTransaction() .replace(R.id.import_keys_top_container, importFileFragment, TAG_FRAG_TOP) .commit(); } /** * loads the CloudFragment, which consists of the search bar, search button and settings icon * visually. * * @param query search query * @param disableQueryEdit if true, user will not be able to edit the search query * @param cloudSearchPrefs keyserver authority to use for search, if null will use keyserver * specified in user preferences */ private void startTopCloudFragment(String query, boolean disableQueryEdit, Preferences.CloudSearchPrefs cloudSearchPrefs) { findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE); Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, cloudSearchPrefs); getSupportFragmentManager().beginTransaction() .replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP) .commit(); } private boolean isFingerprintValid(String fingerprint) { if (fingerprint == null || fingerprint.length() < 40) { Notify.create(this, R.string.import_qr_code_too_short_fingerprint, Notify.Style.ERROR) .show((ViewGroup) findViewById(R.id.import_snackbar)); return false; } else { return true; } } public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) { FragmentManager fragMan = getSupportFragmentManager(); ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); keyListFragment.loadNew(loaderState); } private void importSelectedKeys() { FragmentManager fragMan = getSupportFragmentManager(); ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST); if (keyListFragment.getSelectedEntries().size() == 0) { Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR) .show((ViewGroup) findViewById(R.id.import_snackbar)); return; } mOperationHelper = new CryptoOperationHelper<>( 1, this, this, R.string.progress_importing ); ImportKeysListFragment.LoaderState ls = keyListFragment.getLoaderState(); if (ls instanceof ImportKeysListFragment.BytesLoaderState) { Log.d(Constants.TAG, "importKeys started"); // get DATA from selected key entries IteratorWithSize selectedEntries = keyListFragment.getSelectedData(); // instead of giving the entries by Intent extra, cache them into a // file to prevent Java Binder problems on heavy imports // read FileImportCache for more info. try { // We parcel this iteratively into a file - anything we can // display here, we should be able to import. ParcelableFileCache cache = new ParcelableFileCache<>(this, "key_import.pcl"); cache.writeCache(selectedEntries); mKeyList = null; mKeyserver = null; mOperationHelper.cryptoOperation(); } catch (IOException e) { Log.e(Constants.TAG, "Problem writing cache file", e); Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR) .show((ViewGroup) findViewById(R.id.import_snackbar)); } } else if (ls instanceof ImportKeysListFragment.CloudLoaderState) { ImportKeysListFragment.CloudLoaderState sls = (ImportKeysListFragment.CloudLoaderState) ls; // get selected key entries ArrayList keys = new ArrayList<>(); { // change the format into ParcelableKeyRing ArrayList entries = keyListFragment.getSelectedEntries(); for (ImportKeysListEntry entry : entries) { keys.add(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(), entry.getKeybaseName(), entry.getFbUsername())); } } mKeyList = keys; mKeyserver = sls.mCloudPrefs.keyserver; mOperationHelper.cryptoOperation(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mOperationHelper != null && mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { return; } super.onActivityResult(requestCode, resultCode, data); } /** * Defines how the result of this activity is returned. * Is overwritten in RemoteImportKeysActivity */ protected void handleResult(ImportKeyResult result) { String intentAction = getIntent().getAction(); if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction) || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) { Intent intent = new Intent(); intent.putExtra(ImportKeyResult.EXTRA_RESULT, result); setResult(RESULT_OK, intent); finish(); } else if (result.isOkNew() || result.isOkUpdated()) { // User has successfully imported a key, hide first time dialog Preferences.getPreferences(this).setFirstTime(false); // Close activities opened for importing keys and go to the list of keys Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } else { result.createNotify(ImportKeysActivity.this) .show((ViewGroup) findViewById(R.id.import_snackbar)); } } // methods from CryptoOperationHelper.Callback @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { handleResult(result); } @Override public void onCryptoOperationCancelled() { // do nothing } @Override public void onCryptoOperationError(ImportKeyResult result) { handleResult(result); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7261 Content-Disposition: inline; filename="ImportKeysCloudFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "fb0217cda45d0051712f5550e149538c7c5fc349" /* * Copyright (C) 2013-2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.preference.PreferenceActivity; import android.support.v4.app.Fragment; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import java.util.List; /** * Consists of the search bar, search button, and search settings button */ 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_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; private ImportKeysActivity mImportActivity; private AutoCompleteTextView mQueryEditText; /** * Creates new instance of this fragment * * @param query query to search for * @param disableQueryEdit if true, user cannot edit query * @param cloudSearchPrefs search parameters to use. If null will retrieve from user's * preferences. */ public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit, Preferences.CloudSearchPrefs cloudSearchPrefs) { ImportKeysCloudFragment frag = new ImportKeysCloudFragment(); Bundle args = new Bundle(); args.putString(ARG_QUERY, query); args.putBoolean(ARG_DISABLE_QUERY_EDIT, disableQueryEdit); args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); frag.setArguments(args); return frag; } /** * Inflate the layout for this fragment */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_cloud_fragment, container, false); mQueryEditText = (AutoCompleteTextView) view.findViewById(R.id.cloud_import_server_query); ContactHelper contactHelper = new ContactHelper(getActivity()); List namesAndEmails = contactHelper.getContactNames(); namesAndEmails.addAll(contactHelper.getContactMails()); mQueryEditText.setThreshold(3); mQueryEditText.setAdapter( new ArrayAdapter<> (getActivity(), android.R.layout.simple_spinner_dropdown_item, namesAndEmails ) ); View searchButton = view.findViewById(R.id.cloud_import_server_search); searchButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { search(mQueryEditText.getText().toString()); } }); mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_SEARCH) { search(mQueryEditText.getText().toString()); // Don't return true to let the keyboard close itself after pressing search return false; } return false; } }); View configButton = view.findViewById(R.id.cloud_import_server_config_button); configButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(mImportActivity, SettingsActivity.class); intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.CloudSearchPrefsFragment.class.getName()); startActivity(intent); } }); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // set displayed values if (getArguments() != null) { String query = getArguments().getString(ARG_QUERY); if (query != null) { mQueryEditText.setText(query, TextView.BufferType.EDITABLE); Log.d(Constants.TAG, "query: " + query); } else { // open keyboard mQueryEditText.requestFocus(); toggleKeyboard(true); } if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) { mQueryEditText.setEnabled(false); } } } @Override public void onAttach(Activity activity) { super.onAttach(activity); mImportActivity = (ImportKeysActivity) activity; } private void search(String query) { Preferences.CloudSearchPrefs cloudSearchPrefs = getArguments().getParcelable(ARG_CLOUD_SEARCH_PREFS); // no explicit search preferences passed if (cloudSearchPrefs == null) { cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); } mImportActivity.loadCallback( new ImportKeysListFragment.CloudLoaderState(query, cloudSearchPrefs)); toggleKeyboard(false); } private void toggleKeyboard(boolean show) { 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; } if (show) { inputManager.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT); } else { inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7795 Content-Disposition: inline; filename="ImportKeysFileFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "133cf299f3bc855e70f0e554d6010dcf97417060" /* * Copyright (C) 2013-2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.Manifest; import android.app.Activity; import android.content.ContentResolver; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.ui.ImportKeysListFragment.BytesLoaderState; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class ImportKeysFileFragment extends Fragment { private ImportKeysActivity mImportActivity; private View mBrowse; private View mClipboardButton; private Uri mCurrentUri; private static final int REQUEST_CODE_FILE = 0x00007003; private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; /** * Creates new instance of this fragment */ public static ImportKeysFileFragment newInstance() { ImportKeysFileFragment frag = new ImportKeysFileFragment(); Bundle args = new Bundle(); frag.setArguments(args); return frag; } /** * Inflate the layout for this fragment */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false); mBrowse = view.findViewById(R.id.import_keys_file_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // open .asc or .gpg files // setting it to text/plain prevents Cyanogenmod's file manager from selecting asc // or gpg types! FileHelper.openDocument(ImportKeysFileFragment.this, Uri.fromFile(Constants.Path.APP_DIR), "*/*", false, REQUEST_CODE_FILE); } }); mClipboardButton = view.findViewById(R.id.import_clipboard_button); mClipboardButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); String sendText = ""; if (clipboardText != null) { sendText = clipboardText.toString(); sendText = PgpHelper.getPgpKeyContent(sendText); if (sendText == null) { Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); return; } mImportActivity.loadCallback(new BytesLoaderState(sendText.getBytes(), null)); } } }); return view; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mImportActivity = (ImportKeysActivity) activity; } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_FILE: { if (resultCode == Activity.RESULT_OK && data != null && data.getData() != null) { mCurrentUri = data.getData(); if (checkAndRequestReadPermission(mCurrentUri)) { startImportingKeys(); } } break; } default: super.onActivityResult(requestCode, resultCode, data); break; } } private void startImportingKeys() { boolean isEncrypted; try { isEncrypted = FileHelper.isEncryptedFile(mImportActivity, mCurrentUri); } catch (IOException e) { Log.e(Constants.TAG, "Error opening file", e); Notify.create(mImportActivity, R.string.error_bad_data, Style.ERROR).show(); return; } if (isEncrypted) { Intent intent = new Intent(mImportActivity, DecryptActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setData(mCurrentUri); startActivity(intent); } else { mImportActivity.loadCallback(new BytesLoaderState(null, mCurrentUri)); } } /** * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. *

* This method returns true on Android < 6, or if permission is already granted. It * requests the permission and returns false otherwise. *

* see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html */ private boolean checkAndRequestReadPermission(final Uri uri) { if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return true; } // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { return true; } requestPermissions( new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } boolean permissionWasGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; if (permissionWasGranted) { startImportingKeys(); } else { Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 19139 Content-Disposition: inline; filename="ImportKeysListFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "4d4219f56b76906a0fede67d8637143a6bf0138b" /* * Copyright (C) 2012-2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import android.Manifest; import android.app.Activity; import android.content.ContentResolver; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.ContextCompat; import android.support.v4.content.Loader; import android.support.v4.util.LongSparseArray; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.ListView; import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; public class ImportKeysListFragment extends ListFragment implements LoaderManager.LoaderCallbacks>> { private static final String ARG_DATA_URI = "uri"; 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_CLOUD_SEARCH_PREFS = "cloud_search_prefs"; private static final int REQUEST_PERMISSION_READ_EXTERNAL_STORAGE = 12; private Activity mActivity; private ImportKeysAdapter mAdapter; private ParcelableProxy mParcelableProxy; private LoaderState mLoaderState; private static final int LOADER_ID_BYTES = 0; private static final int LOADER_ID_CLOUD = 1; private LongSparseArray mCachedKeyData; private boolean mNonInteractive; private boolean mShowingOrbotDialog; public LoaderState getLoaderState() { return mLoaderState; } public List getData() { return mAdapter.getData(); } /** * 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. */ public IteratorWithSize getSelectedData() { final ArrayList entries = getSelectedEntries(); final Iterator it = entries.iterator(); return new IteratorWithSize() { @Override public int getSize() { return entries.size(); } @Override public boolean hasNext() { return it.hasNext(); } @Override public ParcelableKeyRing next() { // throws NoSuchElementException if it doesn't exist, but that's not our problem return mCachedKeyData.get(it.next().hashCode()); } @Override public void remove() { it.remove(); } }; } public ArrayList getSelectedEntries() { if (mAdapter != null) { return mAdapter.getSelectedEntries(); } else { Log.e(Constants.TAG, "Adapter not initialized, returning empty list"); return new ArrayList<>(); } } /** * 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 * Will immediately load data if non-null bytes/dataUri/serverQuery * * @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 cloudSearchPrefs search parameters to use. If null will retrieve from user's * preferences. * @return fragment with arguments set based on passed parameters */ public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, Preferences.CloudSearchPrefs cloudSearchPrefs) { return newInstance(bytes, dataUri, serverQuery, false, cloudSearchPrefs); } /** * Visually consists of a list of keyrings with checkboxes to specify which are to be imported * Will immediately load data if non-null bytes/dataUri/serverQuery is supplied * * @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 cloudSearchPrefs search parameters to use. If null will retrieve from user's * preferences. * @return fragment with arguments set based on passed parameters */ public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery, boolean nonInteractive, Preferences.CloudSearchPrefs cloudSearchPrefs) { ImportKeysListFragment frag = new ImportKeysListFragment(); Bundle args = new Bundle(); args.putByteArray(ARG_BYTES, bytes); args.putParcelable(ARG_DATA_URI, dataUri); args.putString(ARG_SERVER_QUERY, serverQuery); args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive); args.putParcelable(ARG_CLOUD_SEARCH_PREFS, cloudSearchPrefs); frag.setArguments(args); return frag; } static public class LoaderState { } static public class BytesLoaderState extends LoaderState { public byte[] mKeyBytes; public Uri mDataUri; BytesLoaderState(byte[] keyBytes, Uri dataUri) { mKeyBytes = keyBytes; mDataUri = dataUri; } } static public class CloudLoaderState extends LoaderState { Preferences.CloudSearchPrefs mCloudPrefs; String mServerQuery; CloudLoaderState(String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { mServerQuery = serverQuery; mCloudPrefs = cloudPrefs; } } /** * Define Adapter and Loader on create of Activity */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mActivity = getActivity(); // Give some text to display if there is no data. setEmptyText(mActivity.getString(R.string.error_nothing_import)); // Create an empty adapter we will use to display the loaded data. mAdapter = new ImportKeysAdapter(mActivity); setListAdapter(mAdapter); Bundle args = getArguments(); Uri dataUri = args.getParcelable(ARG_DATA_URI); byte[] bytes = args.getByteArray(ARG_BYTES); String query = args.getString(ARG_SERVER_QUERY); mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false); getListView().setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (!mAdapter.isEmpty()) { mActivity.onTouchEvent(event); } return false; } }); getListView().setFastScrollEnabled(true); if (dataUri != null || bytes != null) { mLoaderState = new BytesLoaderState(bytes, dataUri); } else if (query != null) { Preferences.CloudSearchPrefs cloudSearchPrefs = args.getParcelable(ARG_CLOUD_SEARCH_PREFS); if (cloudSearchPrefs == null) { cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs(); } mLoaderState = new CloudLoaderState(query, cloudSearchPrefs); } if (dataUri != null && ! checkAndRequestReadPermission(dataUri)) { return; } restartLoaders(); } /** * Request READ_EXTERNAL_STORAGE permission on Android >= 6.0 to read content from "file" Uris. * * This method returns true on Android < 6, or if permission is already granted. It * requests the permission and returns false otherwise. * * see https://commonsware.com/blog/2015/10/07/runtime-permissions-files-action-send.html */ private boolean checkAndRequestReadPermission(final Uri uri) { if ( ! ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return true; } // Additional check due to https://commonsware.com/blog/2015/11/09/you-cannot-hold-nonexistent-permissions.html if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { return true; } requestPermissions( new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_READ_EXTERNAL_STORAGE); return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode != REQUEST_PERMISSION_READ_EXTERNAL_STORAGE) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } boolean permissionWasGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; if (permissionWasGranted) { // permission granted -> load key restartLoaders(); } else { Toast.makeText(getActivity(), R.string.error_denied_storage_permission, Toast.LENGTH_LONG).show(); getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } } @Override public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); if (mNonInteractive) { return; } // Select checkbox! // Update underlying data and notify adapter of change. The adapter will // update the view automatically. ImportKeysListEntry entry = mAdapter.getItem(position); entry.setSelected(!entry.isSelected()); mAdapter.notifyDataSetChanged(); } public void loadNew(LoaderState loaderState) { mLoaderState = loaderState; if (mLoaderState instanceof BytesLoaderState) { BytesLoaderState ls = (BytesLoaderState) mLoaderState; if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) { return; } } restartLoaders(); } public void destroyLoader() { if (getLoaderManager().getLoader(LOADER_ID_BYTES) != null) { getLoaderManager().destroyLoader(LOADER_ID_BYTES); } if (getLoaderManager().getLoader(LOADER_ID_CLOUD) != null) { getLoaderManager().destroyLoader(LOADER_ID_CLOUD); } if (getView() != null) { setListShown(true); } } private void restartLoaders() { if (mLoaderState instanceof BytesLoaderState) { // Start out with a progress indicator. setListShown(false); getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this); } else if (mLoaderState instanceof CloudLoaderState) { // Start out with a progress indicator. setListShown(false); getLoaderManager().restartLoader(LOADER_ID_CLOUD, null, this); } } @Override public Loader>> onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_BYTES: { return new ImportKeysListLoader(mActivity, (BytesLoaderState) mLoaderState); } case LOADER_ID_CLOUD: { CloudLoaderState ls = (CloudLoaderState) mLoaderState; return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs, mParcelableProxy); } default: return null; } } @Override public void onLoadFinished(Loader>> loader, AsyncTaskResultWrapper> data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) Log.d(Constants.TAG, "data: " + data.getResult()); // swap in the real data! mAdapter.setData(data.getResult()); mAdapter.notifyDataSetChanged(); setListAdapter(mAdapter); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } // free old cached key data mCachedKeyData = null; GetKeyResult getKeyResult = (GetKeyResult) data.getOperationResult(); switch (loader.getId()) { case LOADER_ID_BYTES: if (getKeyResult.success()) { // No error mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings(); } else { getKeyResult.createNotify(getActivity()).show(); } break; case LOADER_ID_CLOUD: if (getKeyResult.success()) { // No error } else if (getKeyResult.isPending()) { if (getKeyResult.getRequiredInputParcel().mType == RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) { if (mShowingOrbotDialog) { // to prevent dialogs stacking return; } // this is because we can't commit fragment dialogs in onLoadFinished Runnable showOrbotDialog = new Runnable() { @Override public void run() { OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() { @Override public void onOrbotStarted() { mShowingOrbotDialog = false; restartLoaders(); } @Override public void onNeutralButton() { mParcelableProxy = ParcelableProxy .getForNoProxy(); mShowingOrbotDialog = false; restartLoaders(); } @Override public void onCancel() { mShowingOrbotDialog = false; } }; if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) { // looks like we didn't have to show the // dialog after all mShowingOrbotDialog = false; restartLoaders(); } } }; new Handler().post(showOrbotDialog); mShowingOrbotDialog = true; } } else { getKeyResult.createNotify(getActivity()).show(); } break; default: break; } } @Override public void onLoaderReset(Loader>> loader) { switch (loader.getId()) { case LOADER_ID_BYTES: // Clear the data in the adapter. mAdapter.clear(); break; case LOADER_ID_CLOUD: // Clear the data in the adapter. mAdapter.clear(); break; default: break; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 10536 Content-Disposition: inline; filename="ImportKeysProxyActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "3969f403997de10bd910b1229dfa231ebb26261a" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.content.Intent; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NfcAdapter; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.FragmentActivity; import android.widget.Toast; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.intents.OpenKeychainIntents; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.SingletonResult; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; import java.util.Locale; /** * Proxy activity (just a transparent content view) to scan QR Codes using the Barcode Scanner app */ public class ImportKeysProxyActivity extends FragmentActivity implements CryptoOperationHelper.Callback { 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"; // for CryptoOperationHelper private String mKeyserver; private ArrayList mKeyList; private CryptoOperationHelper mImportOpHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // this activity itself has no content view (see manifest) handleActions(getIntent()); } protected void handleActions(Intent intent) { String action = intent.getAction(); Uri dataUri = intent.getData(); String scheme = intent.getScheme(); if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) { // Scanning a fingerprint directly with Barcode Scanner, thus we already have scanned processScannedContent(dataUri); } else if (ACTION_SCAN_WITH_RESULT.equals(action) || ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) { new IntentIntegrator(this).setCaptureActivity(QrCodeCaptureActivity.class).initiateScan(); } 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) { handleActionNdefDiscovered(getIntent()); } else { Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1"); finish(); } } else { Log.e(Constants.TAG, "No valid scheme or action given!"); finish(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { if (!mImportOpHelper.handleActivityResult(requestCode, resultCode, data)) { // if a result has been returned, and it does not belong to mImportOpHelper, // return it down to other activity if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { returnResult(data); } else { super.onActivityResult(requestCode, resultCode, data); finish(); } } } if (requestCode == IntentIntegratorSupportV4.REQUEST_CODE) { IntentResult scanResult = IntentIntegratorSupportV4.parseActivityResult(requestCode, resultCode, data); if (scanResult == null || scanResult.getFormatName() == null) { Log.e(Constants.TAG, "scanResult or formatName null! Should not happen!"); finish(); return; } String scannedContent = scanResult.getContents(); processScannedContent(scannedContent); } } private void processScannedContent(String content) { Uri uri = Uri.parse(content); processScannedContent(uri); } private void processScannedContent(Uri uri) { String action = getIntent().getAction(); Log.d(Constants.TAG, "scanned: " + uri); // example: openpgp4fpr:73EE2314F65FA92EC2390D3A718C070100012282 if (uri == null || uri.getScheme() == null || !uri.getScheme().toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) { SingletonResult result = new SingletonResult( SingletonResult.RESULT_ERROR, LogType.MSG_WRONG_QR_CODE); Intent intent = new Intent(); intent.putExtra(SingletonResult.EXTRA_RESULT, result); returnResult(intent); return; } final String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH); if (!fingerprint.matches("[a-fA-F0-9]{40}")) { SingletonResult result = new SingletonResult( SingletonResult.RESULT_ERROR, LogType.MSG_WRONG_QR_CODE_FP); Intent intent = new Intent(); intent.putExtra(SingletonResult.EXTRA_RESULT, result); returnResult(intent); return; } if (ACTION_SCAN_WITH_RESULT.equals(action)) { Intent result = new Intent(); result.putExtra(EXTRA_FINGERPRINT, fingerprint); setResult(RESULT_OK, result); finish(); } else { importKeys(fingerprint); } } public void returnResult(Intent data) { 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 selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); startImportService(selectedEntries); } public void importKeys(String fingerprint) { ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); ArrayList selectedEntries = new ArrayList<>(); selectedEntries.add(keyEntry); startImportService(selectedEntries); } private void startImportService(ArrayList keyRings) { // search config mKeyserver = Preferences.getPreferences(this).getPreferredKeyserver(); mKeyList = keyRings; mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing); mImportOpHelper.cryptoOperation(); } // CryptoOperationHelper.Callback methods @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { Intent certifyIntent = new Intent(this, CertifyKeyActivity.class); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds()); startActivityForResult(certifyIntent, 0); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(ImportKeyResult result) { Bundle returnData = new Bundle(); returnData.putParcelable(OperationResult.EXTRA_RESULT, result); Intent data = new Intent(); data.putExtras(returnData); returnResult(data); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } /** * NFC: Parses the NDEF Message from the intent and prints to the TextView */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) void handleActionNdefDiscovered(Intent intent) { Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); // only one message sent during the beam NdefMessage msg = (NdefMessage) rawMsgs[0]; // record 0 contains the MIME type, record 1 is the AAR, if present final byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); importKeys(receivedKeyringBytes); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 36238 Content-Disposition: inline; filename="KeyListFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "77139f5de366162a749d988e51a50fa478e4451f" /* * Copyright (C) 2013-2015 Dominik Schürmann * Copyright (C) 2014-2015 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.view.MenuItemCompat; import android.support.v7.widget.SearchView; import android.text.TextUtils; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import android.widget.ViewAnimator; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.BenchmarkResult; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.BenchmarkInputParcel; import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; /** * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * StickyListHeaders library which does not extend upon ListView. */ public class KeyListFragment extends LoaderFragment implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, LoaderManager.LoaderCallbacks, FabContainer, CryptoOperationHelper.Callback { static final int REQUEST_ACTION = 1; private static final int REQUEST_DELETE = 2; private static final int REQUEST_VIEW_KEY = 3; private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; // saves the mode object for multiselect, needed for reset at some point private ActionMode mActionMode = null; private Button vSearchButton; private ViewAnimator vSearchContainer; private String mQuery; private FloatingActionsMenu mFab; // for CryptoOperationHelper import private ArrayList mKeyList; private String mKeyserver; private CryptoOperationHelper mImportOpHelper; // for ConsolidateOperation private CryptoOperationHelper mConsolidateOpHelper; /** * Load custom layout with StickyListView from library */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.key_list_fragment, getContainer()); mStickyList = (StickyListHeadersListView) view.findViewById(R.id.key_list_list); mStickyList.setOnItemClickListener(this); mFab = (FloatingActionsMenu) view.findViewById(R.id.fab_main); FloatingActionButton fabQrCode = (FloatingActionButton) view.findViewById(R.id.fab_add_qr_code); FloatingActionButton fabCloud = (FloatingActionButton) view.findViewById(R.id.fab_add_cloud); FloatingActionButton fabFile = (FloatingActionButton) view.findViewById(R.id.fab_add_file); fabQrCode.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mFab.collapse(); scanQrCode(); } }); fabCloud.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mFab.collapse(); searchCloud(); } }); fabFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mFab.collapse(); importFile(); } }); return root; } /** * Define Adapter and Loader on create of Activity */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // show app name instead of "keys" from nav drawer final FragmentActivity activity = getActivity(); activity.setTitle(R.string.app_name); mStickyList.setOnItemClickListener(this); mStickyList.setAreHeadersSticky(true); mStickyList.setDrawingListUnderStickyHeader(false); mStickyList.setFastScrollEnabled(true); // Adds an empty footer view so that the Floating Action Button won't block content // in last few rows. View footer = new View(activity); int spacing = (int) android.util.TypedValue.applyDimension( android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() ); android.widget.AbsListView.LayoutParams params = new android.widget.AbsListView.LayoutParams( android.widget.AbsListView.LayoutParams.MATCH_PARENT, spacing ); footer.setLayoutParams(params); mStickyList.addFooterView(footer, null, false); /* * Multi-selection */ mStickyList.setFastScrollAlwaysVisible(true); mStickyList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); mStickyList.getWrappedList().setMultiChoiceModeListener(new MultiChoiceModeListener() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { android.view.MenuInflater inflater = activity.getMenuInflater(); inflater.inflate(R.menu.key_list_multi, menu); mActionMode = mode; return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { // get IDs for checked positions as long array long[] ids; switch (item.getItemId()) { case R.id.menu_key_list_multi_encrypt: { ids = mAdapter.getCurrentSelectedMasterKeyIds(); encrypt(mode, ids); break; } case R.id.menu_key_list_multi_delete: { ids = mAdapter.getCurrentSelectedMasterKeyIds(); showDeleteKeyDialog(ids, mAdapter.isAnySecretSelected()); break; } } return true; } @Override public void onDestroyActionMode(ActionMode mode) { mActionMode = null; mAdapter.clearSelection(); } @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { if (checked) { mAdapter.setNewSelection(position, true); } else { mAdapter.removeSelection(position); } int count = mStickyList.getCheckedItemCount(); String keysSelected = getResources().getQuantityString( R.plurals.key_list_selected_keys, count, count); mode.setTitle(keysSelected); } }); // We have a menu item to show in action bar. setHasOptionsMenu(true); // Start out with a progress indicator. setContentShown(false); // this view is made visible if no data is available mStickyList.setEmptyView(activity.findViewById(R.id.key_list_empty)); // click on search button (in empty view) starts query for search string vSearchContainer = (ViewAnimator) activity.findViewById(R.id.search_container); vSearchButton = (Button) activity.findViewById(R.id.search_button); vSearchButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startSearchForQuery(); } }); // Create an empty adapter we will use to display the loaded data. mAdapter = new KeyListAdapter(activity, null, 0); mStickyList.setAdapter(mAdapter); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } private void startSearchForQuery() { Activity activity = getActivity(); if (activity == null) { return; } Intent searchIntent = new Intent(activity, ImportKeysActivity.class); searchIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, mQuery); searchIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER); startActivity(searchIntent); } static final String ORDER = KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " COLLATE NOCASE ASC"; @Override public Loader onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. Uri uri; if (!TextUtils.isEmpty(mQuery)) { uri = KeyRings.buildUnifiedKeyRingsFindByUserIdUri(mQuery); } else { uri = KeyRings.buildUnifiedKeyRingsUri(); } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), uri, KeyListAdapter.PROJECTION, null, null, ORDER); } @Override public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.setSearchQuery(mQuery); if (data != null && (mQuery == null || TextUtils.isEmpty(mQuery))) { boolean isSecret = data.moveToFirst() && data.getInt(KeyListAdapter.INDEX_HAS_ANY_SECRET) != 0; if (!isSecret) { MatrixCursor headerCursor = new MatrixCursor(KeyListAdapter.PROJECTION); Long[] row = new Long[KeyListAdapter.PROJECTION.length]; row[KeyListAdapter.INDEX_HAS_ANY_SECRET] = 1L; row[KeyListAdapter.INDEX_MASTER_KEY_ID] = 0L; headerCursor.addRow(row); Cursor dataCursor = data; data = new MergeCursor(new Cursor[] { headerCursor, dataCursor }); } } mAdapter.swapCursor(data); // end action mode, if any if (mActionMode != null) { mActionMode.finish(); } // The list should now be shown. if (isResumed()) { setContentShown(true); } else { setContentShownNoAnimation(true); } } @Override public void onLoaderReset(Loader loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } /** * On click on item, start key view activity */ @Override public void onItemClick(AdapterView adapterView, View view, int position, long id) { Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); viewIntent.setData( KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position))); startActivityForResult(viewIntent, REQUEST_VIEW_KEY); } protected void encrypt(ActionMode mode, long[] masterKeyIds) { Intent intent = new Intent(getActivity(), EncryptFilesActivity.class); intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, masterKeyIds); // used instead of startActivity set actionbar based on callingPackage startActivityForResult(intent, REQUEST_ACTION); mode.finish(); } /** * Show dialog to delete key * * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not */ public void showDeleteKeyDialog(long[] masterKeyIds, boolean hasSecret) { Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, masterKeyIds); intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); if (hasSecret) { intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, Preferences.getPreferences(getActivity()).getPreferredKeyserver()); } startActivityForResult(intent, REQUEST_DELETE); } @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.key_list, menu); if (Constants.DEBUG) { menu.findItem(R.id.menu_key_list_debug_cons).setVisible(true); menu.findItem(R.id.menu_key_list_debug_bench).setVisible(true); menu.findItem(R.id.menu_key_list_debug_read).setVisible(true); menu.findItem(R.id.menu_key_list_debug_write).setVisible(true); menu.findItem(R.id.menu_key_list_debug_first_time).setVisible(true); } // Get the searchview MenuItem searchItem = menu.findItem(R.id.menu_key_list_search); SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); // Execute this when searching searchView.setOnQueryTextListener(this); // Erase search result without focus MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { // disable swipe-to-refresh // mSwipeRefreshLayout.setIsLocked(true); return true; } @Override public boolean onMenuItemActionCollapse(MenuItem item) { mQuery = null; getLoaderManager().restartLoader(0, null, KeyListFragment.this); // enable swipe-to-refresh // mSwipeRefreshLayout.setIsLocked(false); return true; } }); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_key_list_create: createKey(); return true; case R.id.menu_key_list_update_all_keys: updateAllKeys(); return true; case R.id.menu_key_list_debug_read: try { KeychainDatabase.debugBackup(getActivity(), true); Notify.create(getActivity(), "Restored debug_backup.db", Notify.Style.OK).show(); getActivity().getContentResolver().notifyChange(KeychainContract.KeyRings.CONTENT_URI, null); } catch (IOException e) { Log.e(Constants.TAG, "IO Error", e); Notify.create(getActivity(), "IO Error " + e.getMessage(), Notify.Style.ERROR).show(); } return true; case R.id.menu_key_list_debug_write: try { KeychainDatabase.debugBackup(getActivity(), false); Notify.create(getActivity(), "Backup to debug_backup.db completed", Notify.Style.OK).show(); } catch (IOException e) { Log.e(Constants.TAG, "IO Error", e); Notify.create(getActivity(), "IO Error: " + e.getMessage(), Notify.Style.ERROR).show(); } return true; case R.id.menu_key_list_debug_first_time: Preferences prefs = Preferences.getPreferences(getActivity()); prefs.setFirstTime(true); Intent intent = new Intent(getActivity(), CreateKeyActivity.class); intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true); startActivity(intent); getActivity().finish(); return true; case R.id.menu_key_list_debug_cons: consolidate(); return true; case R.id.menu_key_list_debug_bench: benchmark(); return true; default: return super.onOptionsItemSelected(item); } } @Override public boolean onQueryTextSubmit(String s) { return true; } @Override public boolean onQueryTextChange(String s) { Log.d(Constants.TAG, "onQueryTextChange s:" + s); // Called when the action bar search text has changed. Update the // search filter, and restart the loader to do a new query with this // filter. // If the nav drawer is opened, onQueryTextChange("") is executed. // This hack prevents restarting the loader. if (!s.equals(mQuery)) { mQuery = s; getLoaderManager().restartLoader(0, null, this); } if (s.length() > 2) { vSearchButton.setText(getString(R.string.btn_search_for_query, mQuery)); vSearchContainer.setDisplayedChild(1); vSearchContainer.setVisibility(View.VISIBLE); } else { vSearchContainer.setDisplayedChild(0); vSearchContainer.setVisibility(View.GONE); } return true; } private void searchCloud() { Intent importIntent = new Intent(getActivity(), ImportKeysActivity.class); importIntent.putExtra(ImportKeysActivity.EXTRA_QUERY, (String) null); // hack to show only cloud tab startActivity(importIntent); } private void scanQrCode() { Intent scanQrCode = new Intent(getActivity(), ImportKeysProxyActivity.class); scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_IMPORT); startActivityForResult(scanQrCode, REQUEST_ACTION); } private void importFile() { Intent intentImportExisting = new Intent(getActivity(), ImportKeysActivity.class); intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN); startActivityForResult(intentImportExisting, REQUEST_ACTION); } private void createKey() { Intent intent = new Intent(getActivity(), CreateKeyActivity.class); startActivityForResult(intent, REQUEST_ACTION); } private void updateAllKeys() { Activity activity = getActivity(); if (activity == null) { return; } ProviderHelper providerHelper = new ProviderHelper(activity); Cursor cursor = providerHelper.getContentResolver().query( KeyRings.buildUnifiedKeyRingsUri(), new String[]{ KeyRings.FINGERPRINT }, null, null, null ); if (cursor == null) { Notify.create(activity, R.string.error_loading_keys, Notify.Style.ERROR); return; } ArrayList keyList = new ArrayList<>(); try { while (cursor.moveToNext()) { byte[] blob = cursor.getBlob(0);//fingerprint column is 0 String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); keyList.add(keyEntry); } mKeyList = keyList; } finally { cursor.close(); } // search config mKeyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating); mImportOpHelper.setProgressCancellable(true); mImportOpHelper.cryptoOperation(); } private void consolidate() { CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public ConsolidateInputParcel createOperationInput() { return new ConsolidateInputParcel(false); // we want to perform a full consolidate } @Override public void onCryptoOperationSuccess(ConsolidateResult result) { result.createNotify(getActivity()).show(); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(ConsolidateResult result) { result.createNotify(getActivity()).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mConsolidateOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); mConsolidateOpHelper.cryptoOperation(); } private void benchmark() { CryptoOperationHelper.Callback callback = new CryptoOperationHelper.Callback() { @Override public BenchmarkInputParcel createOperationInput() { return new BenchmarkInputParcel(); // we want to perform a full consolidate } @Override public void onCryptoOperationSuccess(BenchmarkResult result) { result.createNotify(getActivity()).show(); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(BenchmarkResult result) { result.createNotify(getActivity()).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; CryptoOperationHelper opHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); opHelper.cryptoOperation(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { mImportOpHelper.handleActivityResult(requestCode, resultCode, data); } if (mConsolidateOpHelper != null) { mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data); } switch (requestCode) { case REQUEST_DELETE: if (mActionMode != null) { mActionMode.finish(); } if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(getActivity()).show(); } else { super.onActivityResult(requestCode, resultCode, data); } break; case REQUEST_ACTION: // if a result has been returned, display a notify if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(getActivity()).show(); } else { super.onActivityResult(requestCode, resultCode, data); } break; case REQUEST_VIEW_KEY: if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(getActivity()).show(); } else { super.onActivityResult(requestCode, resultCode, data); } break; } } @Override public void fabMoveUp(int height) { ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, "translationY", 0, -height); // we're a little behind, so skip 1/10 of the time anim.setDuration(270); anim.start(); } @Override public void fabRestorePosition() { ObjectAnimator anim = ObjectAnimator.ofFloat(mFab, "translationY", 0); // we're a little ahead, so wait a few ms anim.setStartDelay(70); anim.setDuration(300); anim.start(); } // CryptoOperationHelper.Callback methods @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { result.createNotify(getActivity()).show(); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(ImportKeyResult result) { result.createNotify(getActivity()).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter { private HashMap mSelection = new HashMap<>(); private Context mContext; public KeyListAdapter(Context context, Cursor c, int flags) { super(context, c, flags); mContext = context; } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = super.newView(context, cursor, parent); final KeyItemViewHolder holder = (KeyItemViewHolder) view.getTag(); holder.mSlinger.setVisibility(View.VISIBLE); ContentDescriptionHint.setup(holder.mSlingerButton); holder.mSlingerButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (holder.mMasterKeyId != null) { Intent safeSlingerIntent = new Intent(mContext, SafeSlingerActivity.class); safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, holder.mMasterKeyId); startActivityForResult(safeSlingerIntent, REQUEST_ACTION); } } }); return view; } @Override public View getView(int position, View convertView, ViewGroup parent) { // let the adapter handle setting up the row views View v = super.getView(position, convertView, parent); int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis); if (mSelection.get(position) != null) { // selected position color v.setBackgroundColor(colorEmphasis); } else { // default color v.setBackgroundColor(Color.TRANSPARENT); } return v; } @Override public void bindView(View view, Context context, Cursor cursor) { boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); if (isSecret && masterKeyId == 0L) { // sort of a hack: if this item isn't enabled, we make it clickable // to intercept its click events view.setClickable(true); KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); h.setDummy(new OnClickListener() { @Override public void onClick(View v) { createKey(); } }); return; } super.bindView(view, context, cursor); } private class HeaderViewHolder { TextView mText; TextView mCount; } /** * Creates a new header view and binds the section headers to it. It uses the ViewHolder * pattern. Most functionality is similar to getView() from Android's CursorAdapter. *

* NOTE: The variables mDataValid and mCursor are available due to the super class * CursorAdapter. */ @Override public View getHeaderView(int position, View convertView, ViewGroup parent) { HeaderViewHolder holder; if (convertView == null) { holder = new HeaderViewHolder(); convertView = mInflater.inflate(R.layout.key_list_header, parent, false); holder.mText = (TextView) convertView.findViewById(R.id.stickylist_header_text); holder.mCount = (TextView) convertView.findViewById(R.id.contacts_num); convertView.setTag(holder); } else { holder = (HeaderViewHolder) convertView.getTag(); } if (!mDataValid) { // no data available at this point Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); return convertView; } if (!mCursor.moveToPosition(position)) { throw new IllegalStateException("couldn't move cursor to position " + position); } if (mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0) { { // set contact count int num = mCursor.getCount(); // If this is a dummy secret key, subtract one if (mCursor.getLong(INDEX_MASTER_KEY_ID) == 0L) { num -= 1; } String contactsTotal = mContext.getResources().getQuantityString(R.plurals.n_keys, num, num); holder.mCount.setText(contactsTotal); holder.mCount.setVisibility(View.VISIBLE); } holder.mText.setText(convertView.getResources().getString(R.string.my_keys)); return convertView; } // set header text as first char in user id String userId = mCursor.getString(INDEX_USER_ID); String headerText = convertView.getResources().getString(R.string.user_id_no_name); if (userId != null && userId.length() > 0) { headerText = "" + userId.charAt(0); } holder.mText.setText(headerText); holder.mCount.setVisibility(View.GONE); return convertView; } /** * Header IDs should be static, position=1 should always return the same Id that is. */ @Override public long getHeaderId(int position) { if (!mDataValid) { // no data available at this point Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); return -1; } if (!mCursor.moveToPosition(position)) { throw new IllegalStateException("couldn't move cursor to position " + position); } // early breakout: all secret keys are assigned id 0 if (mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0) { return 1L; } // otherwise, return the first character of the name as ID String userId = mCursor.getString(INDEX_USER_ID); if (userId != null && userId.length() > 0) { return Character.toUpperCase(userId.charAt(0)); } else { return Long.MAX_VALUE; } } /** * -------------------------- MULTI-SELECTION METHODS -------------- */ public void setNewSelection(int position, boolean value) { mSelection.put(position, value); notifyDataSetChanged(); } public boolean isAnySecretSelected() { for (int pos : mSelection.keySet()) { if (isSecretAvailable(pos)) { return true; } } return false; } public long[] getCurrentSelectedMasterKeyIds() { long[] ids = new long[mSelection.size()]; int i = 0; // get master key ids for (int pos : mSelection.keySet()) { ids[i++] = getMasterKeyId(pos); } return ids; } public void removeSelection(int position) { mSelection.remove(position); notifyDataSetChanged(); } public void clearSelection() { mSelection.clear(); notifyDataSetChanged(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3522 Content-Disposition: inline; filename="LoaderFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b05335cb8100d5a69a4f6798a6aee43a67cce40f" /* * Copyright (C) 2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import org.sufficientlysecure.keychain.R; /** * This is a fragment helper class, which implements a generic * progressbar/container view. *

* To use it in a fragment, simply subclass, use onCreateView to create the * layout's root view, and ues getContainer() as root view of your subclass. * The layout shows a progress bar by default, and can be switched to the * actual contents by calling setContentShown(). */ public abstract class LoaderFragment extends Fragment { private boolean mContentShown; private View mProgressContainer; private ViewGroup mContainer; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.loader_layout, container, false); mContentShown = true; mContainer = (ViewGroup) root.findViewById(R.id.loader_container); mProgressContainer = root.findViewById(R.id.loader_progress); // content is not shown (by visibility statuses in the layout files) mContentShown = false; return root; } protected ViewGroup getContainer() { return mContainer; } public void setContentShown(boolean shown, boolean animate) { if (mContentShown == shown) { return; } mContentShown = shown; if (shown) { if (animate) { mProgressContainer.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_out)); mContainer.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_in)); } mProgressContainer.setVisibility(View.GONE); mContainer.setVisibility(View.VISIBLE); } else { if (animate) { mProgressContainer.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_in)); mContainer.startAnimation(AnimationUtils.loadAnimation( getActivity(), android.R.anim.fade_out)); } mProgressContainer.setVisibility(View.VISIBLE); mContainer.setVisibility(View.INVISIBLE); } } public void setContentShown(boolean shown) { setContentShown(shown, true); } public void setContentShownNoAnimation(boolean shown) { setContentShown(shown, false); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1614 Content-Disposition: inline; filename="LogDisplayActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "df325d31d92a27bb4bc4e55b29173541550af5eb" /* * Copyright (C) 2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; import android.view.View; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class LogDisplayActivity extends BaseActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Inflate a "Done" custom action bar setFullScreenDialogClose( new View.OnClickListener() { @Override public void onClick(View v) { // "Done" finish(); } } ); } @Override protected void initLayout() { setContentView(R.layout.log_display_activity); } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11789 Content-Disposition: inline; filename="LogDisplayFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "411dac3ef5349a6c75555ecf8eac80731c3cb44c" /* * Copyright (C) 2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel; import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.ui.dialog.ShareLogDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import java.io.IOException; import java.io.OutputStream; public class LogDisplayFragment extends ListFragment implements OnItemClickListener { LogAdapter mAdapter; OperationResult mResult; public static final String EXTRA_RESULT = "log"; protected int mTextColor; private Uri mLogTempFile; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mTextColor = FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorText); setHasOptionsMenu(true); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Intent intent = getActivity().getIntent(); if (intent == null) { getActivity().finish(); return; } if (savedInstanceState != null) { mResult = savedInstanceState.getParcelable(EXTRA_RESULT); } else { mResult = intent.getParcelableExtra(EXTRA_RESULT); } if (mResult == null) { getActivity().finish(); return; } mAdapter = new LogAdapter(getActivity(), mResult.getLog()); setListAdapter(mAdapter); getListView().setOnItemClickListener(this); getListView().setFastScrollEnabled(true); getListView().setDividerHeight(0); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // need to parcel this again, logs are only single-instance parcelable outState.putParcelable(EXTRA_RESULT, mResult); } @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.log_display, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_log_display_export_log: shareLog(); break; } return super.onOptionsItemSelected(item); } private void shareLog() { Activity activity = getActivity(); if (activity == null) { return; } String log = mResult.getLog().getPrintableOperationLog(getResources(), 0); // if there is no log temp file yet, create one if (mLogTempFile == null) { mLogTempFile = TemporaryFileProvider.createFile(getActivity(), "openkeychain_log.txt", "text/plain"); try { OutputStream outputStream = activity.getContentResolver().openOutputStream(mLogTempFile); outputStream.write(log.getBytes()); } catch (IOException e) { Notify.create(activity, R.string.error_log_share_internal, Style.ERROR).show(); return; } } ShareLogDialogFragment shareLogDialog = ShareLogDialogFragment.newInstance(mLogTempFile); shareLogDialog.show(getActivity().getSupportFragmentManager(), "shareLogDialog"); } @Override public void onItemClick(AdapterView parent, View view, int position, long id) { LogEntryParcel parcel = mAdapter.getItem(position); if ( ! (parcel instanceof SubLogEntryParcel)) { return; } Intent intent = new Intent( getActivity(), LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ((SubLogEntryParcel) parcel).getSubResult()); startActivity(intent); } private class LogAdapter extends ArrayAdapter { private LayoutInflater mInflater; private int dipFactor; public LogAdapter(Context context, OperationResult.OperationLog log) { super(context, R.layout.log_display_item, log.toList()); mInflater = LayoutInflater.from(getContext()); dipFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (float) 8, getResources().getDisplayMetrics()); } private class ItemHolder { final View mSecond; final TextView mText, mSecondText; final ImageView mImg, mSecondImg, mSub; public ItemHolder(TextView text, ImageView image, ImageView sub, View second, TextView secondText, ImageView secondImg) { mText = text; mImg = image; mSub = sub; mSecond = second; mSecondText = secondText; mSecondImg = secondImg; } } // Check if convertView.setPadding is redundant @Override public View getView(int position, View convertView, ViewGroup parent) { LogEntryParcel entry = getItem(position); ItemHolder ih; if (convertView == null) { convertView = mInflater.inflate(R.layout.log_display_item, parent, false); ih = new ItemHolder( (TextView) convertView.findViewById(R.id.log_text), (ImageView) convertView.findViewById(R.id.log_img), (ImageView) convertView.findViewById(R.id.log_sub), convertView.findViewById(R.id.log_second), (TextView) convertView.findViewById(R.id.log_second_text), (ImageView) convertView.findViewById(R.id.log_second_img) ); convertView.setTag(ih); } else { ih = (ItemHolder) convertView.getTag(); } if (entry instanceof SubLogEntryParcel) { ih.mSub.setVisibility(View.VISIBLE); convertView.setClickable(false); convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); OperationResult result = ((SubLogEntryParcel) entry).getSubResult(); LogEntryParcel subEntry = result.getLog().getLast(); if (subEntry != null) { ih.mSecond.setVisibility(View.VISIBLE); // special case: first parameter may be a quantity if (subEntry.mParameters != null && subEntry.mParameters.length > 0 && subEntry.mParameters[0] instanceof Integer) { ih.mSecondText.setText(getResources().getQuantityString(subEntry.mType.getMsgId(), (Integer) subEntry.mParameters[0], subEntry.mParameters)); } else { ih.mSecondText.setText(getResources().getString(subEntry.mType.getMsgId(), subEntry.mParameters)); } ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); switch (subEntry.mType.mLevel) { case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break; case INFO: ih.mSecondImg.setBackgroundColor(mTextColor); break; case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; case START: ih.mSecondImg.setBackgroundColor(mTextColor); break; case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } } else { ih.mSecond.setVisibility(View.GONE); } } else { ih.mSub.setVisibility(View.GONE); ih.mSecond.setVisibility(View.GONE); convertView.setClickable(true); } // special case: first parameter may be a quantity if (entry.mParameters != null && entry.mParameters.length > 0 && entry.mParameters[0] instanceof Integer) { ih.mText.setText(getResources().getQuantityString(entry.mType.getMsgId(), (Integer) entry.mParameters[0], entry.mParameters)); } else { ih.mText.setText(getResources().getString(entry.mType.getMsgId(), entry.mParameters)); } convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); switch (entry.mType.mLevel) { case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break; case INFO: ih.mImg.setBackgroundColor(mTextColor); break; case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; case START: ih.mImg.setBackgroundColor(mTextColor); break; case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } return convertView; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 11530 Content-Disposition: inline; filename="MainActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "13df0b5394ca298e2dbe028c913e57182f6213a6" /* * Copyright (C) 2012-2015 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * Copyright (C) 2015 Kai Jiang * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; 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.FragmentManager.OnBackStackChangedListener; import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.Toolbar; import android.view.View; import com.mikepenz.community_material_typeface_library.CommunityMaterial; import com.mikepenz.fontawesome_typeface_library.FontAwesome; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.DividerDrawerItem; 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.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Preferences; public class MainActivity extends BaseSecurityTokenActivity implements FabContainer, OnBackStackChangedListener { static final int ID_KEYS = 1; static final int ID_ENCRYPT_DECRYPT = 2; static final int ID_APPS = 3; static final int ID_BACKUP = 4; static final int ID_SETTINGS = 5; static final int ID_HELP = 6; // both of these are used for instrumentation testing only public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time"; public static final String EXTRA_INIT_FRAG = "init_frag"; public Drawer mDrawer; private Toolbar mToolbar; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); mToolbar = (Toolbar) findViewById(R.id.toolbar); mToolbar.setTitle(R.string.app_name); setSupportActionBar(mToolbar); mDrawer = new DrawerBuilder() .withActivity(this) .withHeader(R.layout.main_drawer_header) .withToolbar(mToolbar) .addDrawerItems( new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key) .withIdentifier(ID_KEYS).withSelectable(false), new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock) .withIdentifier(ID_ENCRYPT_DECRYPT).withSelectable(false), new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps) .withIdentifier(ID_APPS).withSelectable(false), new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore) .withIdentifier(ID_BACKUP).withSelectable(false), new DividerDrawerItem(), new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(ID_SETTINGS).withSelectable(false), new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(ID_HELP).withSelectable(false) ) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override public boolean onItemClick(View view, int position, IDrawerItem drawerItem) { if (drawerItem != null) { PrimaryDrawerItem item = (PrimaryDrawerItem) drawerItem; Intent intent = null; switch ((int) item.getIdentifier()) { case ID_KEYS: onKeysSelected(); break; case ID_ENCRYPT_DECRYPT: onEnDecryptSelected(); break; case ID_APPS: onAppsSelected(); break; case ID_BACKUP: onBackupSelected(); break; case ID_SETTINGS: intent = new Intent(MainActivity.this, SettingsActivity.class); break; case ID_HELP: intent = new Intent(MainActivity.this, HelpActivity.class); break; } if (intent != null) { MainActivity.this.startActivity(intent); } } return false; } }) .withSelectedItem(-1) .withSavedInstance(savedInstanceState) .build(); // if this is the first time show first time activity Preferences prefs = Preferences.getPreferences(this); if (!getIntent().getBooleanExtra(EXTRA_SKIP_FIRST_TIME, false) && prefs.isFirstTime()) { Intent intent = new Intent(this, CreateKeyActivity.class); intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true); startActivity(intent); finish(); return; } getSupportFragmentManager().addOnBackStackChangedListener(this); // all further initialization steps are saved as instance state if (savedInstanceState != null) { return; } Intent data = getIntent(); // If we got an EXTRA_RESULT in the intent, show the notification if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(this).show(); } // always initialize keys fragment to the bottom of the backstack onKeysSelected(); if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) { // initialize FragmentLayout with KeyListFragment at first switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) { case ID_ENCRYPT_DECRYPT: onEnDecryptSelected(); break; case ID_APPS: onAppsSelected(); break; } } } private void setFragment(Fragment fragment, boolean addToBackStack) { FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction ft = fragmentManager.beginTransaction(); ft.replace(R.id.main_fragment_container, fragment); if (addToBackStack) { ft.addToBackStack(null); } ft.commit(); } private void onKeysSelected() { mToolbar.setTitle(R.string.app_name); mDrawer.setSelection(ID_KEYS, false); Fragment frag = new KeyListFragment(); setFragment(frag, false); } private void onEnDecryptSelected() { mToolbar.setTitle(R.string.nav_encrypt_decrypt); mDrawer.setSelection(ID_ENCRYPT_DECRYPT, false); Fragment frag = new EncryptDecryptFragment(); setFragment(frag, true); } private void onAppsSelected() { mToolbar.setTitle(R.string.nav_apps); mDrawer.setSelection(ID_APPS, false); Fragment frag = new AppsListFragment(); setFragment(frag, true); } private void onBackupSelected() { mToolbar.setTitle(R.string.nav_backup); mDrawer.setSelection(ID_BACKUP, false); Fragment frag = new BackupRestoreFragment(); setFragment(frag, true); } @Override protected void onSaveInstanceState(Bundle outState) { // add the values which need to be saved from the drawer to the bundle outState = mDrawer.saveInstanceState(outState); super.onSaveInstanceState(outState); } @Override public void onBackPressed() { // close the drawer first and if the drawer is closed do regular backstack handling if (mDrawer != null && mDrawer.isDrawerOpen()) { mDrawer.closeDrawer(); } else { super.onBackPressed(); } } @Override public void fabMoveUp(int height) { Object fragment = getSupportFragmentManager() .findFragmentById(R.id.main_fragment_container); if (fragment instanceof FabContainer) { ((FabContainer) fragment).fabMoveUp(height); } } @Override public void fabRestorePosition() { Object fragment = getSupportFragmentManager() .findFragmentById(R.id.main_fragment_container); if (fragment instanceof FabContainer) { ((FabContainer) fragment).fabRestorePosition(); } } @Override public void onBackStackChanged() { FragmentManager fragmentManager = getSupportFragmentManager(); if (fragmentManager == null) { return; } Fragment frag = fragmentManager.findFragmentById(R.id.main_fragment_container); if (frag == null) { return; } // make sure the selected icon is the one shown at this point if (frag instanceof KeyListFragment) { mToolbar.setTitle(R.string.app_name); mDrawer.setSelection(mDrawer.getPosition(ID_KEYS), false); } else if (frag instanceof EncryptDecryptFragment) { mToolbar.setTitle(R.string.nav_encrypt_decrypt); mDrawer.setSelection(mDrawer.getPosition(ID_ENCRYPT_DECRYPT), false); } else if (frag instanceof AppsListFragment) { mToolbar.setTitle(R.string.nav_apps); mDrawer.setSelection(mDrawer.getPosition(ID_APPS), false); } else if (frag instanceof BackupRestoreFragment) { mToolbar.setTitle(R.string.nav_backup); mDrawer.setSelection(mDrawer.getPosition(ID_BACKUP), false); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7968 Content-Disposition: inline; filename="MultiUserIdsFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "5116073ae21b60ffe8c6caf44611ed2904c0128f" package org.sufficientlysecure.keychain.ui; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import org.sufficientlysecure.keychain.util.Log; import java.util.ArrayList; public class MultiUserIdsFragment extends Fragment implements LoaderManager.LoaderCallbacks{ public static final String ARG_CHECK_STATES = "check_states"; public static final String EXTRA_KEY_IDS = "extra_key_ids"; private boolean checkboxVisibility = true; ListView mUserIds; private MultiUserIdsAdapter mUserIdsAdapter; private long[] mPubMasterKeyIds; public static final String[] USER_IDS_PROJECTION = new String[]{ KeychainContract.UserPackets._ID, KeychainContract.UserPackets.MASTER_KEY_ID, KeychainContract.UserPackets.USER_ID, KeychainContract.UserPackets.IS_PRIMARY, KeychainContract.UserPackets.IS_REVOKED }; private static final int INDEX_MASTER_KEY_ID = 1; private static final int INDEX_USER_ID = 2; @SuppressWarnings("unused") private static final int INDEX_IS_PRIMARY = 3; @SuppressWarnings("unused") private static final int INDEX_IS_REVOKED = 4; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.multi_user_ids_fragment, null); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); return view; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mPubMasterKeyIds = getActivity().getIntent().getLongArrayExtra(EXTRA_KEY_IDS); if (mPubMasterKeyIds == null) { Log.e(Constants.TAG, "List of key ids to certify missing!"); getActivity().finish(); return; } ArrayList checkedStates = null; if (savedInstanceState != null) { checkedStates = (ArrayList) savedInstanceState.getSerializable(ARG_CHECK_STATES); } mUserIdsAdapter = new MultiUserIdsAdapter(getActivity(), null, 0, checkedStates, checkboxVisibility); mUserIds.setAdapter(mUserIdsAdapter); mUserIds.setDividerHeight(0); getLoaderManager().initLoader(0, null, this); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); ArrayList states = mUserIdsAdapter.getCheckStates(); // no proper parceling method available :( outState.putSerializable(ARG_CHECK_STATES, states); } public ArrayList getSelectedCertifyActions() { if (!checkboxVisibility) { throw new AssertionError("Item selection not allowed"); } return mUserIdsAdapter.getSelectedCertifyActions(); } @Override public Loader onCreateLoader(int id, Bundle args) { Uri uri = KeychainContract.UserPackets.buildUserIdsUri(); String selection, ids[]; { // generate placeholders and string selection args ids = new String[mPubMasterKeyIds.length]; StringBuilder placeholders = new StringBuilder("?"); for (int i = 0; i < mPubMasterKeyIds.length; i++) { ids[i] = Long.toString(mPubMasterKeyIds[i]); if (i != 0) { placeholders.append(",?"); } } // put together selection string selection = KeychainContract.UserPackets.IS_REVOKED + " = 0" + " AND " + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + " IN (" + placeholders + ")"; } return new CursorLoader(getActivity(), uri, USER_IDS_PROJECTION, selection, ids, KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.MASTER_KEY_ID + " ASC" + ", " + KeychainDatabase.Tables.USER_PACKETS + "." + KeychainContract.UserPackets.USER_ID + " ASC" ); } @Override public void onLoadFinished(Loader loader, Cursor data) { MatrixCursor matrix = new MatrixCursor(new String[]{ "_id", "user_data", "grouped" }) { @Override public byte[] getBlob(int column) { return super.getBlob(column); } }; data.moveToFirst(); long lastMasterKeyId = 0; String lastName = ""; ArrayList uids = new ArrayList<>(); boolean header = true; // Iterate over all rows while (!data.isAfterLast()) { long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); String userId = data.getString(INDEX_USER_ID); OpenPgpUtils.UserId pieces = KeyRing.splitUserId(userId); // Two cases: boolean grouped = masterKeyId == lastMasterKeyId; boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name); // Remember for next loop lastName = pieces.name; Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped")); if (!subGrouped) { // 1. This name should NOT be grouped with the previous, so we flush the buffer Parcel p = Parcel.obtain(); p.writeStringList(uids); byte[] d = p.marshall(); p.recycle(); matrix.addRow(new Object[]{ lastMasterKeyId, d, header ? 1 : 0 }); // indicate that we have a header for this masterKeyId header = false; // Now clear the buffer, and add the new user id, for the next round uids.clear(); } // 2. This name should be grouped with the previous, just add to buffer uids.add(userId); lastMasterKeyId = masterKeyId; // If this one wasn't grouped, the next one's gotta be a header if (!grouped) { header = true; } // Regardless of the outcome, move to next entry data.moveToNext(); } // If there is anything left in the buffer, flush it one last time if (!uids.isEmpty()) { Parcel p = Parcel.obtain(); p.writeStringList(uids); byte[] d = p.marshall(); p.recycle(); matrix.addRow(new Object[]{ lastMasterKeyId, d, header ? 1 : 0 }); } mUserIdsAdapter.swapCursor(matrix); } @Override public void onLoaderReset(Loader loader) { mUserIdsAdapter.swapCursor(null); } public void setCheckboxVisibility(boolean checkboxVisibility) { this.checkboxVisibility = checkboxVisibility; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 6479 Content-Disposition: inline; filename="OrbotRequiredDialogActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "0e70cda14dccfd76f4b86c1a33e8841cc831627b" /* * Copyright (C) 2015 Dominik Schürmann * Copyright (C) 2015 Adithya Abraham Philip * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.FragmentActivity; import android.view.ContextThemeWrapper; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; /** * Simply encapsulates a dialog. If orbot is not installed, it shows an install dialog, else a * dialog to enable orbot. */ public class OrbotRequiredDialogActivity extends FragmentActivity implements OrbotHelper.DialogActions { public static final int MESSAGE_ORBOT_STARTED = 1; public static final int MESSAGE_ORBOT_IGNORE = 2; public static final int MESSAGE_DIALOG_CANCEL = 3; // if suppplied and true will start Orbot directly without showing dialog public static final String EXTRA_START_ORBOT = "start_orbot"; // used for communicating results when triggered from a service public static final String EXTRA_MESSENGER = "messenger"; // to provide any previous crypto input into which proxy preference is merged public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; private CryptoInputParcel mCryptoInputParcel; private Messenger mMessenger; private ProgressDialog mShowOrbotProgressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); if (mCryptoInputParcel == null) { // compatibility with usages that don't use a CryptoInputParcel mCryptoInputParcel = new CryptoInputParcel(); } mMessenger = getIntent().getParcelableExtra(EXTRA_MESSENGER); boolean startOrbotDirect = getIntent().getBooleanExtra(EXTRA_START_ORBOT, false); if (startOrbotDirect) { ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(this); mShowOrbotProgressDialog = new ProgressDialog(theme); mShowOrbotProgressDialog.setTitle(R.string.progress_starting_orbot); mShowOrbotProgressDialog.setCancelable(false); mShowOrbotProgressDialog.show(); OrbotHelper.bestPossibleOrbotStart(this, this, false); } else { showDialog(); } } /** * Displays an install or start orbot dialog (or silent orbot start) depending on orbot's * presence and state */ public void showDialog() { DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { if (OrbotHelper.putOrbotInRequiredState(OrbotRequiredDialogActivity.this, OrbotRequiredDialogActivity.this)) { // no action required after all onOrbotStarted(); } } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case OrbotHelper.START_TOR_RESULT: { dismissOrbotProgressDialog(); // unfortunately, this result is returned immediately and not when Orbot is started // 10s is approximately the longest time Orbot has taken to start new Handler().postDelayed(new Runnable() { @Override public void run() { onOrbotStarted(); // assumption that orbot was started } }, 10000); } } } /** * for when Orbot is started without showing the dialog by the EXTRA_START_ORBOT intent extra */ private void dismissOrbotProgressDialog() { if (mShowOrbotProgressDialog != null) { mShowOrbotProgressDialog.dismiss(); } } @Override public void onOrbotStarted() { dismissOrbotProgressDialog(); sendMessage(MESSAGE_ORBOT_STARTED); Intent intent = new Intent(); // send back unmodified CryptoInputParcel for a retry intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); setResult(RESULT_OK, intent); finish(); } @Override public void onNeutralButton() { sendMessage(MESSAGE_ORBOT_IGNORE); Intent intent = new Intent(); mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy()); intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); setResult(RESULT_OK, intent); finish(); } @Override public void onCancel() { sendMessage(MESSAGE_DIALOG_CANCEL); finish(); } private void sendMessage(int what) { if (mMessenger != null) { Message msg = Message.obtain(); msg.what = what; try { mMessenger.send(msg); } catch (RemoteException e) { Log.e(Constants.TAG, "Could not deliver message", e); } } } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1727 Content-Disposition: inline; filename="PanicExitActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "3f64bfe25c69c5bb09acd7e85438aee03e15d903" /* * Copyright (C) 2015-2016 Hans-Christoph Steiner * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Build; import android.os.Bundle; /** * For Guardianproject's PANIC app */ public class PanicExitActivity extends Activity { @SuppressLint("NewApi") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= 21) { finishAndRemoveTask(); } else { finish(); } System.exit(0); } public static void exitAndRemoveFromRecentApps(Activity activity) { Intent intent = new Intent(activity, PanicExitActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); activity.startActivity(intent); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1738 Content-Disposition: inline; filename="PanicResponderActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "ff7b649f789ba143b300719c37b6a8c7858d6e8e" /* * Copyright (C) 2015-2016 Hans-Christoph Steiner * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Build; import android.os.Bundle; import org.sufficientlysecure.keychain.service.PassphraseCacheService; /** * Responder for Guardianproject's PANIC app */ public class PanicResponderActivity extends Activity { public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER"; @SuppressLint("NewApi") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) { PassphraseCacheService.clearCachedPassphrases(this); PanicExitActivity.exitAndRemoveFromRecentApps(this); } if (Build.VERSION.SDK_INT >= 21) { finishAndRemoveTask(); } else { finish(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 25663 Content-Disposition: inline; filename="PassphraseDialogActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "5f4303b32fbccfa45b972eea89923baaf7f46943" /* * Copyright (C) 2014-2016 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.text.method.PasswordTransformationMethod; import android.view.ContextThemeWrapper; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewAnimator; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.widget.CacheTTLSpinner; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; /** * We can not directly create a dialog on the application context. * This activity encapsulates a DialogFragment to emulate a dialog. * NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created * internally and is NOT meant to be used by signing operations before adding a signature time */ public class PassphraseDialogActivity extends FragmentActivity { public static final String RESULT_CRYPTO_INPUT = "result_data"; public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // do not allow screenshots of passphrase input // to prevent "too easy" passphrase theft by root apps if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { getWindow().setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ); } CryptoInputParcel cryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); if (cryptoInputParcel == null) { cryptoInputParcel = new CryptoInputParcel(); getIntent().putExtra(EXTRA_CRYPTO_INPUT, cryptoInputParcel); } // this activity itself has no content view (see manifest) RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT); if (requiredInput.mType != RequiredInputType.PASSPHRASE) { return; } // handle empty passphrases by directly returning an empty crypto input parcel try { CachedPublicKeyRing pubRing = new ProviderHelper(this).getCachedPublicKeyRing(requiredInput.getMasterKeyId()); // use empty passphrase for empty passphrase if (pubRing.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) { // also return passphrase back to activity Intent returnIntent = new Intent(); cryptoInputParcel.mPassphrase = new Passphrase(""); returnIntent.putExtra(RESULT_CRYPTO_INPUT, cryptoInputParcel); setResult(RESULT_OK, returnIntent); finish(); } } catch (NotFoundException e) { Log.e(Constants.TAG, "Key not found?!", e); setResult(RESULT_CANCELED); finish(); } } @Override protected void onResumeFragments() { super.onResumeFragments(); /* Show passphrase dialog to cache a new passphrase the user enters for using it later for * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks * for a symmetric passphrase */ PassphraseDialogFragment frag = new PassphraseDialogFragment(); frag.setArguments(getIntent().getExtras()); frag.show(getSupportFragmentManager(), "passphraseDialog"); } @Override protected void onPause() { super.onPause(); DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag("passphraseDialog"); if (dialog != null) { dialog.dismiss(); } } public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener { private EditText mPassphraseEditText; private TextView mPassphraseText; private EditText[] mBackupCodeEditText; private boolean mIsCancelled = false; private RequiredInputParcel mRequiredInput; private ViewAnimator mLayout; private CacheTTLSpinner mTimeToLiveSpinner; @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); mRequiredInput = getArguments().getParcelable(EXTRA_REQUIRED_INPUT); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme); // No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts //alert.setTitle() if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) { LayoutInflater inflater = LayoutInflater.from(theme); View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null); alert.setView(view); mBackupCodeEditText = new EditText[6]; mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1); mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2); mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3); mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4); mBackupCodeEditText[4] = (EditText) view.findViewById(R.id.backup_code_5); mBackupCodeEditText[5] = (EditText) view.findViewById(R.id.backup_code_6); setupEditTextFocusNext(mBackupCodeEditText); AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); return dialog; } LayoutInflater inflater = LayoutInflater.from(theme); mLayout = (ViewAnimator) inflater.inflate(R.layout.passphrase_dialog, null); alert.setView(mLayout); mPassphraseText = (TextView) mLayout.findViewById(R.id.passphrase_text); mPassphraseEditText = (EditText) mLayout.findViewById(R.id.passphrase_passphrase); View vTimeToLiveLayout = mLayout.findViewById(R.id.remember_layout); vTimeToLiveLayout.setVisibility(mRequiredInput.mSkipCaching ? View.GONE : View.VISIBLE); mTimeToLiveSpinner = (CacheTTLSpinner) mLayout.findViewById(R.id.ttl_spinner); alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); String userId; CanonicalizedSecretKey.SecretKeyType keyType = CanonicalizedSecretKey.SecretKeyType.PASSPHRASE; String message; String hint; if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) { message = getString(R.string.passphrase_for_symmetric_encryption); hint = getString(R.string.label_passphrase); } else { try { long subKeyId = mRequiredInput.getSubKeyId(); ProviderHelper helper = new ProviderHelper(activity); CachedPublicKeyRing cachedPublicKeyRing = helper.getCachedPublicKeyRing( KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); // yes the inner try/catch block is necessary, otherwise the final variable // above can't be statically verified to have been set in all cases because // the catch clause doesn't return. String mainUserId = cachedPublicKeyRing.getPrimaryUserIdWithFallback(); OpenPgpUtils.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId); if (mainUserIdSplit.name != null) { userId = mainUserIdSplit.name; } else { userId = getString(R.string.user_id_no_name); } keyType = cachedPublicKeyRing.getSecretKeyType(subKeyId); switch (keyType) { case PASSPHRASE: message = getString(R.string.passphrase_for, userId); hint = getString(R.string.label_passphrase); break; case PIN: message = getString(R.string.pin_for, userId); hint = getString(R.string.label_pin); break; case DIVERT_TO_CARD: message = getString(R.string.security_token_pin_for, userId); hint = getString(R.string.label_pin); break; // special case: empty passphrase just returns the empty passphrase case PASSPHRASE_EMPTY: finishCaching(new Passphrase("")); default: throw new AssertionError("Unhandled SecretKeyType (should not happen)"); } } catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) { alert.setTitle(R.string.title_key_not_found); alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId())); alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dismiss(); } }); alert.setCancelable(false); return alert.create(); } } mPassphraseText.setText(message); mPassphraseEditText.setHint(hint); openKeyboard(mPassphraseEditText); mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForSecurityTokenPin()) || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER); mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); } else { mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); return dialog; } /** * 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(); } private static void setupEditTextFocusNext(final EditText[] backupCodes) { for (int i = 0; i < backupCodes.length - 1; i++) { final int next = i + 1; backupCodes[i].addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean inserting = before < count; boolean cursorAtEnd = (start + count) == 4; if (inserting && cursorAtEnd) { backupCodes[next].requestFocus(); } } @Override public void afterTextChanged(Editable s) { } }); } } @Override public void onStart() { super.onStart(); // Override the default behavior so the dialog is NOT dismissed on click final Button positive = ((AlertDialog) getDialog()).getButton(DialogInterface.BUTTON_POSITIVE); positive.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) { StringBuilder backupCodeInput = new StringBuilder(26); for (EditText editText : mBackupCodeEditText) { if (editText.getText().length() < 4) { return; } backupCodeInput.append(editText.getText()); backupCodeInput.append('-'); } backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); Passphrase passphrase = new Passphrase(backupCodeInput.toString()); finishCaching(passphrase); return; } final Passphrase passphrase = new Passphrase(mPassphraseEditText); final int timeToLiveSeconds = mTimeToLiveSpinner.getSelectedTimeToLive(); // Early breakout if we are dealing with a symmetric key if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) { if (!mRequiredInput.mSkipCaching) { PassphraseCacheService.addCachedPassphrase(getActivity(), Constants.key.symmetric, Constants.key.symmetric, passphrase, getString(R.string.passp_cache_notif_pwd), timeToLiveSeconds); } finishCaching(passphrase); return; } mLayout.setDisplayedChild(1); positive.setEnabled(false); new AsyncTask() { @Override protected CanonicalizedSecretKey doInBackground(Void... params) { try { long timeBeforeOperation = System.currentTimeMillis(); Long subKeyId = mRequiredInput.getSubKeyId(); CanonicalizedSecretKeyRing secretKeyRing = new ProviderHelper(getActivity()).getCanonicalizedSecretKeyRing( KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); CanonicalizedSecretKey secretKeyToUnlock = secretKeyRing.getSecretKey(subKeyId); // this is the operation may take a very long time (100ms to several seconds!) boolean unlockSucceeded = secretKeyToUnlock.unlock(passphrase); // if it didn't take that long, give the user time to appreciate the progress bar long operationTime = System.currentTimeMillis() - timeBeforeOperation; if (operationTime < 100) { try { Thread.sleep(100 - operationTime); } catch (InterruptedException e) { // ignore } } return unlockSucceeded ? secretKeyToUnlock : null; } catch (NotFoundException | PgpGeneralException e) { Toast.makeText(getActivity(), R.string.error_could_not_extract_private_key, Toast.LENGTH_SHORT).show(); getActivity().setResult(RESULT_CANCELED); dismiss(); getActivity().finish(); return null; } } /** Handle a good or bad passphrase. This happens in the UI thread! */ @Override protected void onPostExecute(CanonicalizedSecretKey result) { super.onPostExecute(result); // if we were cancelled in the meantime, the result isn't relevant anymore if (mIsCancelled) { return; } // if the passphrase was wrong, reset and re-enable the dialogue if (result == null) { mPassphraseEditText.setText(""); mPassphraseEditText.setError(getString(R.string.wrong_passphrase)); mLayout.setDisplayedChild(0); positive.setEnabled(true); return; } // cache the new passphrase as specified in CryptoInputParcel Log.d(Constants.TAG, "Everything okay!"); if (mRequiredInput.mSkipCaching) { Log.d(Constants.TAG, "Not caching entered passphrase!"); } else { Log.d(Constants.TAG, "Caching entered passphrase"); try { PassphraseCacheService.addCachedPassphrase(getActivity(), mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase, result.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "adding of a passphrase failed", e); } } finishCaching(passphrase); } }.execute(); } }); } private void finishCaching(Passphrase passphrase) { // any indication this isn't needed anymore, don't do it. if (mIsCancelled || getActivity() == null) { return; } CryptoInputParcel inputParcel = getArguments().getParcelable(EXTRA_CRYPTO_INPUT); // noinspection ConstantConditions, we handle the non-null case in PassphraseDialogActivity.onCreate() inputParcel.mPassphrase = passphrase; ((PassphraseDialogActivity) getActivity()).handleResult(inputParcel); dismiss(); getActivity().finish(); } @Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); // note we need no synchronization here, this variable is only accessed in the ui thread mIsCancelled = true; getActivity().setResult(RESULT_CANCELED); getActivity().finish(); } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); hideKeyboard(); } private void hideKeyboard() { if (getActivity() == null) { return; } InputMethodManager inputManager = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // Associate the "done" button on the soft keyboard with the okay button in the view if (EditorInfo.IME_ACTION_DONE == actionId) { AlertDialog dialog = ((AlertDialog) getDialog()); Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE); bt.performClick(); return true; } return false; } } /** * Defines how the result of this activity is returned. * Is overwritten in RemotePassphraseDialogActivity */ protected void handleResult(CryptoInputParcel inputParcel) { // also return passphrase back to activity Intent returnIntent = new Intent(); returnIntent.putExtra(RESULT_CRYPTO_INPUT, inputParcel); setResult(RESULT_OK, returnIntent); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4202 Content-Disposition: inline; filename="QrCodeCaptureActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b5d3948bec3a0e51c1da4ae2f6bb6533e969c0be" /* * Copyright (C) 2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.view.KeyEvent; import com.journeyapps.barcodescanner.CaptureManager; import com.journeyapps.barcodescanner.CompoundBarcodeView; import org.sufficientlysecure.keychain.R; public class QrCodeCaptureActivity extends FragmentActivity { private CaptureManager capture; private CompoundBarcodeView barcodeScannerView; public static final int MY_PERMISSIONS_REQUEST_CAMERA = 42; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.qr_code_capture_activity); barcodeScannerView = (CompoundBarcodeView) findViewById(R.id.zxing_barcode_scanner); barcodeScannerView.setStatusText(getString(R.string.import_qr_code_text)); if (savedInstanceState != null) { init(barcodeScannerView, getIntent(), savedInstanceState); } // check Android 6 permission if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { init(barcodeScannerView, getIntent(), null); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, MY_PERMISSIONS_REQUEST_CAMERA); } } private void init(CompoundBarcodeView barcodeScannerView, Intent intent, Bundle savedInstanceState) { capture = new CaptureManager(this, barcodeScannerView); capture.initializeFromIntent(intent, savedInstanceState); capture.decode(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST_CAMERA: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted init(barcodeScannerView, getIntent(), null); } else { setResult(Activity.RESULT_CANCELED); finish(); } } } } @Override protected void onResume() { super.onResume(); if (capture != null) { capture.onResume(); } } @Override protected void onPause() { super.onPause(); if (capture != null) { capture.onPause(); } } @Override protected void onDestroy() { super.onDestroy(); if (capture != null) { capture.onDestroy(); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (capture != null) { capture.onSaveInstanceState(outState); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 4789 Content-Disposition: inline; filename="QrCodeViewActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "e54852f1b11c2d28f1b0665bb4db77b7807c0235" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v7.widget.CardView; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.Log; public class QrCodeViewActivity extends BaseActivity { private ImageView mQrCode; private CardView mQrCodeLayout; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Inflate a "Done" custom action bar setFullScreenDialogClose( new View.OnClickListener() { @Override public void onClick(View v) { // "Done" ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } } ); Uri dataUri = getIntent().getData(); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); return; } mQrCode = (ImageView) findViewById(R.id.qr_code_image); mQrCodeLayout = (CardView) findViewById(R.id.qr_code_image_layout); mQrCodeLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } }); ProviderHelper providerHelper = new ProviderHelper(this); try { byte[] blob = (byte[]) providerHelper.getGenericData( KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), KeychainContract.KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); if (blob == null) { Log.e(Constants.TAG, "key not found!"); Notify.create(this, R.string.error_key_not_found, Style.ERROR).show(); ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } Uri uri = new Uri.Builder() .scheme(Constants.FINGERPRINT_SCHEME) .opaquePart(KeyFormattingUtils.convertFingerprintToHex(blob)) .build(); // create a minimal size qr code, we can keep this in ram no problem final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(uri, 0); mQrCode.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // create actual bitmap in display dimensions Bitmap scaled = Bitmap.createScaledBitmap(qrCode, mQrCode.getWidth(), mQrCode.getWidth(), false); mQrCode.setImageBitmap(scaled); } }); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); Notify.create(this, R.string.error_key_not_found, Style.ERROR).show(); ActivityCompat.finishAfterTransition(QrCodeViewActivity.this); } } @Override protected void initLayout() { setContentView(R.layout.qr_code_activity); } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2319 Content-Disposition: inline; filename="RedirectImportKeysActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "5cb680a574c9dd9b2867459623b8893a3044b337" /* * Copyright (C) 2016 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AlertDialog; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class RedirectImportKeysActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); startQrCodeCaptureActivity(); } private void startQrCodeCaptureActivity() { final Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class); scanQrCode.setAction(ImportKeysProxyActivity.ACTION_QR_CODE_API); new AlertDialog.Builder(this) .setTitle(R.string.redirect_import_key_title) .setMessage(R.string.redirect_import_key_message) .setPositiveButton(R.string.redirect_import_key_yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // directly scan with OpenKeychain startActivity(scanQrCode); finish(); } }) .setNegativeButton(R.string.redirect_import_key_no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // close window finish(); } }) .show(); } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 3520 Content-Disposition: inline; filename="RetryUploadDialogActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "2a00e8b70a15e345ed0883954cb489fba17db90e" /* * Copyright (C) 2013-2015 Dominik Schürmann * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2015 Adithya Abraham Philip * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.view.ContextThemeWrapper; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; public class RetryUploadDialogActivity extends FragmentActivity { public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); UploadRetryDialogFragment.newInstance().show(getSupportFragmentManager(), "uploadRetryDialog"); } public static class UploadRetryDialogFragment extends DialogFragment { public static UploadRetryDialogFragment newInstance() { return new UploadRetryDialogFragment(); } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(getActivity()); CustomAlertDialogBuilder dialogBuilder = new CustomAlertDialogBuilder(theme); dialogBuilder.setTitle(R.string.retry_up_dialog_title); dialogBuilder.setMessage(R.string.retry_up_dialog_message); dialogBuilder.setNegativeButton(R.string.retry_up_dialog_btn_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { getActivity().setResult(RESULT_CANCELED); getActivity().finish(); } }); dialogBuilder.setPositiveButton(R.string.retry_up_dialog_btn_reupload, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(); intent.putExtra(RESULT_CRYPTO_INPUT, getActivity() .getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT)); getActivity().setResult(RESULT_OK, intent); getActivity().finish(); } }); return dialogBuilder.show(); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8317 Content-Disposition: inline; filename="SafeSlingerActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "534dbfd0544191609e359e9d9fa55ab2446faf41" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; import android.content.Intent; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.ImageView; import android.widget.NumberPicker; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; import java.io.IOException; import java.util.ArrayList; import edu.cmu.cylab.starslinger.exchange.ExchangeActivity; import edu.cmu.cylab.starslinger.exchange.ExchangeConfig; @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class SafeSlingerActivity extends BaseActivity implements CryptoOperationHelper.Callback { private static final int REQUEST_CODE_SAFE_SLINGER = 211; public static final String EXTRA_MASTER_KEY_ID = "master_key_id"; private long mMasterKeyId; private int mSelectedNumber = 2; // for CryptoOperationHelper private ArrayList mKeyList; private String mKeyserver; private CryptoOperationHelper mOperationHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mMasterKeyId = getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0); NumberPicker picker = (NumberPicker) findViewById(R.id.safe_slinger_picker); picker.setMinValue(2); picker.setMaxValue(10); picker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mSelectedNumber = newVal; } }); ImageView buttonIcon = (ImageView) findViewById(R.id.safe_slinger_button_image); buttonIcon.setColorFilter(FormattingUtils.getColorFromAttr(this, R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); View button = findViewById(R.id.safe_slinger_button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startExchange(mMasterKeyId, mSelectedNumber); } }); } @Override protected void initLayout() { setContentView(R.layout.safe_slinger_activity); } private void startExchange(long masterKeyId, int number) { // retrieve public key blob and start SafeSlinger Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(masterKeyId); try { byte[] keyBlob = (byte[]) new ProviderHelper(this).getGenericData( uri, KeychainContract.KeyRingData.KEY_RING_DATA, ProviderHelper.FIELD_TYPE_BLOB); Intent slingerIntent = new Intent(this, ExchangeActivity.class); slingerIntent.putExtra(ExchangeConfig.extra.NUM_USERS, number); slingerIntent.putExtra(ExchangeConfig.extra.USER_DATA, keyBlob); slingerIntent.putExtra(ExchangeConfig.extra.HOST_NAME, Constants.SAFESLINGER_SERVER); startActivityForResult(slingerIntent, REQUEST_CODE_SAFE_SLINGER); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "personal key not found", e); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mOperationHelper != null) { mOperationHelper.handleActivityResult(requestCode, resultCode, data); } if (requestCode == REQUEST_CODE_SAFE_SLINGER) { if (resultCode == ExchangeActivity.RESULT_EXCHANGE_CANCELED) { return; } Log.d(Constants.TAG, "importKeys started"); // instead of giving the entries by Intent extra, cache them into a // file to prevent Java Binder problems on heavy imports // read FileImportCache for more info. try { // import exchanged keys ArrayList it = getSlingedKeys(data.getExtras()); // We parcel this iteratively into a file - anything we can // display here, we should be able to import. ParcelableFileCache cache = new ParcelableFileCache<>(this, "key_import.pcl"); cache.writeCache(it.size(), it.iterator()); mOperationHelper = new CryptoOperationHelper(1, this, this, R.string.progress_importing); mKeyList = null; mKeyserver = null; mOperationHelper.cryptoOperation(); } catch (IOException e) { Log.e(Constants.TAG, "Problem writing cache file", e); Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR).show(); } } else { // give everything else down to KeyListActivity! setResult(resultCode, data); finish(); } } private static ArrayList getSlingedKeys(Bundle extras) { ArrayList list = new ArrayList<>(); if (extras != null) { byte[] d; int i = 0; do { d = extras.getByteArray(ExchangeConfig.extra.MEMBER_DATA + i); if (d != null) { list.add(new ParcelableKeyRing(d)); i++; } } while (d != null); } return list; } // CryptoOperationHelper.Callback functions @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { Intent certifyIntent = new Intent(this, CertifyKeyActivity.class); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_RESULT, result); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, result.getImportedMasterKeyIds()); certifyIntent.putExtra(CertifyKeyActivity.EXTRA_CERTIFY_KEY_ID, mMasterKeyId); startActivityForResult(certifyIntent, 0); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(ImportKeyResult result) { Bundle returnData = new Bundle(); returnData.putParcelable(OperationResult.EXTRA_RESULT, result); Intent data = new Intent(); data.putExtras(returnData); setResult(RESULT_OK, data); finish(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13982 Content-Disposition: inline; filename="SecurityTokenOperationActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "4e7c06b69837ab7ca3dd69d1541c750217156526" /* * Copyright (C) 2013-2015 Dominik Schürmann * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit * Copyright (C) 2016 Nikita Mikhailov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.securitytoken.KeyType; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.OrientationUtils; import org.sufficientlysecure.keychain.util.Passphrase; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import nordpol.android.NfcGuideView; /** * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant * NFC devices. * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf */ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; public static final String RESULT_CRYPTO_INPUT = "result_data"; public ViewAnimator vAnimator; public TextView vErrorText; public Button vErrorTryAgainButton; public NfcGuideView nfcGuideView; private RequiredInputParcel mRequiredInput; private CryptoInputParcel mInputParcel; @Override protected void initTheme() { mThemeChanger = new ThemeChanger(this); mThemeChanger.setThemes(R.style.Theme_Keychain_Light_Dialog, R.style.Theme_Keychain_Dark_Dialog); mThemeChanger.changeTheme(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(Constants.TAG, "NfcOperationActivity.onCreate"); nfcGuideView = (NfcGuideView) findViewById(R.id.nfc_guide_view); // prevent annoying orientation changes while fumbling with the device OrientationUtils.lockOrientation(this); // prevent close when touching outside of the dialog (happens easily when fumbling with the device) setFinishOnTouchOutside(false); // keep screen on getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); setTitle(R.string.security_token_nfc_text); vAnimator = (ViewAnimator) findViewById(R.id.view_animator); vAnimator.setDisplayedChild(0); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.STARTING_POSITION); vErrorText = (TextView) findViewById(R.id.security_token_activity_3_error_text); vErrorTryAgainButton = (Button) findViewById(R.id.security_token_activity_3_error_try_again); vErrorTryAgainButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { resumeTagHandling(); obtainPassphraseIfRequired(); vAnimator.setDisplayedChild(0); nfcGuideView.setVisibility(View.VISIBLE); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.STARTING_POSITION); } }); Button vCancel = (Button) findViewById(R.id.security_token_activity_0_cancel); vCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setResult(RESULT_CANCELED); finish(); } }); Intent intent = getIntent(); Bundle data = intent.getExtras(); mRequiredInput = data.getParcelable(EXTRA_REQUIRED_INPUT); obtainPassphraseIfRequired(); } private void obtainPassphraseIfRequired() { // obtain passphrase for this subkey if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); checkPinAvailability(); } else { checkDeviceConnection(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (REQUEST_CODE_PIN == requestCode) { checkPinAvailability(); } } private void checkPinAvailability() { try { Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); if (passphrase != null) { checkDeviceConnection(); } } catch (PassphraseCacheService.KeyNotFoundException e) { throw new AssertionError( "tried to find passphrase for non-existing key. this is a programming error!"); } } @Override protected void initLayout() { setContentView(R.layout.security_token_operation_activity); } @Override public void onSecurityTokenPreExecute() { // start with indeterminate progress vAnimator.setDisplayedChild(1); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING); } @Override protected void doSecurityTokenInBackground() throws IOException { switch (mRequiredInput.mType) { case SECURITY_TOKEN_DECRYPT: { long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( mSecurityTokenHelper.getKeyFingerprint(KeyType.ENCRYPT)); if (tokenKeyId != mRequiredInput.getSubKeyId()) { throw new IOException(getString(R.string.error_wrong_security_token)); } for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; } case SECURITY_TOKEN_SIGN: { long tokenSignKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( mSecurityTokenHelper.getKeyFingerprint(KeyType.SIGN)); long tokenAuthKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( mSecurityTokenHelper.getKeyFingerprint(KeyType.AUTH)); long requiredKey = mRequiredInput.getSubKeyId(); if ((tokenSignKeyId != requiredKey) && (tokenAuthKeyId != requiredKey)) { throw new IOException(getString(R.string.error_wrong_security_token)); } mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime); for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo, requiredKey == tokenAuthKeyId); mInputParcel.addCryptoData(hash, signedHash); } break; } case SECURITY_TOKEN_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation mSecurityTokenHelper.setPin(new Passphrase("123456")); mSecurityTokenHelper.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; try { secretKeyRing = providerHelper.getCanonicalizedSecretKeyRing( KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mRequiredInput.getMasterKeyId()) ); } catch (ProviderHelper.NotFoundException e) { throw new IOException("Couldn't find subkey for key to token operation."); } byte[] newPin = mRequiredInput.mInputData[0]; byte[] newAdminPin = mRequiredInput.mInputData[1]; for (int i = 2; i < mRequiredInput.mInputData.length; i++) { byte[] subkeyBytes = mRequiredInput.mInputData[i]; ByteBuffer buf = ByteBuffer.wrap(subkeyBytes); long subkeyId = buf.getLong(); CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId); byte[] tokenSerialNumber = Arrays.copyOf(mSecurityTokenHelper.getAid(), 16); Passphrase passphrase; try { passphrase = PassphraseCacheService.getCachedPassphrase(this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); } catch (PassphraseCacheService.KeyNotFoundException e) { throw new IOException("Unable to get cached passphrase!"); } mSecurityTokenHelper.changeKey(key, passphrase); // TODO: Is this really used anywhere? mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber); } // change PINs afterwards mSecurityTokenHelper.modifyPin(0x81, newPin); mSecurityTokenHelper.modifyPin(0x83, newAdminPin); break; } case SECURITY_TOKEN_RESET_CARD: { mSecurityTokenHelper.resetAndWipeToken(); break; } default: { throw new AssertionError("Unhandled mRequiredInput.mType"); } } } @Override protected final void onSecurityTokenPostExecute() { handleResult(mInputParcel); // show finish vAnimator.setDisplayedChild(2); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); if (mSecurityTokenHelper.isPersistentConnectionAllowed()) { // Just close finish(); } else { new AsyncTask() { @Override protected Void doInBackground(Void... params) { // check all 200ms if Security Token has been taken away while (true) { if (isSecurityTokenConnected()) { try { Thread.sleep(200); } catch (InterruptedException ignored) { } } else { return null; } } } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); finish(); } }.execute(); } } /** * Defines how the result of this activity is returned. * Is overwritten in RemoteSecurityTokenOperationActivity */ protected void handleResult(CryptoInputParcel inputParcel) { Intent result = new Intent(); // send back the CryptoInputParcel we received result.putExtra(RESULT_CRYPTO_INPUT, inputParcel); setResult(RESULT_OK, result); } @Override protected void onSecurityTokenError(String error) { pauseTagHandling(); vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); vAnimator.setDisplayedChild(3); nfcGuideView.setVisibility(View.GONE); } @Override public void onSecurityTokenPinError(String error) { onSecurityTokenError(error); // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 14674 Content-Disposition: inline; filename="SelectPublicKeyFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "6f48b74555f27a15b655fe6ed131bd540c63d460" /* * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import java.util.Vector; public class SelectPublicKeyFragment extends ListFragmentWorkaround implements TextWatcher, LoaderManager.LoaderCallbacks { public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; private SelectKeyCursorAdapter mAdapter; private EditText mSearchView; private long mSelectedMasterKeyIds[]; private String mQuery; // copied from ListFragment static final int INTERNAL_EMPTY_ID = 0x00ff0001; static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002; static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003; // added for search view static final int SEARCH_ID = 0x00ff0004; /** * Creates new instance of this fragment */ public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) { SelectPublicKeyFragment frag = new SelectPublicKeyFragment(); Bundle args = new Bundle(); args.putLongArray(ARG_PRESELECTED_KEY_IDS, preselectedKeyIds); frag.setArguments(args); return frag; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS); } /** * Copied from ListFragment and added EditText for search on top of list. * We do not use a custom layout here, because this breaks the progress bar functionality * of ListFragment. * * @param inflater * @param container * @param savedInstanceState * @return */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final Context context = getActivity(); FrameLayout root = new FrameLayout(context); // ------------------------------------------------------------------ LinearLayout pframe = new LinearLayout(context); pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID); pframe.setOrientation(LinearLayout.VERTICAL); pframe.setVisibility(View.GONE); pframe.setGravity(Gravity.CENTER); ProgressBar progress = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge); pframe.addView(progress, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); root.addView(pframe, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); // ------------------------------------------------------------------ FrameLayout lframe = new FrameLayout(context); lframe.setId(INTERNAL_LIST_CONTAINER_ID); TextView tv = new TextView(getActivity()); tv.setId(INTERNAL_EMPTY_ID); tv.setGravity(Gravity.CENTER); lframe.addView(tv, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); // Added for search view: linearLayout, mSearchView LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(LinearLayout.VERTICAL); mSearchView = new EditText(context); mSearchView.setId(SEARCH_ID); mSearchView.setHint(R.string.menu_search); mSearchView.setCompoundDrawablesWithIntrinsicBounds( getResources().getDrawable(R.drawable.ic_search_grey_24dp), null, null, null); linearLayout.addView(mSearchView, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); ListView lv = new ListView(getActivity()); lv.setId(android.R.id.list); lv.setDrawSelectorOnTop(false); linearLayout.addView(lv, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); lframe.addView(linearLayout, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); root.addView(lframe, new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); // ------------------------------------------------------------------ root.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); return root; } /** * Define Adapter and Loader on create of Activity */ @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); mSearchView.addTextChangedListener(this); mAdapter = new SelectPublicKeyCursorAdapter(getActivity(), null, 0, getListView()); setListAdapter(mAdapter); // Start out with a progress indicator. setListShown(false); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } /** * Selects items based on master key ids in list view * * @param masterKeyIds */ private void preselectMasterKeyIds(long[] masterKeyIds) { if (masterKeyIds != null) { for (int i = 0; i < getListView().getCount(); ++i) { long keyId = mAdapter.getMasterKeyId(i); for (long masterKeyId : masterKeyIds) { if (keyId == masterKeyId) { getListView().setItemChecked(i, true); break; } } } } } /** * Returns all selected master key ids * * @return */ public long[] getSelectedMasterKeyIds() { // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key // ids! Vector vector = new Vector<>(); for (int i = 0; i < getListView().getCount(); ++i) { if (getListView().isItemChecked(i)) { vector.add(mAdapter.getMasterKeyId(i)); } } // convert to long array long[] selectedMasterKeyIds = new long[vector.size()]; for (int i = 0; i < vector.size(); ++i) { selectedMasterKeyIds[i] = vector.get(i); } return selectedMasterKeyIds; } /** * Returns all selected user ids * * @return */ public String[] getSelectedUserIds() { Vector userIds = new Vector<>(); for (int i = 0; i < getListView().getCount(); ++i) { if (getListView().isItemChecked(i)) { userIds.add(mAdapter.getUserId(i)); } } // make empty array to not return null String userIdArray[] = new String[0]; return userIds.toArray(userIdArray); } @Override public Loader onCreateLoader(int id, Bundle args) { Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); // These are the rows that we will retrieve. String[] projection = new String[]{ KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.USER_ID, KeyRings.IS_EXPIRED, KeyRings.IS_REVOKED, KeyRings.HAS_ENCRYPT, KeyRings.VERIFIED, KeyRings.HAS_DUPLICATE_USER_ID, KeyRings.CREATION, }; String inMasterKeyList = null; if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) { if (i != 0) { inMasterKeyList += ", "; } inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]); } inMasterKeyList += ")"; } String orderBy = KeyRings.USER_ID + " ASC"; if (inMasterKeyList != null) { // sort by selected master keys orderBy = inMasterKeyList + " DESC, " + orderBy; } String where = null; String whereArgs[] = null; if (mQuery != null) { String[] words = mQuery.trim().split("\\s+"); whereArgs = new String[words.length]; for (int i = 0; i < words.length; ++i) { if (where == null) { where = ""; } else { where += " AND "; } where += KeyRings.USER_ID + " LIKE ?"; whereArgs[i] = "%" + words[i] + "%"; } } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), baseUri, projection, where, whereArgs, orderBy); } @Override public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.setSearchQuery(mQuery); mAdapter.swapCursor(data); // The list should now be shown. if (isResumed()) { setListShown(true); } else { setListShownNoAnimation(true); } // preselect given master keys preselectMasterKeyIds(mSelectedMasterKeyIds); } @Override public void onLoaderReset(Loader loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } @Override public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void afterTextChanged(Editable editable) { mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; getLoaderManager().restartLoader(0, null, this); } private class SelectPublicKeyCursorAdapter extends SelectKeyCursorAdapter { private int mIndexHasEncrypt, mIndexIsVerified; public SelectPublicKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { super(context, c, flags, listView); } @Override protected void initIndex(Cursor cursor) { super.initIndex(cursor); if (cursor != null) { mIndexHasEncrypt = cursor.getColumnIndexOrThrow(KeyRings.HAS_ENCRYPT); mIndexIsVerified = cursor.getColumnIndexOrThrow(KeyRings.VERIFIED); } } @Override public void bindView(View view, Context context, Cursor cursor) { super.bindView(view, context, cursor); ViewHolderItem h = (SelectKeyCursorAdapter.ViewHolderItem) view.getTag(); // We care about the checkbox h.selected.setVisibility(View.VISIBLE); // the getListView works because this is not a static subclass! h.selected.setChecked(getListView().isItemChecked(cursor.getPosition())); boolean enabled = false; if((Boolean) h.statusIcon.getTag()) { // Check if key is viable for our purposes if (cursor.getInt(mIndexHasEncrypt) == 0) { h.statusIcon.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNAVAILABLE); enabled = false; } else if (cursor.getInt(mIndexIsVerified) != 0) { h.statusIcon.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.VERIFIED); enabled = true; } else { h.statusIcon.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNVERIFIED); enabled = true; } } h.setEnabled(enabled); } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 25274 Content-Disposition: inline; filename="SettingsActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "894ce7846e50a76a49346e9bad13ec64501fb95c" /* * Copyright (C) 2014-2015 Dominik Schürmann * Copyright (C) 2010-2014 Thialfihar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.Manifest; import android.accounts.Account; import android.accounts.AccountManager; import android.annotation.TargetApi; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.KeychainApplication; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; import org.sufficientlysecure.keychain.service.ContactSyncAdapterService; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.List; public class SettingsActivity extends AppCompatPreferenceActivity { public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; private static final int REQUEST_PERMISSION_READ_CONTACTS = 13; private static Preferences sPreferences; private ThemeChanger mThemeChanger; @Override protected void onCreate(Bundle savedInstanceState) { sPreferences = Preferences.getPreferences(this); mThemeChanger = new ThemeChanger(this); mThemeChanger.setThemes(R.style.Theme_Keychain_Light, R.style.Theme_Keychain_Dark); mThemeChanger.changeTheme(); super.onCreate(savedInstanceState); setupToolbar(); } @Override protected void onResume() { super.onResume(); BaseActivity.onResumeChecks(this); if (mThemeChanger.changeTheme()) { Intent intent = getIntent(); finish(); overridePendingTransition(0, 0); startActivity(intent); overridePendingTransition(0, 0); } } /** * Hack to get Toolbar in PreferenceActivity. See http://stackoverflow.com/a/26614696 */ private void setupToolbar() { ViewGroup root = (ViewGroup) findViewById(android.R.id.content); LinearLayout content = (LinearLayout) root.getChildAt(0); LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar, null); root.removeAllViews(); toolbarContainer.addView(content); root.addView(toolbarContainer); Toolbar toolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar); toolbar.setTitle(R.string.title_preferences); // noinspection deprecation, TODO use alternative in API level 21 toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp)); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //What to do on back clicked finish(); } }); } public static abstract class PresetPreferenceFragment extends PreferenceFragment { @Override public void addPreferencesFromResource(int preferencesResId) { // so that preferences are written to our preference file, not the default Preferences.setPreferenceManagerFileAndMode(this.getPreferenceManager()); super.addPreferencesFromResource(preferencesResId); } } @Override public void onBuildHeaders(List

target) { super.onBuildHeaders(target); loadHeadersFromResource(R.xml.preference_headers, target); } /** * This fragment shows the Cloud Search preferences */ public static class CloudSearchPrefsFragment extends PresetPreferenceFragment { private PreferenceScreen mKeyServerPreference = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.cloud_search_preferences); mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS); mKeyServerPreference.setSummary(keyserverSummary(getActivity())); mKeyServerPreference .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { Intent intent = new Intent(getActivity(), SettingsKeyServerActivity.class); intent.putExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS, sPreferences.getKeyServers()); startActivityForResult(intent, REQUEST_CODE_KEYSERVER_PREF); return false; } }); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_KEYSERVER_PREF: { // update preference, in case it changed mKeyServerPreference.setSummary(keyserverSummary(getActivity())); break; } default: { super.onActivityResult(requestCode, resultCode, data); break; } } } public static String keyserverSummary(Context context) { String[] servers = sPreferences.getKeyServers(); String serverSummary = context.getResources().getQuantityString( R.plurals.n_keyservers, servers.length, servers.length); return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences .getPreferredKeyserver(); } } /** * This fragment shows the PIN/password preferences */ public static class PassphrasePrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.passphrase_preferences); findPreference(Constants.Pref.PASSPHRASE_CACHE_TTLS) .setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { Intent intent = new Intent(getActivity(), SettingsCacheTTLActivity.class); intent.putExtra(SettingsCacheTTLActivity.EXTRA_TTL_PREF, sPreferences.getPassphraseCacheTtl()); startActivity(intent); return false; } }); } } public static class ProxyPrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new Initializer(this).initialize(); } public static class Initializer { private SwitchPreference mUseTor; private SwitchPreference mUseNormalProxy; private EditTextPreference mProxyHost; private EditTextPreference mProxyPort; private ListPreference mProxyType; private PresetPreferenceFragment mFragment; public Initializer(PresetPreferenceFragment fragment) { mFragment = fragment; } public Preference automaticallyFindPreference(String key) { return mFragment.findPreference(key); } public void initialize() { mFragment.addPreferencesFromResource(R.xml.proxy_preferences); mUseTor = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); mUseNormalProxy = (SwitchPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY); mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST); mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT); mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE); initializeUseTorPref(); initializeUseNormalProxyPref(); initializeEditTextPreferences(); initializeProxyTypePreference(); if (mUseTor.isChecked()) { disableNormalProxyPrefs(); } else if (mUseNormalProxy.isChecked()) { disableUseTorPrefs(); } else { disableNormalProxySettings(); } } private void initializeUseTorPref() { mUseTor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { Activity activity = mFragment.getActivity(); if ((Boolean) newValue) { boolean installed = OrbotHelper.isOrbotInstalled(activity); if (!installed) { Log.d(Constants.TAG, "Prompting to install Tor"); OrbotHelper.getPreferenceInstallDialogFragment().show(activity.getFragmentManager(), "installDialog"); // don't let the user check the box until he's installed orbot return false; } else { disableNormalProxyPrefs(); // let the enable tor box be checked return true; } } else { // we're unchecking Tor, so enable other proxy enableNormalProxyCheckbox(); return true; } } }); } private void initializeUseNormalProxyPref() { mUseNormalProxy.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if ((Boolean) newValue) { disableUseTorPrefs(); enableNormalProxySettings(); } else { enableUseTorPrefs(); disableNormalProxySettings(); } return true; } }); } private void initializeEditTextPreferences() { mProxyHost.setSummary(mProxyHost.getText()); mProxyPort.setSummary(mProxyPort.getText()); mProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { Activity activity = mFragment.getActivity(); if (TextUtils.isEmpty((String) newValue)) { Notify.create( activity, R.string.pref_proxy_host_err_invalid, Notify.Style.ERROR ).show(); return false; } else { mProxyHost.setSummary((CharSequence) newValue); return true; } } }); mProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { Activity activity = mFragment.getActivity(); try { int port = Integer.parseInt((String) newValue); if (port < 0 || port > 65535) { Notify.create( activity, R.string.pref_proxy_port_err_invalid, Notify.Style.ERROR ).show(); return false; } // no issues, save port mProxyPort.setSummary("" + port); return true; } catch (NumberFormatException e) { Notify.create( activity, R.string.pref_proxy_port_err_invalid, Notify.Style.ERROR ).show(); return false; } } }); } private void initializeProxyTypePreference() { mProxyType.setSummary(mProxyType.getEntry()); mProxyType.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { CharSequence entry = mProxyType.getEntries()[mProxyType.findIndexOfValue((String) newValue)]; mProxyType.setSummary(entry); return true; } }); } private void disableNormalProxyPrefs() { mUseNormalProxy.setChecked(false); mUseNormalProxy.setEnabled(false); disableNormalProxySettings(); } private void enableNormalProxyCheckbox() { mUseNormalProxy.setEnabled(true); } private void enableNormalProxySettings() { mProxyHost.setEnabled(true); mProxyPort.setEnabled(true); mProxyType.setEnabled(true); } private void disableNormalProxySettings() { mProxyHost.setEnabled(false); mProxyPort.setEnabled(false); mProxyType.setEnabled(false); } private void disableUseTorPrefs() { mUseTor.setChecked(false); mUseTor.setEnabled(false); } private void enableUseTorPrefs() { mUseTor.setEnabled(true); } } } /** * This fragment shows the keyserver/wifi-only-sync/contacts sync preferences */ public static class SyncPrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.sync_preferences); } @Override public void onResume() { super.onResume(); // this needs to be done in onResume since the user can change sync values from Android // settings and we need to reflect that change when the user navigates back final Account account = KeychainApplication.createAccountIfNecessary(getActivity()); // for keyserver sync initializeSyncCheckBox( (SwitchPreference) findPreference(Constants.Pref.SYNC_KEYSERVER), account, Constants.PROVIDER_AUTHORITY ); // for contacts sync initializeSyncCheckBox( (SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS), account, ContactsContract.AUTHORITY ); } private void initializeSyncCheckBox(final SwitchPreference syncCheckBox, final Account account, final String authority) { // account is null if it could not be created for some reason boolean syncEnabled = account != null && ContentResolver.getSyncAutomatically(account, authority) && checkContactsPermission(authority); syncCheckBox.setChecked(syncEnabled); setSummary(syncCheckBox, authority, syncEnabled); syncCheckBox.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @TargetApi(Build.VERSION_CODES.M) @Override public boolean onPreferenceChange(Preference preference, Object newValue) { boolean syncEnabled = (Boolean) newValue; if (syncEnabled) { if (checkContactsPermission(authority)) { ContentResolver.setSyncAutomatically(account, authority, true); setSummary(syncCheckBox, authority, true); return true; } else { requestPermissions( new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_PERMISSION_READ_CONTACTS); // don't update preference return false; } } else { if (account == null) { // if account could not be created for some reason, // we can't have our sync return false; } // disable syncs ContentResolver.setSyncAutomatically(account, authority, false); // immediately delete any linked contacts ContactSyncAdapterService.deleteIfSyncDisabled(getActivity()); // cancel any ongoing/pending syncs ContentResolver.cancelSync(account, authority); setSummary(syncCheckBox, authority, false); return true; } } }); } private boolean checkContactsPermission(String authority) { if (!ContactsContract.AUTHORITY.equals(authority)) { // provides convenience of not using separate checks for keyserver and contact sync // in initializeSyncCheckBox return true; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { return true; } return false; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode != REQUEST_PERMISSION_READ_CONTACTS) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } boolean permissionWasGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; if (permissionWasGranted) { // permission granted -> enable contact linking AccountManager manager = AccountManager.get(getActivity()); final Account account = manager.getAccountsByType(Constants.ACCOUNT_TYPE)[0]; SwitchPreference pref = (SwitchPreference) findPreference(Constants.Pref.SYNC_CONTACTS); ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); setSummary(pref, ContactsContract.AUTHORITY, true); pref.setChecked(true); } } private void setSummary(SwitchPreference syncCheckBox, String authority, boolean checked) { switch (authority) { case Constants.PROVIDER_AUTHORITY: { if (checked) { syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_on); } else { syncCheckBox.setSummary(R.string.label_sync_settings_keyserver_summary_off); } break; } case ContactsContract.AUTHORITY: { if (checked) { syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_on); } else { syncCheckBox.setSummary(R.string.label_sync_settings_contacts_summary_off); } break; } } } } /** * This fragment shows experimental features */ public static class ExperimentalPrefsFragment extends PresetPreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.experimental_preferences); initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); } private static void initializeTheme(final ListPreference themePref) { themePref.setSummary(themePref.getEntry() + "\n" + themePref.getContext().getString(R.string.label_experimental_settings_theme_summary)); themePref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { public boolean onPreferenceChange(Preference preference, Object newValue) { themePref.setSummary(newValue + "\n" + themePref.getContext().getString(R.string.label_experimental_settings_theme_summary)); ((SettingsActivity) themePref.getContext()).recreate(); return true; } }); } } protected boolean isValidFragment(String fragmentName) { return PassphrasePrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) || ProxyPrefsFragment.class.getName().equals(fragmentName) || SyncPrefsFragment.class.getName().equals(fragmentName) || ExperimentalPrefsFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2858 Content-Disposition: inline; filename="SettingsCacheTTLActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "6c3d0fd1ceeb4f3b73bde928f25de94c312e2f59" /* * Copyright (C) 2015 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import android.content.Intent; import android.os.Bundle; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.util.Preferences.CacheTTLPrefs; public class SettingsCacheTTLActivity extends BaseActivity { public static final String EXTRA_TTL_PREF = "ttl_pref"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); CacheTTLPrefs ttlPrefs = (CacheTTLPrefs) intent.getSerializableExtra(EXTRA_TTL_PREF); loadFragment(savedInstanceState, ttlPrefs); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); return true; } return super.onOptionsItemSelected(item); } @Override protected void initLayout() { setContentView(R.layout.settings_cache_ttl); } private void loadFragment(Bundle savedInstanceState, CacheTTLPrefs ttlPrefs) { // 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. if (savedInstanceState != null) { return; } SettingsCacheTTLFragment fragment = SettingsCacheTTLFragment.newInstance(ttlPrefs); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() .replace(R.id.settings_cache_ttl_fragment, fragment) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7104 Content-Disposition: inline; filename="SettingsCacheTTLFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "b811d51b53642a47b21cbe46b5a6f3832c1de471" /* * Copyright (C) 2015 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences.CacheTTLPrefs; public class SettingsCacheTTLFragment extends Fragment { public static final String ARG_TTL_PREFS = "ttl_prefs"; private CacheTTLListAdapter mAdapter; public static SettingsCacheTTLFragment newInstance(CacheTTLPrefs ttlPrefs) { Bundle args = new Bundle(); args.putSerializable(ARG_TTL_PREFS, ttlPrefs); SettingsCacheTTLFragment fragment = new SettingsCacheTTLFragment(); fragment.setArguments(args); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.settings_cache_ttl_fragment, null); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); CacheTTLPrefs prefs = (CacheTTLPrefs) getArguments().getSerializable(ARG_TTL_PREFS); mAdapter = new CacheTTLListAdapter(prefs); RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.cache_ttl_recycler_view); recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(mAdapter); recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); } private void savePreference() { FragmentActivity activity = getActivity(); if (activity == null) { return; } CacheTTLPrefs prefs = mAdapter.getPrefs(); Preferences.getPreferences(activity).setPassphraseCacheTtl(prefs); } public class CacheTTLListAdapter extends RecyclerView.Adapter { private final ArrayList mPositionIsChecked; public CacheTTLListAdapter(CacheTTLPrefs prefs) { this.mPositionIsChecked = new ArrayList<>(); for (int ttlTime : CacheTTLPrefs.CACHE_TTLS) { mPositionIsChecked.add(prefs.ttlTimes.contains(ttlTime)); } } public CacheTTLPrefs getPrefs() { ArrayList ttls = new ArrayList<>(); for (int i = 0; i < mPositionIsChecked.size(); i++) { if (mPositionIsChecked.get(i)) { ttls.add(Integer.toString(CacheTTLPrefs.CACHE_TTLS.get(i))); } } return new CacheTTLPrefs(ttls); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.settings_cache_ttl_item, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(final ViewHolder holder, int position) { holder.bind(position); } @Override public int getItemCount() { return mPositionIsChecked.size(); } public class ViewHolder extends RecyclerView.ViewHolder { CheckBox mChecked; TextView mTitle; public ViewHolder(View itemView) { super(itemView); mChecked = (CheckBox) itemView.findViewById(R.id.ttl_selected); mTitle = (TextView) itemView.findViewById(R.id.ttl_title); itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mChecked.performClick(); } }); } public void bind(final int position) { int ttl = CacheTTLPrefs.CACHE_TTLS.get(position); boolean isChecked = mPositionIsChecked.get(position); mTitle.setText(CacheTTLPrefs.CACHE_TTL_NAMES.get(ttl)); // avoid some ui flicker by skipping unnecessary updates if (mChecked.isChecked() != isChecked) { mChecked.setChecked(isChecked); } mChecked.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { setTtlChecked(position); savePreference(); } }); } private void setTtlChecked(int position) { boolean isChecked = mPositionIsChecked.get(position); int checkedItems = countCheckedItems(); boolean isLastChecked = isChecked && checkedItems == 1; boolean isOneTooMany = !isChecked && checkedItems >= 3; if (isLastChecked) { Notify.create(getActivity(), R.string.settings_cache_ttl_at_least_one, Style.ERROR).show(); } else if (isOneTooMany) { Notify.create(getActivity(), R.string.settings_cache_ttl_max_three, Style.ERROR).show(); } else { mPositionIsChecked.set(position, !isChecked); } notifyItemChanged(position); } private int countCheckedItems() { int result = 0; for (boolean isChecked : mPositionIsChecked) { if (isChecked) { result += 1; } } return result; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2676 Content-Disposition: inline; filename="SettingsKeyServerActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "7dd92c45f6701d307d25f588591f1466f0014928" /* * Copyright (C) 2010-2014 Thialfihar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.os.Bundle; import android.view.MenuItem; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; public class SettingsKeyServerActivity extends BaseActivity { public static final String EXTRA_KEY_SERVERS = "key_servers"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS); loadFragment(savedInstanceState, servers); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); return true; } return super.onOptionsItemSelected(item); } @Override protected void initLayout() { setContentView(R.layout.key_server_preference); } private void loadFragment(Bundle savedInstanceState, String[] keyservers) { // 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. if (savedInstanceState != null) { return; } SettingsKeyserverFragment fragment = SettingsKeyserverFragment.newInstance(keyservers); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() .replace(R.id.keyserver_settings_fragment_container, fragment) .commitAllowingStateLoss(); // do it immediately! getSupportFragmentManager().executePendingTransactions(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13796 Content-Disposition: inline; filename="SettingsKeyserverFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "488558aa3dd7a807df4dd1a4730a8569e1223d5f" /* * Copyright (C) 2012-2015 Dominik Schürmann * Copyright (C) 2015 Adithya Abraham Philip * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.Fragment; import android.support.v4.view.MotionEventCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; 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; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class SettingsKeyserverFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener { private static final String ARG_KEYSERVER_ARRAY = "arg_keyserver_array"; private ItemTouchHelper mItemTouchHelper; private ArrayList mKeyservers; private KeyserverListAdapter mAdapter; public static SettingsKeyserverFragment newInstance(String[] keyservers) { Bundle args = new Bundle(); args.putStringArray(ARG_KEYSERVER_ARRAY, keyservers); SettingsKeyserverFragment fragment = new SettingsKeyserverFragment(); fragment.setArguments(args); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.settings_keyserver_fragment, null); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); String keyservers[] = getArguments().getStringArray(ARG_KEYSERVER_ARRAY); mKeyservers = new ArrayList<>(Arrays.asList(keyservers)); mAdapter = new KeyserverListAdapter(mKeyservers); RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.keyserver_recycler_view); // recyclerView.setHasFixedSize(true); // the size of the first item changes recyclerView.setAdapter(mAdapter); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter); mItemTouchHelper = new ItemTouchHelper(callback); mItemTouchHelper.attachToRecyclerView(recyclerView); // for clicks recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this)); // can't use item decoration because it doesn't move with drag and drop // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null)); // We have a menu item to show in action bar. setHasOptionsMenu(true); } @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.keyserver_pref_menu, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_add_keyserver: startAddKeyserverDialog(); return true; default: return super.onOptionsItemSelected(item); } } private void startAddKeyserverDialog() { // keyserver and position have no meaning startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.ADD, null, -1); } private void startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction action, String keyserver, final int position) { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { Bundle data = message.getData(); switch (message.what) { case AddEditKeyserverDialogFragment.MESSAGE_OKAY: { boolean deleted = data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER_DELETED , false); if (deleted) { Notify.create(getActivity(), getActivity().getString( R.string.keyserver_preference_deleted, mKeyservers.get(position)), Notify.Style.OK) .show(); deleteKeyserver(position); return; } boolean verified = data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED); if (verified) { Notify.create(getActivity(), R.string.add_keyserver_connection_verified, Notify.Style.OK).show(); } else { Notify.create(getActivity(), R.string.add_keyserver_without_verification, Notify.Style.WARN).show(); } String keyserver = data.getString( AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER); AddEditKeyserverDialogFragment.DialogAction dialogAction = (AddEditKeyserverDialogFragment.DialogAction) data.getSerializable( AddEditKeyserverDialogFragment.MESSAGE_DIALOG_ACTION); switch (dialogAction) { case ADD: addKeyserver(keyserver); break; case EDIT: editKeyserver(keyserver, position); break; } break; } } } }; // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); AddEditKeyserverDialogFragment dialogFragment = AddEditKeyserverDialogFragment .newInstance(messenger, action, keyserver, position); dialogFragment.show(getFragmentManager(), "addKeyserverDialog"); } private void addKeyserver(String keyserver) { mKeyservers.add(keyserver); mAdapter.notifyItemInserted(mKeyservers.size() - 1); saveKeyserverList(); } private void editKeyserver(String newKeyserver, int position) { mKeyservers.set(position, newKeyserver); mAdapter.notifyItemChanged(position); saveKeyserverList(); } private void deleteKeyserver(int position) { if (mKeyservers.size() == 1) { Notify.create(getActivity(), R.string.keyserver_preference_cannot_delete_last, Notify.Style.ERROR).show(); return; } mKeyservers.remove(position); // we use this mAdapter.notifyItemRemoved(position); if (position == 0 && mKeyservers.size() > 0) { // if we deleted the first item, we need the adapter to redraw the new first item mAdapter.notifyItemChanged(0); } saveKeyserverList(); } private void saveKeyserverList() { String servers[] = mKeyservers.toArray(new String[mKeyservers.size()]); Preferences.getPreferences(getActivity()).setKeyServers(servers); } @Override public void onItemClick(View view, int position) { startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.EDIT, mKeyservers.get(position), position); } public class KeyserverListAdapter extends RecyclerView.Adapter implements ItemTouchHelperAdapter { private final List mKeyservers; public KeyserverListAdapter(List keyservers) { mKeyservers = keyservers; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.settings_keyserver_item, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(final ViewHolder holder, int position) { holder.keyserverUrl.setText(mKeyservers.get(position)); // Start a drag whenever the handle view it touched holder.dragHandleView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { mItemTouchHelper.startDrag(holder); } return false; } }); selectUnselectKeyserver(holder, position); } private void selectUnselectKeyserver(ViewHolder holder, int position) { if (position == 0) { holder.showAsSelectedKeyserver(); } else { holder.showAsUnselectedKeyserver(); } } @Override public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target, int fromPosition, int toPosition) { Collections.swap(mKeyservers, fromPosition, toPosition); saveKeyserverList(); selectUnselectKeyserver((ViewHolder) target, fromPosition); // we don't want source to change color while dragging, therefore we just set // isSelectedKeyserver instead of selectUnselectKeyserver ((ViewHolder) source).isSelectedKeyserver = toPosition == 0; notifyItemMoved(fromPosition, toPosition); } @Override public int getItemCount() { return mKeyservers.size(); } public class ViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder { public final ViewGroup outerLayout; public final TextView selectedServerLabel; public final TextView keyserverUrl; public final ImageView dragHandleView; private boolean isSelectedKeyserver = false; public ViewHolder(View itemView) { super(itemView); outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout); selectedServerLabel = (TextView) itemView.findViewById( R.id.selected_keyserver_title); keyserverUrl = (TextView) itemView.findViewById(R.id.keyserver_tv); dragHandleView = (ImageView) itemView.findViewById(R.id.drag_handle); itemView.setClickable(true); } public void showAsSelectedKeyserver() { isSelectedKeyserver = true; selectedServerLabel.setVisibility(View.VISIBLE); outerLayout.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorPrimaryDark)); } public void showAsUnselectedKeyserver() { isSelectedKeyserver = false; selectedServerLabel.setVisibility(View.GONE); outerLayout.setBackgroundColor(0); } @Override public void onItemSelected() { selectedServerLabel.setVisibility(View.GONE); itemView.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorBrightToolbar)); } @Override public void onItemClear() { if (isSelectedKeyserver) { showAsSelectedKeyserver(); } else { showAsUnselectedKeyserver(); } } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 5237 Content-Disposition: inline; filename="UploadKeyActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "306b022c1990faf70e61bb716b0ed8c795663297" /* * Copyright (C) 2012-2014 Dominik Schürmann * Copyright (C) 2011 Senecaso * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.NavUtils; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Spinner; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.UploadResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.UploadKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; /** * Sends the selected public key to a keyserver */ public class UploadKeyActivity extends BaseActivity implements CryptoOperationHelper.Callback { private View mUploadButton; private Spinner mKeyServerSpinner; private Uri mDataUri; // CryptoOperationHelper.Callback vars private String mKeyserver; private CryptoOperationHelper mUploadOpHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mUploadButton = findViewById(R.id.upload_key_action_upload); mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver); MultiUserIdsFragment mMultiUserIdsFragment = (MultiUserIdsFragment) getSupportFragmentManager().findFragmentById(R.id.multi_user_ids_fragment); mMultiUserIdsFragment.setCheckboxVisibility(false); ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, Preferences.getPreferences(this) .getKeyServers() ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mKeyServerSpinner.setAdapter(adapter); if (adapter.getCount() > 0) { mKeyServerSpinner.setSelection(0); } else { mUploadButton.setEnabled(false); } mUploadButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { uploadKey(); } }); mDataUri = getIntent().getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); finish(); return; } } @Override protected void initLayout() { setContentView(R.layout.upload_key_activity); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (mUploadOpHelper != null) { mUploadOpHelper.handleActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } private void uploadKey() { String server = (String) mKeyServerSpinner.getSelectedItem(); mKeyserver = server; mUploadOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: { Intent viewIntent = NavUtils.getParentActivityIntent(this); viewIntent.setData(KeychainContract.KeyRings.buildGenericKeyRingUri(mDataUri)); NavUtils.navigateUpTo(this, viewIntent); return true; } } return super.onOptionsItemSelected(item); } @Override public UploadKeyringParcel createOperationInput() { long[] masterKeyIds = getIntent().getLongArrayExtra(MultiUserIdsFragment.EXTRA_KEY_IDS); return new UploadKeyringParcel(mKeyserver, masterKeyIds[0]); } @Override public void onCryptoOperationSuccess(UploadResult result) { result.createNotify(this).show(); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(UploadResult result) { result.createNotify(this).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 2141 Content-Disposition: inline; filename="UsbEventReceiverActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "05b30b1ae7fb9237a020cfd75fed5c6da61b0717" /* * Copyright (C) 2016 Nikita Mikhailov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.os.Bundle; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; public class UsbEventReceiverActivity extends Activity { public static final String ACTION_USB_PERMISSION = "org.sufficientlysecure.keychain.ui.USB_PERMISSION"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected void onResume() { super.onResume(); final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); Intent intent = getIntent(); if (intent != null) { if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) { UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); Log.d(Constants.TAG, "Requesting permission for " + usbDevice.getDeviceName()); usbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0)); } } // Close the activity finish(); } }X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 8681 Content-Disposition: inline; filename="ViewCertActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "596b223fe3f05042a065f45982c2612ae76e2e1e" /* * Copyright (C) 2013-2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.app.NavUtils; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.app.ActionBar; import android.text.TextUtils; import android.text.format.DateFormat; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import java.util.Date; public class ViewCertActivity extends BaseActivity implements LoaderManager.LoaderCallbacks { // These are the rows that we will retrieve. static final String[] PROJECTION = new String[]{ Certs.MASTER_KEY_ID, Certs.USER_ID, Certs.TYPE, Certs.CREATION, Certs.KEY_ID_CERTIFIER, Certs.SIGNER_UID, Certs.DATA, }; private static final int INDEX_MASTER_KEY_ID = 0; private static final int INDEX_USER_ID = 1; private static final int INDEX_TYPE = 2; private static final int INDEX_CREATION = 3; private static final int INDEX_KEY_ID_CERTIFIER = 4; private static final int INDEX_SIGNER_UID = 5; private static final int INDEX_DATA = 6; private Uri mDataUri; private long mCertifierKeyId; private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mReason, mCreation; private TextView mCertifierKey, mCertifierUid, mStatus; private View mRowReason; private View mViewCertifierButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); mSigneeKey = (TextView) findViewById(R.id.signee_key); mSigneeUid = (TextView) findViewById(R.id.signee_uid); mAlgorithm = (TextView) findViewById(R.id.algorithm); mType = (TextView) findViewById(R.id.signature_type); mReason = (TextView) findViewById(R.id.reason); mCreation = (TextView) findViewById(R.id.creation); mCertifierKey = (TextView) findViewById(R.id.signer_key_id); mCertifierUid = (TextView) findViewById(R.id.signer_uid); mRowReason = findViewById(R.id.row_reason); mViewCertifierButton = findViewById(R.id.view_cert_view_cert_key); mDataUri = getIntent().getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!"); finish(); return; } getSupportLoaderManager().initLoader(0, null, this); } @Override protected void initLayout() { setContentView(R.layout.view_cert_activity); } @Override public Loader onCreateLoader(int id, Bundle args) { // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(this, mDataUri, PROJECTION, null, null, null); } @Override public void onLoadFinished(Loader loader, Cursor data) { if (data.moveToFirst()) { mSigneeKey.setText(KeyFormattingUtils.beautifyKeyId(data.getLong(INDEX_MASTER_KEY_ID))); String signeeUid = data.getString(INDEX_USER_ID); mSigneeUid.setText(signeeUid); Date creationDate = new Date(data.getLong(INDEX_CREATION) * 1000); mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(creationDate)); mCertifierKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER); mCertifierKey.setText(KeyFormattingUtils.beautifyKeyId(mCertifierKeyId)); String certifierUid = data.getString(INDEX_SIGNER_UID); if (certifierUid != null) { mCertifierUid.setText(certifierUid); } else { mCertifierUid.setText(R.string.unknown_uid); } WrappedSignature sig = WrappedSignature.fromBytes(data.getBlob(INDEX_DATA)); String algorithmStr = KeyFormattingUtils.getAlgorithmInfo(this, sig.getKeyAlgorithm(), null, null); mAlgorithm.setText(algorithmStr); mRowReason.setVisibility(View.GONE); switch (data.getInt(INDEX_TYPE)) { case WrappedSignature.DEFAULT_CERTIFICATION: mType.setText(R.string.cert_default); break; case WrappedSignature.NO_CERTIFICATION: mType.setText(R.string.cert_none); break; case WrappedSignature.CASUAL_CERTIFICATION: mType.setText(R.string.cert_casual); break; case WrappedSignature.POSITIVE_CERTIFICATION: mType.setText(R.string.cert_positive); break; case WrappedSignature.CERTIFICATION_REVOCATION: { mType.setText(R.string.cert_revoke); try { if (! TextUtils.isEmpty(sig.getRevocationReason())) { mReason.setText(sig.getRevocationReason()); } else { mReason.setText(R.string.none); } } catch (PgpGeneralException e) { mReason.setText(R.string.none); } mRowReason.setVisibility(View.VISIBLE); break; } } } // can't do this before the data is initialized mViewCertifierButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent viewIntent = new Intent(ViewCertActivity.this, ViewKeyActivity.class); try { ProviderHelper providerHelper = new ProviderHelper(ViewCertActivity.this); long signerMasterKeyId = providerHelper.getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(mCertifierKeyId)).getMasterKeyId(); viewIntent.setData(KeyRings.buildGenericKeyRingUri(signerMasterKeyId)); startActivity(viewIntent); } catch (PgpKeyNotFoundException e) { // TODO notify user of this, maybe offer download? Log.e(Constants.TAG, "key not found!", e); } } }); } @Override public void onLoaderReset(Loader loader) { } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: { Intent viewIntent = NavUtils.getParentActivityIntent(this); viewIntent.setData(KeyRings.buildGenericKeyRingUri(mDataUri)); NavUtils.navigateUpTo(this, viewIntent); return true; } } return super.onOptionsItemSelected(item); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 48464 Content-Disposition: inline; filename="ViewKeyActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "4dceb94f07911110fcfc8d325515a449540467e8" /* * Copyright (C) 2013-2014 Dominik Schürmann * Copyright (C) 2013 Bahtiar 'kalkin' Gadimov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.net.Uri; import android.nfc.NfcAdapter; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.provider.ContactsContract; import android.support.annotation.IntDef; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.widget.CardView; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; public class ViewKeyActivity extends BaseSecurityTokenActivity implements LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { public static final String EXTRA_SECURITY_TOKEN_USER_ID = "security_token_user_id"; public static final String EXTRA_SECURITY_TOKEN_AID = "security_token_aid"; public static final String EXTRA_SECURITY_TOKEN_FINGERPRINTS = "security_token_fingerprints"; @Retention(RetentionPolicy.SOURCE) @IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE}) private @interface RequestType {} static final int REQUEST_QR_FINGERPRINT = 1; static final int REQUEST_BACKUP = 2; static final int REQUEST_CERTIFY = 3; static final int REQUEST_DELETE = 4; public static final String EXTRA_DISPLAY_RESULT = "display_result"; public static final String EXTRA_LINKED_TRANSITION = "linked_transition"; ProviderHelper mProviderHelper; protected Uri mDataUri; // For CryptoOperationHelper.Callback private String mKeyserver; private ArrayList mKeyList; private CryptoOperationHelper mImportOpHelper; private CryptoOperationHelper mEditOpHelper; private ChangeUnlockParcel mChangeUnlockParcel; private TextView mStatusText; private ImageView mStatusImage; private AppBarLayout mAppBarLayout; private CollapsingToolbarLayout mCollapsingToolbarLayout; private ImageButton mActionEncryptFile; private ImageButton mActionEncryptText; private ImageButton mActionNfc; private FloatingActionButton mFab; private ImageView mPhoto; private FrameLayout mPhotoLayout; private ImageView mQrCode; private CardView mQrCodeLayout; private String mQrCodeLoaded; // NFC private NfcHelper mNfcHelper; private static final int LOADER_ID_UNIFIED = 0; private boolean mIsSecret = false; private boolean mHasEncrypt = false; private boolean mIsVerified = false; private boolean mIsRevoked = false; private boolean mIsExpired = false; private boolean mShowSecurityTokenAfterCreation = false; private MenuItem mRefreshItem; private boolean mIsRefreshing; private Animation mRotate, mRotateSpin; private View mRefresh; private long mMasterKeyId; private byte[] mFingerprint; private String mFingerprintString; private byte[] mSecurityTokenFingerprints; private String mSecurityTokenUserId; private byte[] mSecurityTokenAid; @SuppressLint("InflateParams") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mProviderHelper = new ProviderHelper(this); mImportOpHelper = new CryptoOperationHelper<>(1, this, this, null); setTitle(null); mStatusText = (TextView) findViewById(R.id.view_key_status); mStatusImage = (ImageView) findViewById(R.id.view_key_status_image); mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout); mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files); mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text); mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc); mFab = (FloatingActionButton) findViewById(R.id.fab); mPhoto = (ImageView) findViewById(R.id.view_key_photo); mPhotoLayout = (FrameLayout) findViewById(R.id.view_key_photo_layout); mQrCode = (ImageView) findViewById(R.id.view_key_qr_code); mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout); mRotateSpin = AnimationUtils.loadAnimation(this, R.anim.rotate_spin); //ContentDescriptionHint Listeners implemented ContentDescriptionHint.setup(mActionEncryptFile); ContentDescriptionHint.setup(mActionEncryptText); ContentDescriptionHint.setup(mActionNfc); ContentDescriptionHint.setup(mFab); mRotateSpin.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mRefreshItem.getActionView().clearAnimation(); mRefreshItem.setActionView(null); mRefreshItem.setEnabled(true); // this is a deferred call supportInvalidateOptionsMenu(); } @Override public void onAnimationRepeat(Animation animation) { } }); mRotate = AnimationUtils.loadAnimation(this, R.anim.rotate); mRotate.setRepeatCount(Animation.INFINITE); mRotate.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { if (!mIsRefreshing) { mRefreshItem.getActionView().clearAnimation(); mRefreshItem.getActionView().startAnimation(mRotateSpin); } } }); mRefresh = getLayoutInflater().inflate(R.layout.indeterminate_progress, null); mDataUri = getIntent().getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Data missing. Should be uri of key!"); finish(); return; } if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) { mDataUri = new ContactHelper(this).dataUriFromContactUri(mDataUri); if (mDataUri == null) { Log.e(Constants.TAG, "Contact Data missing. Should be uri of key!"); Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show(); finish(); return; } } Log.i(Constants.TAG, "mDataUri: " + mDataUri); mActionEncryptFile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { encrypt(mDataUri, false); } }); mActionEncryptText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { encrypt(mDataUri, true); } }); mFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mIsSecret) { startSafeSlinger(mDataUri); } else { scanQrCode(); } } }); mQrCodeLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showQrCodeDialog(); } }); mActionNfc.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mNfcHelper.invokeNfcBeam(); } }); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); mNfcHelper = new NfcHelper(this, mProviderHelper); mNfcHelper.initNfc(mDataUri); if (savedInstanceState == null && getIntent().hasExtra(EXTRA_DISPLAY_RESULT)) { OperationResult result = getIntent().getParcelableExtra(EXTRA_DISPLAY_RESULT); result.createNotify(this).show(); } // Fragments are stored, no need to recreate those if (savedInstanceState != null) { return; } boolean linkedTransition = getIntent().getBooleanExtra(EXTRA_LINKED_TRANSITION, false); if (linkedTransition && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { postponeEnterTransition(); } FragmentManager manager = getSupportFragmentManager(); // Create an instance of the fragment final ViewKeyFragment frag = ViewKeyFragment.newInstance(mDataUri, linkedTransition ? PostponeType.LINKED : PostponeType.NONE); manager.beginTransaction() .replace(R.id.view_key_fragment, frag) .commit(); if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) { final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(mDataUri); manager.beginTransaction() .replace(R.id.view_key_keybase_fragment, keybaseFrag) .commit(); } // need to postpone loading of the security token fragment until after mMasterKeyId // is available, but we mark here that this should be done mShowSecurityTokenAfterCreation = true; } @Override protected void initLayout() { setContentView(R.layout.view_key_activity); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.key_view, menu); mRefreshItem = menu.findItem(R.id.menu_key_view_refresh); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: { Intent homeIntent = new Intent(this, MainActivity.class); homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(homeIntent); return true; } case R.id.menu_key_change_password: { changePassword(); return true; } case R.id.menu_key_view_backup: { startPassphraseActivity(REQUEST_BACKUP); return true; } case R.id.menu_key_view_delete: { deleteKey(); return true; } case R.id.menu_key_view_advanced: { Intent advancedIntent = new Intent(this, ViewKeyAdvActivity.class); advancedIntent.setData(mDataUri); startActivity(advancedIntent); return true; } case R.id.menu_key_view_refresh: { try { updateFromKeyserver(mDataUri, mProviderHelper); } catch (ProviderHelper.NotFoundException e) { Notify.create(this, R.string.error_key_not_found, Notify.Style.ERROR).show(); } return true; } case R.id.menu_key_view_certify_fingerprint: { certifyFingerprint(mDataUri, false); return true; } case R.id.menu_key_view_certify_fingerprint_word: { certifyFingerprint(mDataUri, true); return true; } } return super.onOptionsItemSelected(item); } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem backupKey = menu.findItem(R.id.menu_key_view_backup); backupKey.setVisible(mIsSecret); MenuItem changePassword = menu.findItem(R.id.menu_key_change_password); changePassword.setVisible(mIsSecret); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); MenuItem certifyFingerprintWord = menu.findItem(R.id.menu_key_view_certify_fingerprint_word); certifyFingerprintWord.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked && Preferences.getPreferences(this).getExperimentalEnableWordConfirm()); return true; } private void changePassword() { CryptoOperationHelper.Callback editKeyCallback = new CryptoOperationHelper.Callback() { @Override public ChangeUnlockParcel createOperationInput() { return mChangeUnlockParcel; } @Override public void onCryptoOperationSuccess(EditKeyResult result) { displayResult(result); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(EditKeyResult result) { displayResult(result); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mEditOpHelper = new CryptoOperationHelper<>(2, this, editKeyCallback, R.string.progress_building_key); // Message is received after passphrase is cached Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); // use new passphrase! mChangeUnlockParcel = new ChangeUnlockParcel( mMasterKeyId, mFingerprint, (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); mEditOpHelper.cryptoOperation(); } } }; // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance( messenger, R.string.title_change_passphrase); setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog"); } private void displayResult(OperationResult result) { result.createNotify(this).show(); } private void scanQrCode() { Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class); scanQrCode.setAction(ImportKeysProxyActivity.ACTION_SCAN_WITH_RESULT); startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT); } private void certifyFingerprint(Uri dataUri, boolean enableWordConfirm) { Intent intent = new Intent(this, CertifyFingerprintActivity.class); intent.setData(dataUri); intent.putExtra(CertifyFingerprintActivity.EXTRA_ENABLE_WORD_CONFIRM, enableWordConfirm); startActivityForResult(intent, REQUEST_CERTIFY); } private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId}); startActivityForResult(intent, REQUEST_CERTIFY); } private void showQrCodeDialog() { Intent qrCodeIntent = new Intent(this, QrCodeViewActivity.class); // create the transition animation - the images in the layouts // of both activities are defined with android:transitionName="qr_code" Bundle opts = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ActivityOptions options = ActivityOptions .makeSceneTransitionAnimation(this, mQrCodeLayout, "qr_code"); opts = options.toBundle(); } qrCodeIntent.setData(mDataUri); ActivityCompat.startActivity(this, qrCodeIntent, opts); } private void startPassphraseActivity(int requestCode) { if (keyHasPassphrase()) { Intent intent = new Intent(this, PassphraseDialogActivity.class); RequiredInputParcel requiredInput = RequiredInputParcel.createRequiredDecryptPassphrase(mMasterKeyId, mMasterKeyId); requiredInput.mSkipCaching = true; intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); startActivityForResult(intent, requestCode); } else { startBackupActivity(); } } private boolean keyHasPassphrase() { try { SecretKeyType secretKeyType = mProviderHelper.getCachedPublicKeyRing(mMasterKeyId).getSecretKeyType(mMasterKeyId); switch (secretKeyType) { // all of these make no sense to ask case PASSPHRASE_EMPTY: case GNU_DUMMY: case DIVERT_TO_CARD: case UNAVAILABLE: return false; default: return true; } } catch (NotFoundException e) { return false; } } private void startBackupActivity() { Intent intent = new Intent(this, BackupActivity.class); intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { mMasterKeyId }); intent.putExtra(BackupActivity.EXTRA_SECRET, true); startActivity(intent); } private void deleteKey() { Intent deleteIntent = new Intent(this, DeleteKeyDialogActivity.class); deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, new long[]{mMasterKeyId}); deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, mIsSecret); if (mIsSecret) { // for upload in case key is secret deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, Preferences.getPreferences(this).getPreferredKeyserver()); } startActivityForResult(deleteIntent, REQUEST_DELETE); } @Override protected void onActivityResult(@RequestType int requestCode, int resultCode, Intent data) { if (mImportOpHelper.handleActivityResult(requestCode, resultCode, data)) { return; } if (mEditOpHelper != null) { mEditOpHelper.handleActivityResult(requestCode, resultCode, data); } if (resultCode != Activity.RESULT_OK) { super.onActivityResult(requestCode, resultCode, data); return; } switch (requestCode) { case REQUEST_QR_FINGERPRINT: { // If there is an EXTRA_RESULT, that's an error. Just show it. if (data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(this).show(); return; } String fp = data.getStringExtra(ImportKeysProxyActivity.EXTRA_FINGERPRINT); if (fp == null) { Notify.create(this, R.string.error_scan_fp, Notify.LENGTH_LONG, Style.ERROR).show(); return; } if (mFingerprintString.equalsIgnoreCase(fp)) { certifyImmediate(); } else { Notify.create(this, R.string.error_scan_match, Notify.LENGTH_LONG, Style.ERROR).show(); } return; } case REQUEST_BACKUP: { startBackupActivity(); return; } case REQUEST_DELETE: { setResult(RESULT_OK, data); finish(); return; } case REQUEST_CERTIFY: { if (data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(this).show(); } return; } } super.onActivityResult(requestCode, resultCode, data); } @Override protected void doSecurityTokenInBackground() throws IOException { mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); mSecurityTokenAid = mSecurityTokenHelper.getAid(); } @Override protected void onSecurityTokenPostExecute() { long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); try { // if the security token matches a subkey in any key CachedPublicKeyRing ring = mProviderHelper.getCachedPublicKeyRing( KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(tokenId)); byte[] candidateFp = ring.getFingerprint(); // if the master key of that key matches this one, just show the token dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) { showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid); return; } // otherwise, offer to go to that key final long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(candidateFp); Notify.create(this, R.string.snack_security_token_other, Notify.LENGTH_LONG, Style.WARN, new ActionListener() { @Override public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } }, R.string.snack_security_token_view).show(); // and if it's not found, offer import } catch (PgpKeyNotFoundException e) { Notify.create(this, R.string.snack_security_token_other, Notify.LENGTH_LONG, Style.WARN, new ActionListener() { @Override public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } }, R.string.snack_security_token_import).show(); } } public void showSecurityTokenFragment( final byte[] tokenFingerprints, final String tokenUserId, final byte[] tokenAid) { new Handler().post(new Runnable() { @Override public void run() { ViewKeySecurityTokenFragment frag = ViewKeySecurityTokenFragment.newInstance( mMasterKeyId, tokenFingerprints, tokenUserId, tokenAid); FragmentManager manager = getSupportFragmentManager(); manager.popBackStack("security_token", FragmentManager.POP_BACK_STACK_INCLUSIVE); manager.beginTransaction() .addToBackStack("security_token") .replace(R.id.view_key_fragment, frag) // if this is called while the activity wasn't resumed, just forget it happened .commitAllowingStateLoss(); } }); } private void encrypt(Uri dataUri, boolean text) { // If there is no encryption key, don't bother. if (!mHasEncrypt) { Notify.create(this, R.string.error_no_encrypt_subkey, Notify.Style.ERROR).show(); return; } try { long keyId = new ProviderHelper(this) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); long[] encryptionKeyIds = new long[]{keyId}; Intent intent; if (text) { intent = new Intent(this, EncryptTextActivity.class); intent.setAction(EncryptTextActivity.ACTION_ENCRYPT_TEXT); intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); } else { intent = new Intent(this, EncryptFilesActivity.class); intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA); intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds); } // used instead of startActivity set actionbar based on callingPackage startActivityForResult(intent, 0); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } } private void startSafeSlinger(Uri dataUri) { long keyId = 0; try { keyId = new ProviderHelper(this) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } Intent safeSlingerIntent = new Intent(this, SafeSlingerActivity.class); safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, keyId); startActivityForResult(safeSlingerIntent, 0); } /** * Load QR Code asynchronously and with a fade in animation */ private void loadQrCode(final String fingerprint) { AsyncTask loadTask = new AsyncTask() { protected Bitmap doInBackground(Void... unused) { Uri uri = new Uri.Builder() .scheme(Constants.FINGERPRINT_SCHEME) .opaquePart(fingerprint) .build(); // render with minimal size return QrCodeUtils.getQRCodeBitmap(uri, 0); } protected void onPostExecute(Bitmap qrCode) { mQrCodeLoaded = fingerprint; // scale the image up to our actual size. we do this in code rather // than let the ImageView do this because we don't require filtering. Bitmap scaled = Bitmap.createScaledBitmap(qrCode, mQrCode.getHeight(), mQrCode.getHeight(), false); mQrCode.setImageBitmap(scaled); // simple fade-in animation AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); anim.setDuration(200); mQrCode.startAnimation(anim); } }; loadTask.execute(); } // These are the rows that we will retrieve. static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.FINGERPRINT, KeychainContract.KeyRings.HAS_ENCRYPT }; static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_IS_REVOKED = 3; static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; static final int INDEX_FINGERPRINT = 7; static final int INDEX_HAS_ENCRYPT = 8; @Override public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(this, baseUri, PROJECTION, null, null, null); } default: return null; } } int mPreviousColor = 0; /** * Calculate a reasonable color for the status bar based on the given toolbar color. * Style guides want the toolbar color to be a "700" on the Android scale and the status * bar should be the same color at "500", this is roughly 17 / 20th of the value in each * channel. * http://www.google.com/design/spec/style/color.html#color-color-palette */ static public int getStatusBarBackgroundColor(int color) { int r = (color >> 16) & 0xff; int g = (color >> 8) & 0xff; int b = color & 0xff; r = r * 17 / 20; g = g * 17 / 20; b = b * 17 / 20; return (0xff << 24) | (r << 16) | (g << 8) | b; } @Override public void onLoadFinished(Loader loader, Cursor data) { /* TODO better error handling? May cause problems when a key is deleted, * because the notification triggers faster than the activity closes. */ // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { case LOADER_ID_UNIFIED: { // Avoid NullPointerExceptions... if (data.getCount() == 0) { return; } if (data.moveToFirst()) { // get name, email, and comment from USER_ID OpenPgpUtils.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); if (mainUserId.name != null) { mCollapsingToolbarLayout.setTitle(mainUserId.name); } else { mCollapsingToolbarLayout.setTitle(getString(R.string.user_id_no_name)); } mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); mFingerprint = data.getBlob(INDEX_FINGERPRINT); mFingerprintString = KeyFormattingUtils.convertFingerprintToHex(mFingerprint); // if it wasn't shown yet, display token fragment if (mShowSecurityTokenAfterCreation && getIntent().hasExtra(EXTRA_SECURITY_TOKEN_AID)) { mShowSecurityTokenAfterCreation = false; Intent intent = getIntent(); byte[] tokenFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_FINGERPRINTS); String tokenUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID); byte[] tokenAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID); showSecurityTokenFragment(tokenFingerprints, tokenUserId, tokenAid); } mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; mIsRevoked = data.getInt(INDEX_IS_REVOKED) > 0; mIsExpired = data.getInt(INDEX_IS_EXPIRED) != 0; mIsVerified = data.getInt(INDEX_VERIFIED) > 0; // if the refresh animation isn't playing if (!mRotate.hasStarted() && !mRotateSpin.hasStarted()) { // re-create options menu based on mIsSecret, mIsVerified supportInvalidateOptionsMenu(); // this is done at the end of the animation otherwise } AsyncTask photoTask = new AsyncTask() { protected Bitmap doInBackground(Long... mMasterKeyId) { return new ContactHelper(ViewKeyActivity.this) .loadPhotoByMasterKeyId(mMasterKeyId[0], true); } protected void onPostExecute(Bitmap photo) { if (photo == null) { return; } mPhoto.setImageBitmap(photo); mPhoto.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), PorterDuff.Mode.SRC_ATOP); mPhotoLayout.setVisibility(View.VISIBLE); } }; // Note: order is important int color; if (mIsRevoked) { mStatusText.setText(R.string.view_key_revoked); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.REVOKED, R.color.icons, true); // noinspection deprecation, fix requires api level 23 color = getResources().getColor(R.color.key_flag_red); mActionEncryptFile.setVisibility(View.INVISIBLE); mActionEncryptText.setVisibility(View.INVISIBLE); mActionNfc.setVisibility(View.INVISIBLE); hideFab(); mQrCodeLayout.setVisibility(View.GONE); } else if (mIsExpired) { if (mIsSecret) { mStatusText.setText(R.string.view_key_expired_secret); } else { mStatusText.setText(R.string.view_key_expired); } mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.EXPIRED, R.color.icons, true); // noinspection deprecation, fix requires api level 23 color = getResources().getColor(R.color.key_flag_red); mActionEncryptFile.setVisibility(View.INVISIBLE); mActionEncryptText.setVisibility(View.INVISIBLE); mActionNfc.setVisibility(View.INVISIBLE); hideFab(); mQrCodeLayout.setVisibility(View.GONE); } else if (mIsSecret) { mStatusText.setText(R.string.view_key_my_key); mStatusImage.setVisibility(View.GONE); // noinspection deprecation, fix requires api level 23 color = getResources().getColor(R.color.key_flag_green); // reload qr code only if the fingerprint changed if (!mFingerprintString.equals(mQrCodeLoaded)) { loadQrCode(mFingerprintString); } photoTask.execute(mMasterKeyId); mQrCodeLayout.setVisibility(View.VISIBLE); // and place leftOf qr code // RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) // mName.getLayoutParams(); // // remove right margin // nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); // if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { // nameParams.setMarginEnd(0); // } // nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); // mName.setLayoutParams(nameParams); RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) mStatusText.getLayoutParams(); statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { statusParams.setMarginEnd(0); } statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); mStatusText.setLayoutParams(statusParams); mActionEncryptFile.setVisibility(View.VISIBLE); mActionEncryptText.setVisibility(View.VISIBLE); // invokeBeam is available from API 21 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && NfcAdapter.getDefaultAdapter(this) != null) { mActionNfc.setVisibility(View.VISIBLE); } else { mActionNfc.setVisibility(View.INVISIBLE); } showFab(); // noinspection deprecation (no getDrawable with theme at current minApi level 15!) mFab.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); } else { mActionEncryptFile.setVisibility(View.VISIBLE); mActionEncryptText.setVisibility(View.VISIBLE); mQrCodeLayout.setVisibility(View.GONE); mActionNfc.setVisibility(View.INVISIBLE); if (mIsVerified) { mStatusText.setText(R.string.view_key_verified); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.VERIFIED, R.color.icons, true); // noinspection deprecation, fix requires api level 23 color = getResources().getColor(R.color.key_flag_green); photoTask.execute(mMasterKeyId); hideFab(); } else { mStatusText.setText(R.string.view_key_unverified); mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.UNVERIFIED, R.color.icons, true); // noinspection deprecation, fix requires api level 23 color = getResources().getColor(R.color.key_flag_orange); showFab(); } } if (mPreviousColor == 0 || mPreviousColor == color) { mAppBarLayout.setBackgroundColor(color); mCollapsingToolbarLayout.setContentScrimColor(color); mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); mPreviousColor = color; } else { ObjectAnimator colorFade = ObjectAnimator.ofObject(mAppBarLayout, "backgroundColor", new ArgbEvaluator(), mPreviousColor, color); mCollapsingToolbarLayout.setContentScrimColor(color); mCollapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); colorFade.setDuration(1200); colorFade.start(); mPreviousColor = color; } //noinspection deprecation mStatusImage.setAlpha(80); break; } } } } /** * Helper to show Fab, from http://stackoverflow.com/a/31047038 */ private void showFab() { CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams(); p.setBehavior(new FloatingActionButton.Behavior()); p.setAnchorId(R.id.app_bar_layout); mFab.setLayoutParams(p); mFab.setVisibility(View.VISIBLE); } /** * Helper to hide Fab, from http://stackoverflow.com/a/31047038 */ private void hideFab() { CoordinatorLayout.LayoutParams p = (CoordinatorLayout.LayoutParams) mFab.getLayoutParams(); p.setBehavior(null); //should disable default animations p.setAnchorId(View.NO_ID); //should let you set visibility mFab.setLayoutParams(p); mFab.setVisibility(View.GONE); } @Override public void onLoaderReset(Loader loader) { } // CryptoOperationHelper.Callback functions private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper) throws ProviderHelper.NotFoundException { mIsRefreshing = true; mRefreshItem.setEnabled(false); mRefreshItem.setActionView(mRefresh); mRefresh.startAnimation(mRotate); byte[] blob = (byte[]) providerHelper.getGenericData( KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null); ArrayList entries = new ArrayList<>(); entries.add(keyEntry); mKeyList = entries; mKeyserver = Preferences.getPreferences(this).getPreferredKeyserver(); mImportOpHelper.cryptoOperation(); } @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); } @Override public void onCryptoOperationSuccess(ImportKeyResult result) { mIsRefreshing = false; result.createNotify(this).show(); } @Override public void onCryptoOperationCancelled() { mIsRefreshing = false; } @Override public void onCryptoOperationError(ImportKeyResult result) { mIsRefreshing = false; result.createNotify(this).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return true; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13154 Content-Disposition: inline; filename="ViewKeyAdvActivity.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "0be64629bc68e051f056d87d174d7a9736e9d4a5" /* * Copyright (C) 2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewPropertyAnimator; import android.view.animation.OvershootInterpolator; import android.widget.Toast; import com.astuetz.PagerSlidingTabStrip; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; public class ViewKeyAdvActivity extends BaseActivity implements LoaderCallbacks, OnPageChangeListener { ProviderHelper mProviderHelper; protected Uri mDataUri; public static final String EXTRA_SELECTED_TAB = "selected_tab"; public static final int TAB_START = 0; public static final int TAB_SHARE = 1; public static final int TAB_IDENTITIES = 2; public static final int TAB_SUBKEYS = 3; public static final int TAB_CERTS = 4; // view private ViewPager mViewPager; private PagerSlidingTabStrip mSlidingTabLayout; private static final int LOADER_ID_UNIFIED = 0; private ActionMode mActionMode; private boolean mHasSecret; private PagerTabStripAdapter mTabAdapter; private boolean mActionIconShown; private boolean[] mTabsWithActionMode; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setFullScreenDialogClose(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); mProviderHelper = new ProviderHelper(this); mViewPager = (ViewPager) findViewById(R.id.pager); mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.sliding_tab_layout); mDataUri = getIntent().getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Data missing. Should be uri of key!"); finish(); return; } if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) { mDataUri = new ContactHelper(this).dataUriFromContactUri(mDataUri); if (mDataUri == null) { Log.e(Constants.TAG, "Contact Data missing. Should be uri of key!"); Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show(); finish(); return; } } // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); initTabs(mDataUri); } @Override protected void initLayout() { setContentView(R.layout.view_key_adv_activity); } private void initTabs(Uri dataUri) { mTabAdapter = new PagerTabStripAdapter(this); mViewPager.setAdapter(mTabAdapter); // keep track which of these are action mode enabled! mTabsWithActionMode = new boolean[5]; mTabAdapter.addTab(ViewKeyAdvStartFragment.class, null, getString(R.string.key_view_tab_start)); mTabsWithActionMode[0] = false; Bundle shareBundle = new Bundle(); shareBundle.putParcelable(ViewKeyAdvShareFragment.ARG_DATA_URI, dataUri); mTabAdapter.addTab(ViewKeyAdvShareFragment.class, shareBundle, getString(R.string.key_view_tab_share)); mTabsWithActionMode[1] = false; Bundle userIdsBundle = new Bundle(); userIdsBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri); mTabAdapter.addTab(ViewKeyAdvUserIdsFragment.class, userIdsBundle, getString(R.string.section_user_ids)); mTabsWithActionMode[2] = true; Bundle keysBundle = new Bundle(); keysBundle.putParcelable(ViewKeyAdvSubkeysFragment.ARG_DATA_URI, dataUri); mTabAdapter.addTab(ViewKeyAdvSubkeysFragment.class, keysBundle, getString(R.string.key_view_tab_keys)); mTabsWithActionMode[3] = true; Bundle certsBundle = new Bundle(); certsBundle.putParcelable(ViewKeyAdvCertsFragment.ARG_DATA_URI, dataUri); mTabAdapter.addTab(ViewKeyAdvCertsFragment.class, certsBundle, getString(R.string.key_view_tab_certs)); mTabsWithActionMode[4] = false; // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); mSlidingTabLayout.setOnPageChangeListener(this); // switch to tab selected by extra Intent intent = getIntent(); int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, TAB_START); mViewPager.setCurrentItem(switchToTab); } // These are the rows that we will retrieve. static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.FINGERPRINT, }; static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_USER_ID = 2; static final int INDEX_IS_REVOKED = 3; static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; static final int INDEX_FINGERPRINT = 7; @Override public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(this, baseUri, PROJECTION, null, null, null); } default: return null; } } @Override public void onLoadFinished(Loader loader, Cursor data) { // Avoid NullPointerExceptions... if (data == null || data.getCount() == 0) { return; } // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { // get name, email, and comment from USER_ID OpenPgpUtils.UserId mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID)); if (mainUserId.name != null) { setTitle(mainUserId.name); } else { setTitle(R.string.user_id_no_name); } byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); // get key id from MASTER_KEY_ID long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId)); mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; // Note: order is important int color; if (isRevoked || isExpired) { color = getResources().getColor(R.color.key_flag_red); } else if (mHasSecret) { color = getResources().getColor(R.color.android_green_light); } else { if (isVerified) { color = getResources().getColor(R.color.android_green_light); } else { color = getResources().getColor(R.color.key_flag_orange); } } mToolbar.setBackgroundColor(color); mStatusBar.setBackgroundColor(ViewKeyActivity.getStatusBarBackgroundColor(color)); mSlidingTabLayout.setBackgroundColor(color); break; } } } } @Override public void onLoaderReset(Loader loader) { } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // if a result has been returned, display a notify if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(this).show(); } else { super.onActivityResult(requestCode, resultCode, data); } } @Override public boolean onCreateOptionsMenu(Menu menu) { if (!mHasSecret) { return false; } // always add the item, switch its visibility depending on fragment getMenuInflater().inflate(R.menu.action_mode_edit, menu); final MenuItem vActionModeItem = menu.findItem(R.id.menu_action_mode_edit); boolean isCurrentActionFragment = mTabsWithActionMode[mViewPager.getCurrentItem()]; // if the state is as it should be, never mind if (isCurrentActionFragment == mActionIconShown) { return isCurrentActionFragment; } // show or hide accordingly mActionIconShown = isCurrentActionFragment; vActionModeItem.setEnabled(isCurrentActionFragment); animateMenuItem(vActionModeItem, isCurrentActionFragment); return true; } private void animateMenuItem(final MenuItem vEditSubkeys, final boolean animateShow) { View actionView = LayoutInflater.from(this).inflate(R.layout.edit_icon, null); vEditSubkeys.setActionView(actionView); actionView.setTranslationX(animateShow ? 150 : 0); ViewPropertyAnimator animator = actionView.animate(); animator.translationX(animateShow ? 0 : 150); animator.setDuration(300); animator.setInterpolator(new OvershootInterpolator(1.5f)); animator.setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (!animateShow) { vEditSubkeys.setVisible(false); } vEditSubkeys.setActionView(null); } }); animator.start(); } @Override public void onActionModeStarted(final ActionMode mode) { super.onActionModeStarted(mode); mActionMode = mode; } @Override public void onActionModeFinished(ActionMode mode) { super.onActionModeFinished(mode); mActionMode = null; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { if (mActionMode != null) { mActionMode.finish(); mActionMode = null; } invalidateOptionsMenu(); } @Override public void onPageScrollStateChanged(int state) { } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 13559 Content-Disposition: inline; filename="ViewKeyAdvCertsFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "c39881e5b8181f084c435ef9fea60619cee932cf" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.CursorAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; public class ViewKeyAdvCertsFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks, AdapterView.OnItemClickListener { public static final String ARG_DATA_URI = "data_uri"; private StickyListHeadersListView mStickyList; private CertListAdapter mCertsAdapter; private Uri mDataUriCerts; // These are the rows that we will retrieve. static final String[] CERTS_PROJECTION = new String[]{ KeychainContract.Certs._ID, KeychainContract.Certs.MASTER_KEY_ID, KeychainContract.Certs.VERIFIED, KeychainContract.Certs.TYPE, KeychainContract.Certs.RANK, KeychainContract.Certs.KEY_ID_CERTIFIER, KeychainContract.Certs.USER_ID, KeychainContract.Certs.SIGNER_UID }; // sort by our user id, static final String CERTS_SORT_ORDER = KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.RANK + " ASC, " + KeychainContract.Certs.VERIFIED + " DESC, " + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.TYPE + " DESC, " + KeychainContract.Certs.SIGNER_UID + " ASC"; /** * Creates new instance of this fragment */ public static ViewKeyAdvCertsFragment newInstance(Uri dataUri) { ViewKeyAdvCertsFragment frag = new ViewKeyAdvCertsFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_adv_certs_fragment, getContainer()); mStickyList = (StickyListHeadersListView) view.findViewById(R.id.certs_list); return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } loadData(dataUri); } private void loadData(Uri dataUri) { mDataUriCerts = KeychainContract.Certs.buildCertsUri(dataUri); mStickyList.setAreHeadersSticky(true); mStickyList.setDrawingListUnderStickyHeader(false); mStickyList.setOnItemClickListener(this); mStickyList.setEmptyView(getActivity().findViewById(R.id.empty)); // Create an empty adapter we will use to display the loaded data. mCertsAdapter = new CertListAdapter(getActivity(), null); mStickyList.setAdapter(mCertsAdapter); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(0, null, this); } public Loader onCreateLoader(int id, Bundle args) { setContentShown(false); // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), mDataUriCerts, CERTS_PROJECTION, null, null, CERTS_SORT_ORDER); } public void onLoadFinished(Loader loader, Cursor data) { // Avoid NullPointerExceptions, if we get an empty result set. if (data.getCount() == 0) { return; } // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mCertsAdapter.swapCursor(data); mStickyList.setAdapter(mCertsAdapter); // TODO: maybe show not before both are loaded! setContentShown(true); } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { mCertsAdapter.swapCursor(null); } /** * On click on item, start key view activity */ @Override public void onItemClick(AdapterView adapterView, View view, int position, long id) { if (view.getTag(R.id.tag_mki) != null) { long masterKeyId = (Long) view.getTag(R.id.tag_mki); long rank = (Long) view.getTag(R.id.tag_rank); long certifierId = (Long) view.getTag(R.id.tag_certifierId); Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class); viewIntent.setData(KeychainContract.Certs.buildCertsSpecificUri( masterKeyId, rank, certifierId)); startActivity(viewIntent); } } /** * Implements StickyListHeadersAdapter from library */ private class CertListAdapter extends CursorAdapter implements StickyListHeadersAdapter { private LayoutInflater mInflater; private int mIndexMasterKeyId, mIndexUserId, mIndexRank; private int mIndexSignerKeyId, mIndexSignerUserId; private int mIndexVerified, mIndexType; public CertListAdapter(Context context, Cursor c) { super(context, c, 0); mInflater = LayoutInflater.from(context); initIndex(c); } @Override public Cursor swapCursor(Cursor newCursor) { initIndex(newCursor); return super.swapCursor(newCursor); } /** * Get column indexes for performance reasons just once in constructor and swapCursor. For a * performance comparison see http://stackoverflow.com/a/17999582 * * @param cursor */ private void initIndex(Cursor cursor) { if (cursor != null) { mIndexMasterKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID); mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.USER_ID); mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.Certs.RANK); mIndexType = cursor.getColumnIndexOrThrow(KeychainContract.Certs.TYPE); mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED); mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER); mIndexSignerUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID); } } /** * Bind cursor data to the item list view *

* NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. * Thus no ViewHolder is required here. */ @Override public void bindView(View view, Context context, Cursor cursor) { // set name and stuff, common to both key types TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId); TextView wSignerName = (TextView) view.findViewById(R.id.signerName); TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus); String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix(getActivity(), cursor.getLong(mIndexSignerKeyId)); OpenPgpUtils.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexSignerUserId)); if (userId.name != null) { wSignerName.setText(userId.name); } else { wSignerName.setText(R.string.user_id_no_name); } wSignerKeyId.setText(signerKeyId); switch (cursor.getInt(mIndexType)) { case WrappedSignature.DEFAULT_CERTIFICATION: // 0x10 wSignStatus.setText(R.string.cert_default); break; case WrappedSignature.NO_CERTIFICATION: // 0x11 wSignStatus.setText(R.string.cert_none); break; case WrappedSignature.CASUAL_CERTIFICATION: // 0x12 wSignStatus.setText(R.string.cert_casual); break; case WrappedSignature.POSITIVE_CERTIFICATION: // 0x13 wSignStatus.setText(R.string.cert_positive); break; case WrappedSignature.CERTIFICATION_REVOCATION: // 0x30 wSignStatus.setText(R.string.cert_revoke); break; } view.setTag(R.id.tag_mki, cursor.getLong(mIndexMasterKeyId)); view.setTag(R.id.tag_rank, cursor.getLong(mIndexRank)); view.setTag(R.id.tag_certifierId, cursor.getLong(mIndexSignerKeyId)); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return mInflater.inflate(R.layout.view_key_adv_certs_item, parent, false); } /** * Creates a new header view and binds the section headers to it. It uses the ViewHolder * pattern. Most functionality is similar to getView() from Android's CursorAdapter. *

* NOTE: The variables mDataValid and mCursor are available due to the super class * CursorAdapter. */ @Override public View getHeaderView(int position, View convertView, ViewGroup parent) { HeaderViewHolder holder; if (convertView == null) { holder = new HeaderViewHolder(); convertView = mInflater.inflate(R.layout.view_key_adv_certs_header, parent, false); holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); holder.count = (TextView) convertView.findViewById(R.id.certs_num); convertView.setTag(holder); } else { holder = (HeaderViewHolder) convertView.getTag(); } if (!mDataValid) { // no data available at this point Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); return convertView; } if (!mCursor.moveToPosition(position)) { throw new IllegalStateException("couldn't move cursor to position " + position); } // set header text as first char in user id String userId = mCursor.getString(mIndexUserId); holder.text.setText(userId); holder.count.setVisibility(View.GONE); return convertView; } /** * Header IDs should be static, position=1 should always return the same Id that is. */ @Override public long getHeaderId(int position) { if (!mDataValid) { // no data available at this point Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); return -1; } if (!mCursor.moveToPosition(position)) { throw new IllegalStateException("couldn't move cursor to position " + position); } // otherwise, return the first character of the name as ID return mCursor.getInt(mIndexRank); // sort by the first four characters (should be enough I guess?) // return ByteBuffer.wrap(userId.getBytes()).asLongBuffer().get(0); } class HeaderViewHolder { TextView text; TextView count; } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 19176 Content-Disposition: inline; filename="ViewKeyAdvShareFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "e2a91b0fd3146540b768eb662c283e65bde145f4" /* * Copyright (C) 2014 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStreamWriter; import android.app.Activity; import android.app.ActivityOptions; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.widget.CardView; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; 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.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; public class ViewKeyAdvShareFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { public static final String ARG_DATA_URI = "uri"; private ImageView mQrCode; private CardView mQrCodeLayout; private TextView mFingerprintView; NfcHelper mNfcHelper; private static final int LOADER_ID_UNIFIED = 0; private Uri mDataUri; private byte[] mFingerprint; private String mUserId; private Bitmap mQrCodeBitmapCache; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer()); ProviderHelper providerHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity()); mNfcHelper = new NfcHelper(getActivity(), providerHelper); mFingerprintView = (TextView) view.findViewById(R.id.view_key_fingerprint); mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code); // We cache the QR code bitmap in its smallest possible size, then scale // it manually for the correct size whenever the layout of the ImageView // changes. The fingerprint qr code loader which runs in the background // just calls requestLayout when it is finished, this way the loader and // background task are disconnected from any layouting the ImageView may // undergo. Please note how these six lines are perfectly right-aligned. mQrCode.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { // bitmap scaling is expensive, avoid doing it if we already have the correct size! int mCurrentWidth = 0, mCurrentHeight = 0; if (mQrCodeBitmapCache != null) { if (mCurrentWidth == mQrCode.getWidth() && mCurrentHeight == mQrCode.getHeight()) { return; } mCurrentWidth = mQrCode.getWidth(); mCurrentHeight = mQrCode.getHeight(); // scale the image up to our actual size. we do this in code rather // than let the ImageView do this because we don't require filtering. Bitmap scaled = Bitmap.createScaledBitmap(mQrCodeBitmapCache, mCurrentWidth, mCurrentHeight, false); mQrCode.setImageBitmap(scaled); } } }); mQrCodeLayout = (CardView) view.findViewById(R.id.view_key_qr_code_layout); mQrCodeLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showQrCodeDialog(); } }); View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share); View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard); View vKeyShareButton = view.findViewById(R.id.view_key_action_key_share); View vKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc); View vKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard); ImageButton vKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger); View vKeyUploadButton = view.findViewById(R.id.view_key_action_upload); vKeySafeSlingerButton.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); vFingerprintShareButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { shareFingerprint(false); } }); vFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { shareFingerprint(true); } }); vKeyShareButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { shareKey(false); } }); vKeyClipboardButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { shareKey(true); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { vKeyNfcButton.setVisibility(View.VISIBLE); vKeyNfcButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mNfcHelper.invokeNfcBeam(); } }); } else { vKeyNfcButton.setVisibility(View.GONE); } vKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startSafeSlinger(mDataUri); } }); vKeyUploadButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { uploadToKeyserver(); } }); return root; } private void startSafeSlinger(Uri dataUri) { long keyId = 0; try { keyId = new ProviderHelper(getActivity()) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "key not found!", e); } Intent safeSlingerIntent = new Intent(getActivity(), SafeSlingerActivity.class); safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, keyId); startActivityForResult(safeSlingerIntent, 0); } private void shareKey(boolean toClipboard) { Activity activity = getActivity(); if (activity == null || mFingerprint == null) { return; } ProviderHelper providerHelper = new ProviderHelper(activity); try { String content = providerHelper.getKeyRingAsArmoredString( KeychainContract.KeyRingData.buildPublicKeyRingUri(mDataUri)); if (toClipboard) { ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); if (clipMan == null) { Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR); return; } ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content); clipMan.setPrimaryClip(clip); Notify.create(activity, R.string.key_copied_to_clipboard, Notify.Style.OK).show(); return; } // let user choose application Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.setType(Constants.MIME_TYPE_KEYS); // NOTE: Don't use Intent.EXTRA_TEXT to send the key // better send it via a Uri! // example: Bluetooth Share will convert text/plain sent via Intent.EXTRA_TEXT to HTML try { TemporaryFileProvider shareFileProv = new TemporaryFileProvider(); String filename = KeyFormattingUtils.convertFingerprintToHex(mFingerprint); OpenPgpUtils.UserId mainUserId = KeyRing.splitUserId(mUserId); if (mainUserId.name != null) { filename = mainUserId.name; } Uri contentUri = TemporaryFileProvider.createFile(activity, filename + Constants.FILE_EXTENSION_ASC); BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter( new ParcelFileDescriptor.AutoCloseOutputStream( shareFileProv.openFile(contentUri, "w")))); contentWriter.write(content); contentWriter.close(); sendIntent.putExtra(Intent.EXTRA_STREAM, contentUri); } catch (FileNotFoundException e) { Log.e(Constants.TAG, "Error creating temporary key share file!", e); // no need for a snackbar because one sharing option doesn't work // Notify.create(getActivity(), R.string.error_temp_file, Notify.Style.ERROR).show(); } String title = getString(R.string.title_share_key); Intent shareChooser = Intent.createChooser(sendIntent, title); startActivity(shareChooser); } catch (PgpGeneralException | IOException e) { Log.e(Constants.TAG, "error processing key!", e); Notify.create(activity, R.string.error_key_processing, Notify.Style.ERROR).show(); } catch (ProviderHelper.NotFoundException e) { Log.e(Constants.TAG, "key not found!", e); Notify.create(activity, R.string.error_key_not_found, Notify.Style.ERROR).show(); } } private void shareFingerprint(boolean toClipboard) { Activity activity = getActivity(); if (activity == null || mFingerprint == null) { return; } String content; String fingerprint = KeyFormattingUtils.convertFingerprintToHex(mFingerprint); if (!toClipboard) { content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint; } else { content = fingerprint; } if (toClipboard) { ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); if (clipMan == null) { Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR); return; } ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content); clipMan.setPrimaryClip(clip); Notify.create(activity, R.string.fingerprint_copied_to_clipboard, Notify.Style.OK).show(); return; } // let user choose application Intent sendIntent = new Intent(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, content); sendIntent.setType("text/plain"); String title = getString(R.string.title_share_fingerprint_with); Intent shareChooser = Intent.createChooser(sendIntent, title); startActivity(shareChooser); } private void showQrCodeDialog() { Intent qrCodeIntent = new Intent(getActivity(), QrCodeViewActivity.class); // create the transition animation - the images in the layouts // of both activities are defined with android:transitionName="qr_code" Bundle opts = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { ActivityOptions options = ActivityOptions .makeSceneTransitionAnimation(getActivity(), mQrCodeLayout, "qr_code"); opts = options.toBundle(); } qrCodeIntent.setData(mDataUri); ActivityCompat.startActivity(getActivity(), qrCodeIntent, opts); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } loadData(dataUri); } private void loadData(Uri dataUri) { mDataUri = dataUri; // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); // Prepare the NfcHelper mNfcHelper.initNfc(mDataUri); } static final String[] UNIFIED_PROJECTION = new String[]{ KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.USER_ID }; static final int INDEX_UNIFIED_FINGERPRINT = 1; static final int INDEX_UNIFIED_USER_ID = 2; public Loader onCreateLoader(int id, Bundle args) { setContentShown(false); switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } default: return null; } } public void onLoadFinished(Loader loader, Cursor data) { // Avoid NullPointerExceptions... if (data == null || data.getCount() == 0) { return; } // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.moveToFirst()) { byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT); setFingerprint(fingerprintBlob); mUserId = data.getString(INDEX_UNIFIED_USER_ID); break; } } } setContentShown(true); } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { mFingerprint = null; mQrCodeBitmapCache = null; } /** * Load QR Code asynchronously and with a fade in animation */ private void setFingerprint(byte[] fingerprintBlob) { mFingerprint = fingerprintBlob; final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); mFingerprintView.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); if (mQrCodeBitmapCache != null) { return; } AsyncTask loadTask = new AsyncTask() { protected Bitmap doInBackground(Void... unused) { Uri uri = new Uri.Builder() .scheme(Constants.FINGERPRINT_SCHEME) .opaquePart(fingerprint) .build(); // render with minimal size return QrCodeUtils.getQRCodeBitmap(uri, 0); } protected void onPostExecute(Bitmap qrCode) { // cache for later, and if we are attached request re-layout mQrCodeBitmapCache = qrCode; if (ViewKeyAdvShareFragment.this.isAdded()) { mQrCode.requestLayout(); // simple fade-in animation AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); anim.setDuration(200); mQrCode.startAnimation(anim); } } }; loadTask.execute(); } private void uploadToKeyserver() { long keyId; try { keyId = new ProviderHelper(getActivity()) .getCachedPublicKeyRing(mDataUri) .extractOrGetMasterKeyId(); } catch (PgpKeyNotFoundException e) { Log.e(Constants.TAG, "key not found!", e); Notify.create(getActivity(), "key not found", Style.ERROR).show(); return; } Intent uploadIntent = new Intent(getActivity(), UploadKeyActivity.class); uploadIntent.setData(mDataUri); uploadIntent.putExtra(MultiUserIdsFragment.EXTRA_KEY_IDS, new long[]{keyId}); startActivityForResult(uploadIntent, 0); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 1999 Content-Disposition: inline; filename="ViewKeyAdvStartFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "f7e6f2b9c9ad58e17b5645c4164cfda703558b41" /* * Copyright (C) 2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.markdown4j.Markdown4jProcessor; import org.sufficientlysecure.htmltextview.HtmlTextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; public class ViewKeyAdvStartFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.view_key_adv_start_fragment, container, false); HtmlTextView textView = (HtmlTextView) view.findViewById(R.id.view_key_adv_start_text); // load markdown from raw resource try { String html = new Markdown4jProcessor().process( getActivity().getResources().openRawResource(R.raw.advanced)); textView.setHtmlFromString(html, new HtmlTextView.LocalImageGetter()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } return view; } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 19515 Content-Disposition: inline; filename="ViewKeyAdvSubkeysFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "93b38af9b563e74360843e28dec6ca633980405a" /* * Copyright (C) 2014-2015 Dominik Schürmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import android.widget.ViewAnimator; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { public static final String ARG_DATA_URI = "data_uri"; private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_SUBKEYS = 1; private ListView mSubkeysList; private ListView mSubkeysAddedList; private View mSubkeysAddedLayout; private ViewAnimator mSubkeyAddFabLayout; private SubkeysAdapter mSubkeysAdapter; private SubkeysAddedAdapter mSubkeysAddedAdapter; private CryptoOperationHelper mEditKeyHelper; private Uri mDataUri; private long mMasterKeyId; private byte[] mFingerprint; private boolean mHasSecret; private SaveKeyringParcel mEditModeSaveKeyringParcel; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_adv_subkeys_fragment, getContainer()); mSubkeysList = (ListView) view.findViewById(R.id.view_key_subkeys); mSubkeysAddedList = (ListView) view.findViewById(R.id.view_key_subkeys_added); mSubkeysAddedLayout = view.findViewById(R.id.view_key_subkeys_add_layout); mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { editSubkey(position); } }); View footer = new View(getActivity()); int spacing = (int) android.util.TypedValue.applyDimension( android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() ); android.widget.AbsListView.LayoutParams params = new android.widget.AbsListView.LayoutParams( android.widget.AbsListView.LayoutParams.MATCH_PARENT, spacing ); footer.setLayoutParams(params); mSubkeysAddedList.addFooterView(footer, null, false); mSubkeyAddFabLayout = (ViewAnimator) view.findViewById(R.id.view_key_subkey_fab_layout); view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addSubkey(); } }); setHasOptionsMenu(true); return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } loadData(dataUri); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mEditKeyHelper != null) { mEditKeyHelper.handleActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } private void loadData(Uri dataUri) { mDataUri = dataUri; // Create an empty adapter we will use to display the loaded data. mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); mSubkeysList.setAdapter(mSubkeysAdapter); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, this); } // These are the rows that we will retrieve. static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.FINGERPRINT, }; static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_HAS_ANY_SECRET = 2; static final int INDEX_FINGERPRINT = 3; @Override public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, null); } case LOADER_ID_SUBKEYS: { setContentShown(false); Uri subkeysUri = KeychainContract.Keys.buildKeysUri(mDataUri); return new CursorLoader(getActivity(), subkeysUri, SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); } default: return null; } } public void onLoadFinished(Loader loader, Cursor data) { // Avoid NullPointerExceptions, if we get an empty result set. if (data.getCount() == 0) { return; } switch (loader.getId()) { case LOADER_ID_UNIFIED: { data.moveToFirst(); mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mFingerprint = data.getBlob(INDEX_FINGERPRINT); break; } case LOADER_ID_SUBKEYS: { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mSubkeysAdapter.swapCursor(data); // TODO: maybe show not before both are loaded! setContentShown(true); break; } } } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { mSubkeysAdapter.swapCursor(null); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_action_mode_edit: enterEditMode(); return true; default: return super.onOptionsItemSelected(item); } } public void enterEditMode() { FragmentActivity activity = getActivity(); if (activity == null) { return; } activity.startActionMode(new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mEditModeSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint); mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mEditModeSaveKeyringParcel.mAddSubKeys, false); mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); mSubkeysAddedLayout.setVisibility(View.VISIBLE); mSubkeyAddFabLayout.setDisplayedChild(1); mSubkeysAdapter.setEditMode(mEditModeSaveKeyringParcel); getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this); mode.setTitle(R.string.title_edit_subkeys); mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { editKey(mode); return true; } @Override public void onDestroyActionMode(ActionMode mode) { mEditModeSaveKeyringParcel = null; mSubkeysAdapter.setEditMode(null); mSubkeysAddedLayout.setVisibility(View.GONE); mSubkeyAddFabLayout.setDisplayedChild(0); getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this); } }); } private void addSubkey() { boolean willBeMasterKey; if (mSubkeysAdapter != null) { willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0; } else { willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0; } AddSubkeyDialogFragment addSubkeyDialogFragment = AddSubkeyDialogFragment.newInstance(willBeMasterKey); addSubkeyDialogFragment .setOnAlgorithmSelectedListener( new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { @Override public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { mSubkeysAddedAdapter.add(newSubkey); } } ); addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog"); } private void editSubkey(final int position) { final long keyId = mSubkeysAdapter.getKeyId(position); Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY: editSubkeyExpiry(position); break; case EditSubkeyDialogFragment.MESSAGE_REVOKE: // toggle if (mEditModeSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) { mEditModeSaveKeyringParcel.mRevokeSubKeys.remove(keyId); } else { mEditModeSaveKeyringParcel.mRevokeSubKeys.add(keyId); } break; case EditSubkeyDialogFragment.MESSAGE_STRIP: { SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); if (secretKeyType == SecretKeyType.GNU_DUMMY) { // Key is already stripped; this is a no-op. break; } SubkeyChange change = mEditModeSaveKeyringParcel.getSubkeyChange(keyId); if (change == null) { mEditModeSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false)); break; } // toggle change.mDummyStrip = !change.mDummyStrip; if (change.mDummyStrip && change.mMoveKeyToSecurityToken) { // User had chosen to divert key, but now wants to strip it instead. change.mMoveKeyToSecurityToken = false; } break; } case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_SECURITY_TOKEN: { SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || secretKeyType == SecretKeyType.GNU_DUMMY) { Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_stripped, Notify.Style.ERROR) .show(); break; } int algorithm = mSubkeysAdapter.getAlgorithm(position); if (algorithm != PublicKeyAlgorithmTags.RSA_GENERAL && algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT && algorithm != PublicKeyAlgorithmTags.RSA_SIGN) { Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_algo, Notify.Style.ERROR) .show(); break; } if (mSubkeysAdapter.getKeySize(position) != 2048) { Notify.create(getActivity(), R.string.edit_key_error_bad_security_token_size, Notify.Style.ERROR) .show(); break; } SubkeyChange change; change = mEditModeSaveKeyringParcel.getSubkeyChange(keyId); if (change == null) { mEditModeSaveKeyringParcel.mChangeSubKeys.add( new SubkeyChange(keyId, false, true) ); break; } // toggle change.mMoveKeyToSecurityToken = !change.mMoveKeyToSecurityToken; if (change.mMoveKeyToSecurityToken && change.mDummyStrip) { // User had chosen to strip key, but now wants to divert it. change.mDummyStrip = false; } break; } } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); } }; // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditSubkeyDialogFragment dialogFragment = EditSubkeyDialogFragment.newInstance(messenger); dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyDialog"); } }); } private void editSubkeyExpiry(final int position) { final long keyId = mSubkeysAdapter.getKeyId(position); final Long creationDate = mSubkeysAdapter.getCreationDate(position); final Long expiryDate = mSubkeysAdapter.getExpiryDate(position); Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY: mEditModeSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = (Long) message.getData().getSerializable( EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY); break; } getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); } }; // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditSubkeyExpiryDialogFragment dialogFragment = EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate); dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog"); } }); } private void editKey(final ActionMode mode) { CryptoOperationHelper.Callback editKeyCallback = new CryptoOperationHelper.Callback() { @Override public SaveKeyringParcel createOperationInput() { return mEditModeSaveKeyringParcel; } @Override public void onCryptoOperationSuccess(EditKeyResult result) { mode.finish(); result.createNotify(getActivity()).show(); } @Override public void onCryptoOperationCancelled() { mode.finish(); } @Override public void onCryptoOperationError(EditKeyResult result) { mode.finish(); result.createNotify(getActivity()).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mEditKeyHelper = new CryptoOperationHelper<>(1, this, editKeyCallback, R.string.progress_saving); mEditKeyHelper.cryptoOperation(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 16282 Content-Disposition: inline; filename="ViewKeyAdvUserIdsFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "69ccab1624b4be7c6f4d1f9ec7349feb7a15f7e4" /* * Copyright (C) 2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.util.Log; public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { public static final String ARG_DATA_URI = "uri"; private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; private ListView mUserIds; private ListView mUserIdsAddedList; private View mUserIdsAddedLayout; private ViewAnimator mUserIdAddFabLayout; private UserIdsAdapter mUserIdsAdapter; private UserIdsAddedAdapter mUserIdsAddedAdapter; private CryptoOperationHelper mEditKeyHelper; private Uri mDataUri; private long mMasterKeyId; private byte[] mFingerprint; private boolean mHasSecret; private SaveKeyringParcel mEditModeSaveKeyringParcel; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_adv_user_ids_fragment, getContainer()); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); mUserIdsAddedList = (ListView) view.findViewById(R.id.view_key_user_ids_added); mUserIdsAddedLayout = view.findViewById(R.id.view_key_user_ids_add_layout); mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { showOrEditUserIdInfo(position); } }); View footer = new View(getActivity()); int spacing = (int) android.util.TypedValue.applyDimension( android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() ); android.widget.AbsListView.LayoutParams params = new android.widget.AbsListView.LayoutParams( android.widget.AbsListView.LayoutParams.MATCH_PARENT, spacing ); footer.setLayoutParams(params); mUserIdsAddedList.addFooterView(footer, null, false); mUserIdAddFabLayout = (ViewAnimator) view.findViewById(R.id.view_key_subkey_fab_layout); view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addUserId(); } }); setHasOptionsMenu(true); return root; } private void showOrEditUserIdInfo(final int position) { if (mEditModeSaveKeyringParcel != null) { editUserId(position); } else { showUserIdInfo(position); } } private void editUserId(final int position) { final String userId = mUserIdsAdapter.getUserId(position); final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); final boolean isRevokedPending = mUserIdsAdapter.getIsRevokedPending(position); Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { switch (message.what) { case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID: // toggle if (mEditModeSaveKeyringParcel.mChangePrimaryUserId != null && mEditModeSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { mEditModeSaveKeyringParcel.mChangePrimaryUserId = null; } else { mEditModeSaveKeyringParcel.mChangePrimaryUserId = userId; } break; case EditUserIdDialogFragment.MESSAGE_REVOKE: // toggle if (mEditModeSaveKeyringParcel.mRevokeUserIds.contains(userId)) { mEditModeSaveKeyringParcel.mRevokeUserIds.remove(userId); } else { mEditModeSaveKeyringParcel.mRevokeUserIds.add(userId); // not possible to revoke and change to primary user id if (mEditModeSaveKeyringParcel.mChangePrimaryUserId != null && mEditModeSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { mEditModeSaveKeyringParcel.mChangePrimaryUserId = null; } } break; } getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad(); } }; // Create a new Messenger for the communication back final Messenger messenger = new Messenger(returnHandler); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { EditUserIdDialogFragment dialogFragment = EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); } }); } private void showUserIdInfo(final int position) { final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); final int isVerified = mUserIdsAdapter.getIsVerified(position); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(isRevoked, isVerified); dialogFragment.show(getActivity().getSupportFragmentManager(), "userIdInfoDialog"); } }); } private void addUserId() { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); // add new user id mUserIdsAddedAdapter.add(data .getString(AddUserIdDialogFragment.MESSAGE_DATA_USER_ID)); } } }; // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); // pre-fill out primary name AddUserIdDialogFragment addUserIdDialog = AddUserIdDialogFragment.newInstance(messenger, "", true); addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } loadData(dataUri); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mEditKeyHelper != null) { mEditKeyHelper.handleActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } private void loadData(Uri dataUri) { mDataUri = dataUri; Log.i(Constants.TAG, "mDataUri: " + mDataUri); mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); mUserIds.setAdapter(mUserIdsAdapter); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); } // These are the rows that we will retrieve. static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.FINGERPRINT, }; static final int INDEX_MASTER_KEY_ID = 1; static final int INDEX_HAS_ANY_SECRET = 2; static final int INDEX_FINGERPRINT = 3; public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null, null); } case LOADER_ID_USER_IDS: { setContentShown(false); Uri userIdUri = UserPackets.buildUserIdsUri(mDataUri); return new CursorLoader(getActivity(), userIdUri, UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); } default: return null; } } public void onLoadFinished(Loader loader, Cursor data) { // Avoid NullPointerExceptions, if we get an empty result set. if (data.getCount() == 0) { return; } switch (loader.getId()) { case LOADER_ID_UNIFIED: { data.moveToFirst(); mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mFingerprint = data.getBlob(INDEX_FINGERPRINT); break; } case LOADER_ID_USER_IDS: { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mUserIdsAdapter.swapCursor(data); setContentShown(true); break; } } } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { if (loader.getId() != LOADER_ID_USER_IDS) { return; } mUserIdsAdapter.swapCursor(null); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_action_mode_edit: enterEditMode(); return true; default: return super.onOptionsItemSelected(item); } } public void enterEditMode() { FragmentActivity activity = getActivity(); if (activity == null) { return; } activity.startActionMode(new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mEditModeSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint); mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mEditModeSaveKeyringParcel.mAddUserIds, false); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); mUserIdsAddedLayout.setVisibility(View.VISIBLE); mUserIdAddFabLayout.setDisplayedChild(1); mUserIdsAdapter.setEditMode(mEditModeSaveKeyringParcel); getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, ViewKeyAdvUserIdsFragment.this); mode.setTitle(R.string.title_edit_identities); mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { editKey(mode); return true; } @Override public void onDestroyActionMode(ActionMode mode) { mEditModeSaveKeyringParcel = null; mUserIdsAdapter.setEditMode(null); mUserIdsAddedLayout.setVisibility(View.GONE); mUserIdAddFabLayout.setDisplayedChild(0); getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, ViewKeyAdvUserIdsFragment.this); } }); } private void editKey(final ActionMode mode) { CryptoOperationHelper.Callback editKeyCallback = new CryptoOperationHelper.Callback() { @Override public SaveKeyringParcel createOperationInput() { return mEditModeSaveKeyringParcel; } @Override public void onCryptoOperationSuccess(EditKeyResult result) { mode.finish(); result.createNotify(getActivity()).show(); } @Override public void onCryptoOperationCancelled() { mode.finish(); } @Override public void onCryptoOperationError(EditKeyResult result) { mode.finish(); result.createNotify(getActivity()).show(); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } }; mEditKeyHelper = new CryptoOperationHelper<>(1, this, editKeyCallback, R.string.progress_saving); mEditKeyHelper.cryptoOperation(); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 22286 Content-Disposition: inline; filename="ViewKeyFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "8cb1e03af73cbda37731313e560f69db0bf2880e" /* * Copyright (C) 2014 Dominik Schürmann * Copyright (C) 2014 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.io.IOException; import java.util.List; import android.Manifest; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.provider.ContactsContract; import android.support.v4.app.LoaderManager; import android.support.v4.content.ContextCompat; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.widget.CardView; import android.transition.Fade; import android.transition.Transition; import android.transition.TransitionInflater; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnPreDrawListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment; import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener; import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; public class ViewKeyFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks { public static final String ARG_DATA_URI = "uri"; public static final String ARG_POSTPONE_TYPE = "postpone_type"; private ListView mUserIds; enum PostponeType { NONE, LINKED } boolean mIsSecret = false; private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; private static final int LOADER_ID_LINKED_IDS = 2; private static final int LOADER_ID_LINKED_CONTACT = 3; private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID = "loader_linked_contact_master_key_id"; private static final String LOADER_EXTRA_LINKED_CONTACT_IS_SECRET = "loader_linked_contact_is_secret"; private UserIdsAdapter mUserIdsAdapter; private LinkedIdsAdapter mLinkedIdsAdapter; private Uri mDataUri; private PostponeType mPostponeType; private CardView mSystemContactCard; private LinearLayout mSystemContactLayout; private ImageView mSystemContactPicture; private TextView mSystemContactName; private ListView mLinkedIds; private CardView mLinkedIdsCard; private TextView mLinkedIdsEmpty; private byte[] mFingerprint; private TextView mLinkedIdsExpander; /** * Creates new instance of this fragment */ public static ViewKeyFragment newInstance(Uri dataUri, PostponeType postponeType) { ViewKeyFragment frag = new ViewKeyFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); args.putString(ARG_POSTPONE_TYPE, postponeType.toString()); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_fragment, getContainer()); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); Button userIdsEditButton = (Button) view.findViewById(R.id.view_key_card_user_ids_edit); mLinkedIdsCard = (CardView) view.findViewById(R.id.card_linked_ids); mLinkedIds = (ListView) view.findViewById(R.id.view_key_linked_ids); mLinkedIdsExpander = (TextView) view.findViewById(R.id.view_key_linked_ids_expander); mLinkedIdsEmpty = (TextView) view.findViewById(R.id.view_key_linked_ids_empty); Button linkedIdsAddButton = (Button) view.findViewById(R.id.view_key_card_linked_ids_add); mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card); mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout); mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name); mSystemContactPicture = (ImageView) view.findViewById(R.id.system_contact_picture); userIdsEditButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { editIdentities(mDataUri); } }); linkedIdsAddButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addLinkedIdentity(mDataUri); } }); mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { showUserIdInfo(position); } }); mLinkedIds.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { showLinkedId(position); } }); return root; } private void editIdentities(Uri dataUri) { Intent editIntent = new Intent(getActivity(), EditIdentitiesActivity.class); editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); startActivityForResult(editIntent, 0); } private void addLinkedIdentity(Uri dataUri) { Intent intent = new Intent(getActivity(), LinkedIdWizard.class); intent.setData(dataUri); startActivity(intent); getActivity().finish(); } private void showLinkedId(final int position) { final LinkedIdViewFragment frag; try { frag = mLinkedIdsAdapter.getLinkedIdFragment(mDataUri, position, mFingerprint); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Transition trans = TransitionInflater.from(getActivity()) .inflateTransition(R.transition.linked_id_card_trans); // setSharedElementReturnTransition(trans); setExitTransition(new Fade()); frag.setSharedElementEnterTransition(trans); } getFragmentManager().beginTransaction() .add(R.id.view_key_fragment, frag) .hide(frag) .commit(); frag.setOnIdentityLoadedListener(new OnIdentityLoadedListener() { @Override public void onIdentityLoaded() { new Handler().post(new Runnable() { @Override public void run() { getFragmentManager().beginTransaction() .show(frag) .addSharedElement(mLinkedIdsCard, "card_linked_ids") .remove(ViewKeyFragment.this) .addToBackStack("linked_id") .commit(); } }); } }); } private void showUserIdInfo(final int position) { if (!mIsSecret) { final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); final int isVerified = mUserIdsAdapter.getIsVerified(position); DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { public void run() { UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(isRevoked, isVerified); dialogFragment.show(getActivity().getSupportFragmentManager(), "userIdInfoDialog"); } }); } } /** * Hides card if no linked system contact exists. Sets name, picture * and onClickListener for the linked system contact's layout. * In the case of a secret key, "me" (own profile) contact details are loaded. */ private void loadLinkedSystemContact(final long contactId) { // contact doesn't exist, stop if (contactId == -1) return; final Context context = mSystemContactName.getContext(); ContactHelper contactHelper = new ContactHelper(context); String contactName = null; if (mIsSecret) {//all secret keys are linked to "me" profile in contacts List mainProfileNames = contactHelper.getMainProfileContactName(); if (mainProfileNames != null && mainProfileNames.size() > 0) { contactName = mainProfileNames.get(0); } } else { contactName = contactHelper.getContactName(contactId); } if (contactName != null) {//contact name exists for given master key showLinkedSystemContact(); mSystemContactName.setText(contactName); Bitmap picture; if (mIsSecret) { picture = contactHelper.loadMainProfilePhoto(false); } else { picture = contactHelper.loadPhotoByContactId(contactId, false); } if (picture != null) mSystemContactPicture.setImageBitmap(picture); mSystemContactLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { launchContactActivity(contactId, context); } }); } else { hideLinkedSystemContact(); } } private void hideLinkedSystemContact() { mSystemContactCard.setVisibility(View.GONE); } private void showLinkedSystemContact() { mSystemContactCard.setVisibility(View.VISIBLE); } /** * launches the default android Contacts app to view a contact with the passed * contactId (CONTACT_ID column from ContactsContract.RawContact table which is _ID column in * ContactsContract.Contact table) * * @param contactId _ID for row in ContactsContract.Contacts table */ private void launchContactActivity(final long contactId, Context context) { Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, String.valueOf(contactId)); intent.setData(uri); context.startActivity(intent); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); mPostponeType = PostponeType.valueOf(getArguments().getString(ARG_POSTPONE_TYPE)); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } loadData(dataUri); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { // if a result has been returned, display a notify if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); result.createNotify(getActivity()).show(); } else { super.onActivityResult(requestCode, resultCode, data); } } static final String[] UNIFIED_PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, KeychainContract.KeyRings.HAS_ANY_SECRET, KeychainContract.KeyRings.FINGERPRINT, KeychainContract.KeyRings.HAS_ENCRYPT }; static final int INDEX_MASTER_KEY_ID = 1; @SuppressWarnings("unused") static final int INDEX_USER_ID = 2; @SuppressWarnings("unused") static final int INDEX_IS_REVOKED = 3; @SuppressWarnings("unused") static final int INDEX_IS_EXPIRED = 4; @SuppressWarnings("unused") static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; static final int INDEX_FINGERPRINT = 7; @SuppressWarnings("unused") static final int INDEX_HAS_ENCRYPT = 8; private static final String[] RAW_CONTACT_PROJECTION = { ContactsContract.RawContacts.CONTACT_ID }; private static final int INDEX_CONTACT_ID = 0; private void loadData(Uri dataUri) { mDataUri = dataUri; Log.i(Constants.TAG, "mDataUri: " + mDataUri); // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); } @Override public Loader onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { setContentShown(false, false); Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); } case LOADER_ID_USER_IDS: { return UserIdsAdapter.createLoader(getActivity(), mDataUri); } case LOADER_ID_LINKED_IDS: { return LinkedIdsAdapter.createLoader(getActivity(), mDataUri); } case LOADER_ID_LINKED_CONTACT: { // we need a separate loader for linked contact // to ensure refreshing on verification // passed in args to explicitly specify their need long masterKeyId = args.getLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID); boolean isSecret = args.getBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET); Uri baseUri = isSecret ? ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI : ContactsContract.RawContacts.CONTENT_URI; return new CursorLoader( getActivity(), baseUri, RAW_CONTACT_PROJECTION, ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=? AND " + ContactsContract.RawContacts.DELETED + "=?", new String[]{ Constants.ACCOUNT_TYPE, Long.toString(masterKeyId), "0" // "0" for "not deleted" }, null); } default: return null; } } @Override public void onLoadFinished(Loader loader, Cursor data) { /* TODO better error handling? May cause problems when a key is deleted, * because the notification triggers faster than the activity closes. */ if (data == null) { return; } // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) switch (loader.getId()) { case LOADER_ID_UNIFIED: { if (data.getCount() == 1 && data.moveToFirst()) { mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mFingerprint = data.getBlob(INDEX_FINGERPRINT); long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); // init other things after we know if it's a secret key initUserIds(mIsSecret); initLinkedIds(mIsSecret); initLinkedContactLoader(masterKeyId, mIsSecret); initCardButtonsVisibility(mIsSecret); } break; } case LOADER_ID_USER_IDS: { setContentShown(true, false); mUserIdsAdapter.swapCursor(data); break; } case LOADER_ID_LINKED_IDS: { mLinkedIdsAdapter.swapCursor(data); if (mIsSecret) { mLinkedIdsCard.setVisibility(View.VISIBLE); mLinkedIdsEmpty.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); } else { mLinkedIdsCard.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.VISIBLE : View.GONE); mLinkedIdsEmpty.setVisibility(View.GONE); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mPostponeType == PostponeType.LINKED) { mLinkedIdsCard.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { @TargetApi(VERSION_CODES.LOLLIPOP) @Override public boolean onPreDraw() { mLinkedIdsCard.getViewTreeObserver().removeOnPreDrawListener(this); getActivity().startPostponedEnterTransition(); return true; } }); } break; } case LOADER_ID_LINKED_CONTACT: { if (data.moveToFirst()) { // if we have a linked contact long contactId = data.getLong(INDEX_CONTACT_ID); loadLinkedSystemContact(contactId); } break; } } } private void initUserIds(boolean isSecret) { mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !isSecret, null); mUserIds.setAdapter(mUserIdsAdapter); getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); } private void initLinkedIds(boolean isSecret) { if (Preferences.getPreferences(getActivity()).getExperimentalEnableLinkedIdentities()) { mLinkedIdsAdapter = new LinkedIdsAdapter(getActivity(), null, 0, isSecret, mLinkedIdsExpander); mLinkedIds.setAdapter(mLinkedIdsAdapter); getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this); } } private void initLinkedContactLoader(long masterKeyId, boolean isSecret) { if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_DENIED) { Log.w(Constants.TAG, "loading linked system contact not possible READ_CONTACTS permission denied!"); hideLinkedSystemContact(); return; } Bundle linkedContactData = new Bundle(); linkedContactData.putLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID, masterKeyId); linkedContactData.putBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET, isSecret); // initialises loader for contact query so we can listen to any updates getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this); } private void initCardButtonsVisibility(boolean isSecret) { LinearLayout buttonsUserIdsLayout = (LinearLayout) getActivity().findViewById(R.id.view_key_card_user_ids_buttons); LinearLayout buttonsLinkedIdsLayout = (LinearLayout) getActivity().findViewById(R.id.view_key_card_linked_ids_buttons); if (isSecret) { buttonsUserIdsLayout.setVisibility(View.VISIBLE); buttonsLinkedIdsLayout.setVisibility(View.VISIBLE); } else { buttonsUserIdsLayout.setVisibility(View.GONE); buttonsLinkedIdsLayout.setVisibility(View.GONE); } } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ @Override public void onLoaderReset(Loader loader) { switch (loader.getId()) { case LOADER_ID_USER_IDS: { mUserIdsAdapter.swapCursor(null); break; } case LOADER_ID_LINKED_IDS: { mLinkedIdsAdapter.swapCursor(null); break; } } } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 20533 Content-Disposition: inline; filename="ViewKeyKeybaseFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "11c032517aafffcf31a3cb134ffafecb774eb52c" /* * Copyright (C) 2014 Tim Bray * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import android.content.Intent; import android.database.Cursor; import android.graphics.Typeface; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.text.style.StyleSpan; import android.text.style.URLSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; import com.textuality.keybase.lib.KeybaseException; import com.textuality.keybase.lib.KeybaseQuery; import com.textuality.keybase.lib.Proof; import com.textuality.keybase.lib.User; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.KeybaseVerificationResult; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.OkHttpKeybaseClient; import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; public class ViewKeyKeybaseFragment extends LoaderFragment implements LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { public static final String ARG_DATA_URI = "uri"; private TextView mReportHeader; private TableLayout mProofListing; private LayoutInflater mInflater; private View mProofVerifyHeader; private TextView mProofVerifyDetail; private static final int LOADER_ID_DATABASE = 1; // for retrieving the key we’re working on private Uri mDataUri; private Proof mProof; // for CryptoOperationHelper,Callback private String mKeybaseProof; private String mKeybaseFingerprint; private CryptoOperationHelper mKeybaseOpHelper; /** * Creates new instance of this fragment */ public static ViewKeyKeybaseFragment newInstance(Uri dataUri) { ViewKeyKeybaseFragment frag = new ViewKeyKeybaseFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); frag.setArguments(args); return frag; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_adv_keybase_fragment, getContainer()); mInflater = inflater; mReportHeader = (TextView) view.findViewById(R.id.view_key_trust_cloud_narrative); mProofListing = (TableLayout) view.findViewById(R.id.view_key_proof_list); mProofVerifyHeader = view.findViewById(R.id.view_key_proof_verify_header); mProofVerifyDetail = (TextView) view.findViewById(R.id.view_key_proof_verify_detail); mReportHeader.setVisibility(View.GONE); mProofListing.setVisibility(View.GONE); mProofVerifyHeader.setVisibility(View.GONE); mProofVerifyDetail.setVisibility(View.GONE); return root; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; } mDataUri = dataUri; // retrieve the key from the database getLoaderManager().initLoader(LOADER_ID_DATABASE, null, this); } static final String[] TRUST_PROJECTION = new String[]{ KeyRings._ID, KeyRings.FINGERPRINT, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED }; static final int INDEX_TRUST_FINGERPRINT = 1; static final int INDEX_TRUST_IS_REVOKED = 2; static final int INDEX_TRUST_IS_EXPIRED = 3; static final int INDEX_UNIFIED_HAS_ANY_SECRET = 4; static final int INDEX_VERIFIED = 5; public Loader onCreateLoader(int id, Bundle args) { setContentShown(false); switch (id) { case LOADER_ID_DATABASE: { Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); return new CursorLoader(getActivity(), baseUri, TRUST_PROJECTION, null, null, null); } // decided to just use an AsyncTask for keybase, but maybe later default: return null; } } public void onLoadFinished(Loader loader, Cursor data) { /* TODO better error handling? May cause problems when a key is deleted, * because the notification triggers faster than the activity closes. */ // Avoid NullPointerExceptions... if (data.getCount() == 0) { return; } boolean nothingSpecial = true; // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) if (data.moveToFirst()) { final byte[] fp = data.getBlob(INDEX_TRUST_FINGERPRINT); final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); startSearch(fingerprint); } setContentShown(true); } private void startSearch(final String fingerprint) { final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()).getProxyPrefs(); OrbotHelper.DialogActions dialogActions = new OrbotHelper.DialogActions() { @Override public void onOrbotStarted() { new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); } @Override public void onNeutralButton() { new DescribeKey(ParcelableProxy.getForNoProxy()) .execute(fingerprint); } @Override public void onCancel() { } }; if (OrbotHelper.putOrbotInRequiredState(dialogActions, getActivity())) { new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); } } /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { // no-op in this case I think } class ResultPage { String mHeader; final List mProofs; public ResultPage(String header, List proofs) { mHeader = header; mProofs = proofs; } } /** * look for evidence from keybase in the background, make tabular version of result */ private class DescribeKey extends AsyncTask { ParcelableProxy mParcelableProxy; public DescribeKey(ParcelableProxy parcelableProxy) { mParcelableProxy = parcelableProxy; } @Override protected ResultPage doInBackground(String... args) { String fingerprint = args[0]; final ArrayList proofList = new ArrayList(); final Hashtable> proofs = new Hashtable>(); try { KeybaseQuery keybaseQuery = new KeybaseQuery(new OkHttpKeybaseClient()); keybaseQuery.setProxy(mParcelableProxy.getProxy()); User keybaseUser = User.findByFingerprint(keybaseQuery, fingerprint); for (Proof proof : keybaseUser.getProofs()) { Integer proofType = proof.getType(); appendIfOK(proofs, proofType, proof); } // a one-liner in a modern programming language for (Integer proofType : proofs.keySet()) { Proof[] x = {}; Proof[] proofsFor = proofs.get(proofType).toArray(x); if (proofsFor.length > 0) { SpannableStringBuilder ssb = new SpannableStringBuilder(); int i = 0; while (i < proofsFor.length - 1) { appendProofLinks(ssb, fingerprint, proofsFor[i]); ssb.append(", "); i++; } appendProofLinks(ssb, fingerprint, proofsFor[i]); proofList.add(formatSpannableString(ssb, getProofNarrative(proofType))); } } } catch (KeybaseException ignored) { } String prefix = ""; if (isAdded()) { prefix = getString(R.string.key_trust_results_prefix); } return new ResultPage(prefix, proofList); } private SpannableStringBuilder formatSpannableString(SpannableStringBuilder proofLinks, String proofType) { //Formatting SpannableStringBuilder with String.format() causes the links to stop working. //This method is to insert the links while reserving the links SpannableStringBuilder ssb = new SpannableStringBuilder(); ssb.append(proofType); if (proofType.contains("%s")) { int i = proofType.indexOf("%s"); ssb.replace(i, i + 2, proofLinks); } else ssb.append(proofLinks); return ssb; } private SpannableStringBuilder appendProofLinks(SpannableStringBuilder ssb, final String fingerprint, final Proof proof) throws KeybaseException { int startAt = ssb.length(); String handle = proof.getHandle(); ssb.append(handle); ssb.setSpan(new URLSpan(proof.getServiceUrl()), startAt, startAt + handle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if (haveProofFor(proof.getType())) { ssb.append("\u00a0["); startAt = ssb.length(); String verify = ""; if (isAdded()) { verify = getString(R.string.keybase_verify); } ssb.append(verify); ClickableSpan clicker = new ClickableSpan() { @Override public void onClick(View view) { verify(proof, fingerprint); } }; ssb.setSpan(clicker, startAt, startAt + verify.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append("]"); } return ssb; } @Override protected void onPostExecute(ResultPage result) { super.onPostExecute(result); // stop if fragment is no longer added to an activity if(!isAdded()) { return; } if (result.mProofs.isEmpty()) { result.mHeader = getActivity().getString(R.string.key_trust_no_cloud_evidence); } mReportHeader.setVisibility(View.VISIBLE); mProofListing.setVisibility(View.VISIBLE); mReportHeader.setText(result.mHeader); int rowNumber = 1; for (CharSequence s : result.mProofs) { TableRow row = (TableRow) mInflater.inflate(R.layout.view_key_adv_keybase_proof, null); TextView number = (TextView) row.findViewById(R.id.proof_number); TextView text = (TextView) row.findViewById(R.id.proof_text); number.setText(Integer.toString(rowNumber++) + ". "); text.setText(s); text.setMovementMethod(LinkMovementMethod.getInstance()); mProofListing.addView(row); } } } private String getProofNarrative(int proofType) { int stringIndex; switch (proofType) { case Proof.PROOF_TYPE_TWITTER: stringIndex = R.string.keybase_narrative_twitter; break; case Proof.PROOF_TYPE_GITHUB: stringIndex = R.string.keybase_narrative_github; break; case Proof.PROOF_TYPE_DNS: stringIndex = R.string.keybase_narrative_dns; break; case Proof.PROOF_TYPE_WEB_SITE: stringIndex = R.string.keybase_narrative_web_site; break; case Proof.PROOF_TYPE_HACKERNEWS: stringIndex = R.string.keybase_narrative_hackernews; break; case Proof.PROOF_TYPE_COINBASE: stringIndex = R.string.keybase_narrative_coinbase; break; case Proof.PROOF_TYPE_REDDIT: stringIndex = R.string.keybase_narrative_reddit; break; default: stringIndex = R.string.keybase_narrative_unknown; } if (isAdded()) { return getString(stringIndex); } else { return ""; } } private void appendIfOK(Hashtable> table, Integer proofType, Proof proof) throws KeybaseException { ArrayList list = table.get(proofType); if (list == null) { list = new ArrayList(); table.put(proofType, list); } list.add(proof); } // which proofs do we have working verifiers for? private boolean haveProofFor(int proofType) { switch (proofType) { case Proof.PROOF_TYPE_TWITTER: return true; case Proof.PROOF_TYPE_GITHUB: return true; case Proof.PROOF_TYPE_DNS: return true; case Proof.PROOF_TYPE_WEB_SITE: return true; case Proof.PROOF_TYPE_HACKERNEWS: return true; case Proof.PROOF_TYPE_COINBASE: return true; case Proof.PROOF_TYPE_REDDIT: return true; default: return false; } } private void verify(final Proof proof, final String fingerprint) { mProof = proof; mKeybaseProof = proof.toString(); mKeybaseFingerprint = fingerprint; mProofVerifyDetail.setVisibility(View.GONE); mKeybaseOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_verifying_signature); mKeybaseOpHelper.cryptoOperation(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (mKeybaseOpHelper != null) { mKeybaseOpHelper.handleActivityResult(requestCode, resultCode, data); } } // CryptoOperationHelper.Callback methods @Override public KeybaseVerificationParcel createOperationInput() { return new KeybaseVerificationParcel(mKeybaseProof, mKeybaseFingerprint); } @Override public void onCryptoOperationSuccess(KeybaseVerificationResult result) { result.createNotify(getActivity()).show(); String proofUrl = result.mProofUrl; String presenceUrl = result.mPresenceUrl; String presenceLabel = result.mPresenceLabel; Proof proof = mProof; // TODO: should ideally be contained in result String proofLabel; switch (proof.getType()) { case Proof.PROOF_TYPE_TWITTER: proofLabel = getString(R.string.keybase_twitter_proof); break; case Proof.PROOF_TYPE_DNS: proofLabel = getString(R.string.keybase_dns_proof); break; case Proof.PROOF_TYPE_WEB_SITE: proofLabel = getString(R.string.keybase_web_site_proof); break; case Proof.PROOF_TYPE_GITHUB: proofLabel = getString(R.string.keybase_github_proof); break; case Proof.PROOF_TYPE_REDDIT: proofLabel = getString(R.string.keybase_reddit_proof); break; default: proofLabel = getString(R.string.keybase_a_post); break; } SpannableStringBuilder ssb = new SpannableStringBuilder(); ssb.append(getString(R.string.keybase_proof_succeeded)); StyleSpan bold = new StyleSpan(Typeface.BOLD); ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append("\n\n"); int length = ssb.length(); ssb.append(proofLabel); if (proofUrl != null) { URLSpan postLink = new URLSpan(proofUrl); ssb.setSpan(postLink, length, length + proofLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } if (Proof.PROOF_TYPE_DNS == proof.getType()) { ssb.append(" ").append(getString(R.string.keybase_for_the_domain)).append(" "); } else { ssb.append(" ").append(getString(R.string.keybase_fetched_from)).append(" "); } length = ssb.length(); URLSpan presenceLink = new URLSpan(presenceUrl); ssb.append(presenceLabel); ssb.setSpan(presenceLink, length, length + presenceLabel.length(), Spanned .SPAN_EXCLUSIVE_EXCLUSIVE); if (Proof.PROOF_TYPE_REDDIT == proof.getType()) { ssb.append(", "). append(getString(R.string.keybase_reddit_attribution)). append(" “").append(proof.getHandle()).append("”, "); } ssb.append(" ").append(getString(R.string.keybase_contained_signature)); displaySpannableResult(ssb); } @Override public void onCryptoOperationCancelled() { } @Override public void onCryptoOperationError(KeybaseVerificationResult result) { result.createNotify(getActivity()).show(); SpannableStringBuilder ssb = new SpannableStringBuilder(); ssb.append(getString(R.string.keybase_proof_failure)); String msg = getString(result.getLog().getLast().mType.mMsgId); if (msg == null) { msg = getString(R.string.keybase_unknown_proof_failure); } StyleSpan bold = new StyleSpan(Typeface.BOLD); ssb.setSpan(bold, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.append("\n\n").append(msg); displaySpannableResult(ssb); } @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } private void displaySpannableResult(SpannableStringBuilder ssb) { mProofVerifyHeader.setVisibility(View.VISIBLE); mProofVerifyDetail.setVisibility(View.VISIBLE); mProofVerifyDetail.setMovementMethod(LinkMovementMethod.getInstance()); mProofVerifyDetail.setText(ssb); } } X-Content-Type-Options: nosniff Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Length: 7532 Content-Disposition: inline; filename="ViewKeySecurityTokenFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "8b773412900a6f35a36a12994225dbe6c2e26a76" /* * Copyright (C) 2015 Dominik Schürmann * Copyright (C) 2015 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.ui; import java.nio.ByteBuffer; import java.util.Arrays; import android.database.Cursor; import android.os.Bundle; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; public class ViewKeySecurityTokenFragment extends QueueingCryptoOperationFragment implements LoaderCallbacks { public static final String ARG_MASTER_KEY_ID = "master_key_id"; public static final String ARG_FINGERPRINT = "fingerprint"; public static final String ARG_USER_ID = "user_id"; public static final String ARG_CARD_AID = "aid"; private byte[][] mFingerprints; private String mUserId; private byte[] mCardAid; private long mMasterKeyId; private long[] mSubKeyIds; private Button vButton; private TextView vStatus; public static ViewKeySecurityTokenFragment newInstance(long masterKeyId, byte[] fingerprints, String userId, byte[] aid) { ViewKeySecurityTokenFragment frag = new ViewKeySecurityTokenFragment(); Bundle args = new Bundle(); args.putLong(ARG_MASTER_KEY_ID, masterKeyId); args.putByteArray(ARG_FINGERPRINT, fingerprints); args.putString(ARG_USER_ID, userId); args.putByteArray(ARG_CARD_AID, aid); frag.setArguments(args); return frag; } public ViewKeySecurityTokenFragment() { super(null); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); ByteBuffer buf = ByteBuffer.wrap(args.getByteArray(ARG_FINGERPRINT)); mFingerprints = new byte[buf.remaining()/20][]; for (int i = 0; i < mFingerprints.length; i++) { mFingerprints[i] = new byte[20]; buf.get(mFingerprints[i]); } mUserId = args.getString(ARG_USER_ID); mCardAid = args.getByteArray(ARG_CARD_AID); mMasterKeyId = args.getLong(ARG_MASTER_KEY_ID); getLoaderManager().initLoader(0, null, this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.view_key_security_token, null); TextView vSerNo = (TextView) view.findViewById(R.id.token_serno); TextView vUserId = (TextView) view.findViewById(R.id.token_userid); String serno = Hex.toHexString(mCardAid, 10, 4); vSerNo.setText(getString(R.string.security_token_serial_no, serno)); if (!mUserId.isEmpty()) { vUserId.setText(getString(R.string.security_token_key_holder, mUserId)); } else { vUserId.setText(getString(R.string.security_token_key_holder_not_set)); } vButton = (Button) view.findViewById(R.id.button_bind); vButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { promoteToSecretKey(); } }); vStatus = (TextView) view.findViewById(R.id.token_status); return view; } public void promoteToSecretKey() { long[] subKeyIds = new long[mFingerprints.length]; for (int i = 0; i < subKeyIds.length; i++) { subKeyIds[i] = KeyFormattingUtils.getKeyIdFromFingerprint(mFingerprints[i]); } // mMasterKeyId and mCardAid are already set mSubKeyIds = subKeyIds; cryptoOperation(); } public static final String[] PROJECTION = new String[]{ Keys._ID, Keys.KEY_ID, Keys.RANK, Keys.HAS_SECRET, Keys.FINGERPRINT }; // private static final int INDEX_KEY_ID = 1; // private static final int INDEX_RANK = 2; private static final int INDEX_HAS_SECRET = 3; private static final int INDEX_FINGERPRINT = 4; @Override public Loader onCreateLoader(int id, Bundle args) { return new CursorLoader(getActivity(), Keys.buildKeysUri(mMasterKeyId), PROJECTION, null, null, null); } @Override public void onLoadFinished(Loader loader, Cursor data) { if (!data.moveToFirst()) { // wut? return; } boolean allBound = true; boolean noneBound = true; do { SecretKeyType keyType = SecretKeyType.fromNum(data.getInt(INDEX_HAS_SECRET)); byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); Integer index = naiveIndexOf(mFingerprints, fingerprint); if (index == null) { continue; } if (keyType == SecretKeyType.DIVERT_TO_CARD) { noneBound = false; } else { allBound = false; } } while (data.moveToNext()); if (allBound) { vButton.setVisibility(View.GONE); vStatus.setText(R.string.security_token_status_bound); } else { vButton.setVisibility(View.VISIBLE); vStatus.setText(noneBound ? R.string.security_token_status_unbound : R.string.security_token_status_partly); } } static private Integer naiveIndexOf(byte[][] haystack, byte[] needle) { for (int i = 0; i < haystack.length; i++) { if (Arrays.equals(needle, haystack[i])) { return i; } } return null; } @Override public void onLoaderReset(Loader loader) { } @Override public PromoteKeyringParcel createOperationInput() { return new PromoteKeyringParcel(mMasterKeyId, mCardAid, mSubKeyIds); } @Override public void onQueuedOperationSuccess(PromoteKeyResult result) { result.createNotify(getActivity()).show(); } } Content-Type: text/plain; charset=UTF-8 Content-Length: 7532 Content-Disposition: inline; filename="ViewKeySecurityTokenFragment.java" Last-Modified: Sun, 09 Jun 2024 12:30:35 GMT Expires: Sun, 09 Jun 2024 12:35:35 GMT ETag: "52c917a4b49744f1622800a6910ea5e49481a6dd" /OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/

/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/