/* * Copyright (C) 2013 Google Inc. * Licensed to The Android Open Source Project. * * 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 com.android.mail.ui; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.Browser; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import com.android.mail.browse.ConversationMessage; import com.android.mail.providers.Account; import com.android.mail.providers.Attachment; import com.android.mail.providers.UIProvider; import com.android.mail.utils.LogTag; import com.android.mail.utils.LogUtils; import com.android.mail.utils.Utils; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.List; /** * Base implementation of a web view client for the conversation views. * Handles proxying the view intent so that additional information can * be sent with the intent when links are clicked. */ public class AbstractConversationWebViewClient extends WebViewClient { private static final String LOG_TAG = LogTag.getLogTag(); private Account mAccount; private Activity mActivity; public AbstractConversationWebViewClient(Account account) { mAccount = account; } public void setAccount(Account account) { mAccount = account; } public void setActivity(Activity activity) { mActivity = activity; } public Activity getActivity() { return mActivity; } /** * Translates Content ID urls (CID urls) into provider queries for the associated attachment. * With the attachment in hand, it's trivial to open a stream to the file containing the content * of the attachment. * * @param uri the raw URI from the HTML document in the Webview * @param message the message containing the HTML that is being rendered * @return a response if a stream to the attachment file can be created from the CID URL; * null if it cannot for any reason */ protected final WebResourceResponse loadCIDUri(Uri uri, ConversationMessage message) { // if the url is not a CID url, we do nothing if (!"cid".equals(uri.getScheme())) { return null; } // cid urls can be translated to content urls final String cid = uri.getSchemeSpecificPart(); if (cid == null) { return null; } if (message.attachmentByCidUri == null) { return null; } final Uri queryUri = Uri.withAppendedPath(message.attachmentByCidUri, cid); // query for the attachment using its cid final ContentResolver cr = getActivity().getContentResolver(); final Cursor c = cr.query(queryUri, UIProvider.ATTACHMENT_PROJECTION, null, null, null); if (c == null) { return null; } // create the attachment from the cursor, if one was found final Attachment target; try { if (!c.moveToFirst()) { return null; } target = new Attachment(c); } finally { c.close(); } // try to return a response that includes a stream to the attachment data try { final ParcelFileDescriptor fd = cr.openFileDescriptor(target.contentUri, "r"); final InputStream stream = new FileInputStream(fd.getFileDescriptor()); return new WebResourceResponse(target.getContentType(), null, stream); } catch (FileNotFoundException e) { // if no attachment file was found return null to let webview handle it return null; } } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (mActivity == null) { return false; } final Uri uri = Uri.parse(url); if (Utils.divertMailtoUri(mActivity, uri, mAccount)) { return true; } final Intent intent; if (mAccount != null && !Utils.isEmpty(mAccount.viewIntentProxyUri)) { intent = generateProxyIntent(uri); } else { intent = new Intent(Intent.ACTION_VIEW, uri); intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName()); intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); } boolean result = false; try { intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | Intent.FLAG_ACTIVITY_NO_ANIMATION); mActivity.startActivity(intent); result = true; } catch (ActivityNotFoundException ex) { // If no application can handle the URL, assume that the // caller can handle it. } return result; } private Intent generateProxyIntent(Uri uri) { return generateProxyIntent( mActivity, mAccount.viewIntentProxyUri, uri, mAccount.getEmailAddress()); } public static Intent generateProxyIntent( Context context, Uri proxyUri, Uri uri, String accountName) { final Intent intent = new Intent(Intent.ACTION_VIEW, proxyUri); intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ORIGINAL_URI, uri); intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ACCOUNT_NAME, accountName); PackageManager manager = null; // We need to catch the exception to make CanvasConversationHeaderView // test pass. Bug: http://b/issue?id=3470653. try { manager = context.getPackageManager(); } catch (UnsupportedOperationException e) { LogUtils.e(LOG_TAG, e, "Error getting package manager"); } if (manager != null) { // Try and resolve the intent, to find an activity from this package final List resolvedActivities = manager.queryIntentActivities( intent, PackageManager.MATCH_DEFAULT_ONLY); final String packageName = context.getPackageName(); // Now try and find one that came from this package, if one is not found, the UI // provider must have specified an intent that is to be handled by a different apk. // In that case, the class name will not be set on the intent, so the default // intent resolution will be used. for (ResolveInfo resolveInfo: resolvedActivities) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (packageName.equals(activityInfo.packageName)) { intent.setClassName(activityInfo.packageName, activityInfo.name); break; } } } return intent; } }