/* * 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.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import android.app.Activity; import android.content.ClipDescription; import android.content.Context; import android.content.Intent; import android.content.pm.LabeledIntent; 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.Bundle; import android.os.Parcelable; 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.ViewGroup; import android.widget.ImageView; 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.ViewAnimator; 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.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; // this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; 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; 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"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; public static final String ARG_CURRENT_URI = "current_uri"; private ArrayList mInputUris; private HashMap mOutputUris; private ArrayList mPendingInputUris; private ArrayList mCancelledInputUris; private Uri mCurrentInputUri; private DecryptFilesAdapter mAdapter; /** * Creates new instance of this fragment */ public static DecryptListFragment newInstance(ArrayList uris) { DecryptListFragment frag = new DecryptListFragment(); Bundle args = new Bundle(); args.putParcelableArrayList(ARG_INPUT_URIS, uris); 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()); mAdapter = new DecryptFilesAdapter(getActivity(), this); 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; } DecryptVerifyResult 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<>(mOutputUris)); outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri); } @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 outputUris = args.getParcelable(ARG_OUTPUT_URIS); ParcelableHashMap results = args.getParcelable(ARG_RESULTS); Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI); displayInputUris(inputUris, currentInputUri, cancelledUris, outputUris != null ? outputUris.getMap() : null, results != null ? results.getMap() : null ); } private void displayInputUris(ArrayList inputUris, Uri currentInputUri, ArrayList cancelledUris, HashMap outputUris, HashMap results) { mInputUris = inputUris; mCurrentInputUri = currentInputUri; mOutputUris = outputUris != null ? outputUris : new HashMap(inputUris.size()); mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList(); mPendingInputUris = new ArrayList<>(); for (final Uri uri : inputUris) { mAdapter.add(uri); if (uri.equals(mCurrentInputUri)) { continue; } if (mCancelledInputUris.contains(uri)) { mAdapter.setCancelled(uri, new OnClickListener() { @Override public void onClick(View v) { retryUri(uri); } }); continue; } if (results != null && results.containsKey(uri)) { processResult(uri, results.get(uri)); } else { mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); mPendingInputUris.add(uri); } } if (mCurrentInputUri == null) { cryptoOperation(); } } private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { File file = new File(inputUri.getPath()); File parentDir = file.exists() ? file.getParentFile() : Constants.Path.APP_DIR; File targetFile = new File(parentDir, originalFilename); FileHelper.saveFile(this, getString(R.string.title_decrypt_to_file), getString(R.string.specify_file_to_decrypt_to), targetFile, REQUEST_CODE_OUTPUT); } else { FileHelper.saveDocument(this, mimeType, originalFilename, REQUEST_CODE_OUTPUT); } } @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 decryptedFileUri = mOutputUris.get(mCurrentInputUri); Uri saveUri = data.getData(); saveFile(decryptedFileUri, saveUri); mCurrentInputUri = null; } return; } default: { super.onActivityResult(requestCode, resultCode, data); } } } private void saveFile(Uri decryptedFileUri, Uri saveUri) { 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(DecryptVerifyResult result) { final Uri uri = mCurrentInputUri; mCurrentInputUri = null; mAdapter.addResult(uri, result, null, null, null); cryptoOperation(); } @Override public void onQueuedOperationSuccess(DecryptVerifyResult result) { Uri uri = mCurrentInputUri; mCurrentInputUri = null; processResult(uri, result); cryptoOperation(); } @Override public void onCryptoOperationCancelled() { super.onCryptoOperationCancelled(); final Uri uri = mCurrentInputUri; mCurrentInputUri = null; mCancelledInputUris.add(uri); mAdapter.setCancelled(uri, new OnClickListener() { @Override public void onClick(View v) { retryUri(uri); } }); cryptoOperation(); } private void processResult(final Uri uri, final DecryptVerifyResult result) { new AsyncTask() { @Override protected Drawable doInBackground(Void... params) { Context context = getActivity(); if (result.getDecryptMetadata() == null || context == null) { return null; } String type = result.getDecryptMetadata().getMimeType(); Uri outputUri = mOutputUris.get(uri); if (type == null || outputUri == null) { return null; } TemporaryStorageProvider.setMimeType(context, outputUri, type); if (ClipDescription.compareMimeTypes(type, "image/*")) { int px = FormattingUtils.dpToPx(context, 48); Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px)); return new BitmapDrawable(context.getResources(), bitmap); } 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) { return match.loadIcon(getActivity().getPackageManager()); } return null; } @Override protected void onPostExecute(Drawable icon) { processResult(uri, result, icon); } }.execute(); } private void processResult(final Uri uri, DecryptVerifyResult result, Drawable icon) { OnClickListener onFileClick = null, onKeyClick = null; OpenPgpSignatureResult sigResult = result.getSignatureResult(); if (sigResult != null) { final long keyId = sigResult.getKeyId(); if (sigResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_KEY_MISSING) { onKeyClick = 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); } }; } } if (result.success() && result.getDecryptMetadata() != null) { onFileClick = new OnClickListener() { @Override public void onClick(View view) { displayWithViewIntent(uri); } }; } mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); } public void retryUri(Uri uri) { // never interrupt running operations! if (mCurrentInputUri != null) { return; } // un-cancel this one mCancelledInputUris.remove(uri); mPendingInputUris.add(uri); mAdapter.setCancelled(uri, null); cryptoOperation(); } public void displayWithViewIntent(final Uri uri) { Activity activity = getActivity(); if (activity == null || mCurrentInputUri != null) { return; } final Uri outputUri = mOutputUris.get(uri); final DecryptVerifyResult result = mAdapter.getItemResult(uri); if (outputUri == null || result == null) { return; } final OpenPgpMetadata metadata = result.getDecryptMetadata(); // 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())) { // this is a significant i/o operation, use an asynctask new AsyncTask() { @Override protected Intent doInBackground(Void... params) { Activity activity = getActivity(); if (activity == null) { return null; } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, "text/plain"); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); return intent; } @Override protected void onPostExecute(Intent intent) { // for result so we can possibly get a snackbar error from internal viewer Activity activity = getActivity(); if (intent == null || activity == null) { return; } LabeledIntent internalIntent = new LabeledIntent( new Intent(intent) .setClass(activity, DisplayTextActivity.class) .putExtra(DisplayTextActivity.EXTRA_METADATA, result), BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher); Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[] { internalIntent }); activity.startActivity(chooserIntent); } }.execute(); } else { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, metadata.getMimeType()); 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); activity.startActivity(chooserIntent); } } @Override public PgpDecryptVerifyInputParcel createOperationInput() { if (mCurrentInputUri == null) { if (mPendingInputUris.isEmpty()) { // nothing left to do return null; } mCurrentInputUri = mPendingInputUris.remove(0); } Uri currentOutputUri = mOutputUris.get(mCurrentInputUri); Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri); return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) .setAllowSymmetricDecryption(true); } @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; DecryptVerifyResult result = model.mResult; switch (menuItem.getItemId()) { case R.id.view_log: Intent intent = new Intent(activity, LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); activity.startActivity(intent); return true; case R.id.decrypt_save: OpenPgpMetadata metadata = result.getDecryptMetadata(); if (metadata == null) { return true; } mCurrentInputUri = model.mInputUri; askForOutputFilename(model.mInputUri, metadata.getFilename(), metadata.getMimeType()); return true; case R.id.decrypt_delete: deleteFile(activity, model.mInputUri); return true; } return false; } private void deleteFile(Activity activity, Uri uri) { if ("file".equals(uri.getScheme())) { File file = new File(uri.getPath()); if (file.delete()) { Notify.create(activity, R.string.file_delete_ok, Style.OK).show(); } else { Notify.create(activity, R.string.file_delete_none, Style.WARN).show(); } return; } if ("content".equals(uri.getScheme())) { try { int deleted = activity.getContentResolver().delete(uri, null, null); 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(); } return; } Notify.create(activity, R.string.file_delete_exception, Style.ERROR).show(); } public static class DecryptFilesAdapter extends RecyclerView.Adapter { private Context mContext; private ArrayList mDataset; private OnMenuItemClickListener mMenuItemClickListener; private ViewModel mMenuClickedModel; public class ViewModel { Context mContext; Uri mInputUri; DecryptVerifyResult mResult; Drawable mIcon; OnClickListener mOnFileClickListener; OnClickListener mOnKeyClickListener; int mProgress, mMax; String mProgressMsg; OnClickListener mCancelled; ViewModel(Context context, Uri uri) { mContext = context; mInputUri = uri; mProgress = 0; mMax = 100; mCancelled = null; } void addResult(DecryptVerifyResult result) { mResult = result; } void addIcon(Drawable icon) { mIcon = icon; } void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) { mOnFileClickListener = onFileClick; mOnKeyClickListener = onKeyClick; } 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; } // 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 !(mInputUri != null ? !mInputUri.equals(viewModel.mInputUri) : viewModel.mInputUri != null); } // 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(Context context, OnMenuItemClickListener menuItemClickListener) { mContext = context; mMenuItemClickListener = menuItemClickListener; 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) { if (holder.vAnimator.getDisplayedChild() != 3) { holder.vAnimator.setDisplayedChild(3); } holder.vCancelledRetry.setOnClickListener(model.mCancelled); } private void bindItemProgress(ViewHolder holder, ViewModel model) { if (holder.vAnimator.getDisplayedChild() != 0) { 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) { if (holder.vAnimator.getDisplayedChild() != 1) { holder.vAnimator.setDisplayedChild(1); } KeyFormattingUtils.setStatus(mContext, holder, model.mResult); final OpenPgpMetadata metadata = model.mResult.getDecryptMetadata(); String filename; if (metadata == null) { filename = mContext.getString(R.string.filename_unknown); } else if (TextUtils.isEmpty(metadata.getFilename())) { filename = mContext.getString("text/plain".equals(metadata.getMimeType()) ? R.string.filename_unknown_text : R.string.filename_unknown); } else { filename = metadata.getFilename(); } holder.vFilename.setText(filename); long size = metadata == null ? 0 : metadata.getOriginalSize(); if (size == -1 || size == 0) { holder.vFilesize.setText(""); } else { holder.vFilesize.setText(FileHelper.readableFileSize(size)); } if (model.mIcon != null) { holder.vThumbnail.setImageDrawable(model.mIcon); } else { holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); } holder.vFile.setOnClickListener(model.mOnFileClickListener); holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); holder.vContextMenu.setTag(model); holder.vContextMenu.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { mMenuClickedModel = model; PopupMenu menu = new PopupMenu(mContext, view); menu.inflate(R.menu.decrypt_item_context_menu); menu.setOnMenuItemClickListener(mMenuItemClickListener); menu.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(PopupMenu popupMenu) { mMenuClickedModel = null; } }); menu.show(); } }); } private void bindItemFailure(ViewHolder holder, final ViewModel model) { if (holder.vAnimator.getDisplayedChild() != 2) { holder.vAnimator.setDisplayedChild(2); } holder.vErrorMsg.setText(model.mResult.getLog().getLast().mType.getMsgId()); holder.vErrorViewLog.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(mContext, LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult); mContext.startActivity(intent); } }); } // Return the size of your dataset (invoked by the layout manager) @Override public int getItemCount() { return mDataset.size(); } public DecryptVerifyResult getItemResult(Uri uri) { ViewModel model = new ViewModel(mContext, 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(mContext, uri); mDataset.add(newModel); notifyItemInserted(mDataset.size()); } public void setProgress(Uri uri, int progress, int max, String msg) { ViewModel newModel = new ViewModel(mContext, uri); int pos = mDataset.indexOf(newModel); mDataset.get(pos).setProgress(progress, max, msg); notifyItemChanged(pos); } public void setCancelled(Uri uri, OnClickListener retryListener) { ViewModel newModel = new ViewModel(mContext, uri); int pos = mDataset.indexOf(newModel); mDataset.get(pos).setCancelled(retryListener); notifyItemChanged(pos); } public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, OnClickListener onFileClick, OnClickListener onKeyClick) { ViewModel model = new ViewModel(mContext, uri); int pos = mDataset.indexOf(model); model = mDataset.get(pos); model.addResult(result); if (icon != null) { model.addIcon(icon); } model.setOnClickListeners(onFileClick, onKeyClick); 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 View vFile; public TextView vFilename; public TextView vFilesize; public ImageView vThumbnail; public ImageView vEncStatusIcon; public TextView vEncStatusText; public ImageView vSigStatusIcon; public TextView vSigStatusText; public View vSignatureLayout; public TextView vSignatureName; public TextView vSignatureMail; public TextView vSignatureAction; public View vContextMenu; public TextView vErrorMsg; public ImageView vErrorViewLog; public ImageView vCancelledRetry; 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); 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); 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 = (TextView) itemView.findViewById(R.id.result_signature_action); 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); } @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 TextView getSignatureAction() { return vSignatureAction; } @Override public TextView getSignatureUserName() { return vSignatureName; } @Override public TextView getSignatureUserEmail() { return vSignatureMail; } @Override public boolean hasEncrypt() { return true; } } }