package com.fsck.k9.view; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.R; import com.fsck.k9.activity.K9Activity; import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingListener; import com.fsck.k9.crypto.CryptoProvider; import com.fsck.k9.crypto.PgpData; import com.fsck.k9.helper.Contacts; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.*; import com.fsck.k9.mail.store.LocalStore; import java.util.List; public class SingleMessageView extends LinearLayout { private boolean mScreenReaderEnabled; private MessageCryptoView mCryptoView; private MessageWebView mMessageContentView; private AccessibleWebView mAccessibleMessageContentView; private MessageHeader mHeaderContainer; private LinearLayout mAttachments; private Button mShowHiddenAttachments; private LinearLayout mHiddenAttachments; private View mShowPicturesAction; private View mShowMessageAction; private View mShowAttachmentsAction; private boolean mShowPictures; private Button mDownloadRemainder; private LayoutInflater mInflater; private Contacts mContacts; private AttachmentView.AttachmentFileDownloadCallback attachmentCallback; private LinearLayout mHeaderPlaceHolder; private LinearLayout mTitleBarHeaderContainer; private View mAttachmentsContainer; public void initialize(Activity activity) { mMessageContentView = (MessageWebView) findViewById(R.id.message_content); mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content); mMessageContentView.configure(); mHeaderPlaceHolder = (LinearLayout) findViewById(R.id.message_view_header_container); mHeaderContainer = (MessageHeader) findViewById(R.id.header_container); mAttachmentsContainer = findViewById(R.id.attachments_container); mAttachments = (LinearLayout) findViewById(R.id.attachments); mHiddenAttachments = (LinearLayout) findViewById(R.id.hidden_attachments); mHiddenAttachments.setVisibility(View.GONE); mShowHiddenAttachments = (Button) findViewById(R.id.show_hidden_attachments); mShowHiddenAttachments.setVisibility(View.GONE); mShowHiddenAttachments.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mShowHiddenAttachments.setVisibility(View.GONE); mHiddenAttachments.setVisibility(View.VISIBLE); } }); mCryptoView = (MessageCryptoView) findViewById(R.id.layout_decrypt); mCryptoView.setActivity(activity); mCryptoView.setupChildViews(); mShowPicturesAction = findViewById(R.id.show_pictures); mShowMessageAction = findViewById(R.id.show_message); mShowMessageAction.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showShowMessageAction(false); showAttachments(false); showMessageWebView(true); showShowAttachmentsAction(true); } }); mShowAttachmentsAction = findViewById(R.id.show_attachments); mShowAttachmentsAction.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showMessageWebView(false); showShowAttachmentsAction(false); showShowMessageAction(true); showAttachments(true); } }); mShowPictures = false; mContacts = Contacts.getInstance(activity); mInflater = activity.getLayoutInflater(); mDownloadRemainder = (Button) findViewById(R.id.download_remainder); mDownloadRemainder.setVisibility(View.GONE); mAttachmentsContainer.setVisibility(View.GONE); if (isScreenReaderActive(activity)) { mAccessibleMessageContentView.setVisibility(View.VISIBLE); mMessageContentView.setVisibility(View.GONE); mScreenReaderEnabled = true; } else { mAccessibleMessageContentView.setVisibility(View.GONE); mMessageContentView.setVisibility(View.VISIBLE); mScreenReaderEnabled = false; mHeaderPlaceHolder.removeView(mHeaderContainer); // the HTC version of WebView tries to force the background of the // titlebar, which is really unfair. mHeaderContainer.setBackgroundColor(((K9Activity)activity).getThemeBackgroundColor()); mTitleBarHeaderContainer = new LinearLayout(activity); mTitleBarHeaderContainer.addView(mHeaderContainer); mMessageContentView.wrapSetTitleBar(mTitleBarHeaderContainer); } } public SingleMessageView(Context context, AttributeSet attrs) { super(context, attrs); } private boolean isScreenReaderActive(Activity activity) { final String SCREENREADER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService"; final String SCREENREADER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN"; // Restrict the set of intents to only accessibility services that have // the category FEEDBACK_SPOKEN (aka, screen readers). Intent screenReaderIntent = new Intent(SCREENREADER_INTENT_ACTION); screenReaderIntent.addCategory(SCREENREADER_INTENT_CATEGORY); List screenReaders = activity.getPackageManager().queryIntentServices( screenReaderIntent, 0); ContentResolver cr = activity.getContentResolver(); Cursor cursor = null; int status = 0; for (ResolveInfo screenReader : screenReaders) { // All screen readers are expected to implement a content provider // that responds to // content://.providers.StatusProvider cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName + ".providers.StatusProvider"), null, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { // These content providers use a special cursor that only has // one element, // an integer that is 1 if the screen reader is running. status = cursor.getInt(0); if (status == 1) { return true; } } } finally { if (cursor != null) { cursor.close(); } } } return false; } public boolean showPictures() { return mShowPictures; } public void setShowPictures(Boolean show) { mShowPictures = show; } /** * Enable/disable image loading of the WebView. But always hide the * "Show pictures" button! * * @param enable true, if (network) images should be loaded. * false, otherwise. */ public void setLoadPictures(boolean enable) { mMessageContentView.blockNetworkData(!enable); setShowPictures(enable); showShowPicturesAction(false); } public Button downloadRemainderButton() { return mDownloadRemainder; } public void showShowPicturesAction(boolean show) { mShowPicturesAction.setVisibility(show ? View.VISIBLE : View.GONE); } public void showShowMessageAction(boolean show) { mShowMessageAction.setVisibility(show ? View.VISIBLE : View.GONE); } public void showShowAttachmentsAction(boolean show) { mShowAttachmentsAction.setVisibility(show ? View.VISIBLE : View.GONE); } public void setHeaders(final Message message, Account account) { try { mHeaderContainer.populate(message, account); mHeaderContainer.setVisibility(View.VISIBLE); } catch (Exception me) { Log.e(K9.LOG_TAG, "setHeaders - error", me); } } public void setShowDownloadButton(Message message) { if (message.isSet(Flag.X_DOWNLOADED_FULL)) { mDownloadRemainder.setVisibility(View.GONE); } else { mDownloadRemainder.setEnabled(true); mDownloadRemainder.setVisibility(View.VISIBLE); } } public void setOnFlagListener(OnClickListener listener) { mHeaderContainer.setOnFlagListener(listener); } public void showAllHeaders() { mHeaderContainer.onShowAdditionalHeaders(); } public boolean additionalHeadersVisible() { return mHeaderContainer.additionalHeadersVisible(); } public void displayMessageBody(Account account, Message message, PgpData pgpData) throws MessagingException { resetView(); String type; String text = pgpData.getDecryptedData(); if (text != null) { type = "text/plain"; } else { // getTextForDisplay() always returns HTML-ified content. text = ((LocalStore.LocalMessage) message).getTextForDisplay(); type = "text/html"; } if (text != null) { final String emailText = text; final String contentType = type; loadBodyFromText(message, emailText, contentType); updateCryptoLayout(account.getCryptoProvider(), pgpData, message); // If the message contains external pictures and the "Show pictures" // button wasn't already pressed, see if the user's preferences has us // showing them anyway. if (Utility.hasExternalImages(text) && !showPictures()) { Address[] from = message.getFrom(); if ((account.getShowPictures() == Account.ShowPictures.ALWAYS) || ((account.getShowPictures() == Account.ShowPictures.ONLY_FROM_CONTACTS) && // Make sure we have at least one from address (from != null && from.length > 0) && mContacts.isInContacts(from[0].getAddress()))) { setLoadPictures(true); } else { showShowPicturesAction(true); } } } else { loadBodyFromUrl("file:///android_asset/empty.html"); } } public void loadBodyFromUrl(String url) { mMessageContentView.loadUrl(url); mCryptoView.hide(); } public void loadBodyFromText(Message message, String emailText, String contentType) { if (mScreenReaderEnabled) { mAccessibleMessageContentView.loadDataWithBaseURL("http://", emailText, contentType, "utf-8", null); } else { mMessageContentView.loadDataWithBaseURL("http://", emailText, contentType, "utf-8", null); mMessageContentView.scrollTo(0, 0); } } public void updateCryptoLayout(CryptoProvider cp, PgpData pgpData, Message message) { mCryptoView.updateLayout(cp, pgpData, message); } public void showAttachments(boolean show) { mAttachmentsContainer.setVisibility(show ? View.VISIBLE : View.GONE); boolean showHidden = (show && mHiddenAttachments.getChildCount() > 0); mShowHiddenAttachments.setVisibility(showHidden ? View.VISIBLE : View.GONE); if (show) { moveHeaderToLayout(); } else { moveHeaderToWebViewTitleBar(); mHiddenAttachments.setVisibility(View.GONE); } } public void showMessageWebView(boolean show) { mMessageContentView.setVisibility(show ? View.VISIBLE : View.GONE); } public void setAttachmentsEnabled(boolean enabled) { for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) { AttachmentView attachment = (AttachmentView) mAttachments.getChildAt(i); attachment.viewButton.setEnabled(enabled); attachment.downloadButton.setEnabled(enabled); } } public void removeAllAttachments() { for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) { mAttachments.removeView(mAttachments.getChildAt(i)); } } public void renderAttachments(Part part, int depth, Message message, Account account, MessagingController controller, MessagingListener listener) throws MessagingException { if (part.getBody() instanceof Multipart) { Multipart mp = (Multipart) part.getBody(); for (int i = 0; i < mp.getCount(); i++) { renderAttachments(mp.getBodyPart(i), depth + 1, message, account, controller, listener); } } else if (part instanceof LocalStore.LocalAttachmentBodyPart) { AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null); view.setCallback(attachmentCallback); if (view.populateFromPart(part, message, account, controller, listener)) { addAttachment(view); } else { addHiddenAttachment(view); } } } public void addAttachment(View attachmentView) { mAttachments.addView(attachmentView); } public void addHiddenAttachment(View attachmentView) { mHiddenAttachments.addView(attachmentView); } public void zoom(KeyEvent event) { if (mScreenReaderEnabled) { mAccessibleMessageContentView.zoomIn(); } else { if (event.isShiftPressed()) { mMessageContentView.zoomIn(); } else { mMessageContentView.zoomOut(); } } } public void beginSelectingText() { mMessageContentView.emulateShiftHeld(); } public void resetView() { setLoadPictures(false); mMessageContentView.scrollTo(0, 0); mMessageContentView.clearView(); mAttachments.removeAllViews(); mHiddenAttachments.removeAllViews(); } public void resetHeaderView() { mHeaderContainer.setVisibility(View.GONE); } public AttachmentView.AttachmentFileDownloadCallback getAttachmentCallback() { return attachmentCallback; } public void setAttachmentCallback( AttachmentView.AttachmentFileDownloadCallback attachmentCallback) { this.attachmentCallback = attachmentCallback; } private void moveHeaderToLayout() { if (mTitleBarHeaderContainer != null && mTitleBarHeaderContainer.getChildCount() != 0) { mTitleBarHeaderContainer.removeView(mHeaderContainer); mHeaderPlaceHolder.addView(mHeaderContainer); } } private void moveHeaderToWebViewTitleBar() { if (mTitleBarHeaderContainer != null && mTitleBarHeaderContainer.getChildCount() == 0) { mHeaderPlaceHolder.removeView(mHeaderContainer); mTitleBarHeaderContainer.addView(mHeaderContainer); } } }