1 /* 2 * Copyright (C) 2013 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.ui; 19 20 import android.app.Activity; 21 import android.content.ActivityNotFoundException; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ActivityInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ResolveInfo; 28 import android.database.Cursor; 29 import android.net.Uri; 30 import android.os.ParcelFileDescriptor; 31 import android.provider.Browser; 32 import android.webkit.WebResourceResponse; 33 import android.webkit.WebView; 34 import android.webkit.WebViewClient; 35 36 import com.android.mail.browse.ConversationMessage; 37 import com.android.mail.providers.Account; 38 import com.android.mail.providers.Attachment; 39 import com.android.mail.providers.UIProvider; 40 import com.android.mail.utils.LogTag; 41 import com.android.mail.utils.LogUtils; 42 import com.android.mail.utils.Utils; 43 44 import java.io.FileInputStream; 45 import java.io.FileNotFoundException; 46 import java.io.InputStream; 47 import java.util.List; 48 49 /** 50 * Base implementation of a web view client for the conversation views. 51 * Handles proxying the view intent so that additional information can 52 * be sent with the intent when links are clicked. 53 */ 54 public class AbstractConversationWebViewClient extends WebViewClient { 55 private static final String LOG_TAG = LogTag.getLogTag(); 56 57 private Account mAccount; 58 private Activity mActivity; 59 AbstractConversationWebViewClient(Account account)60 public AbstractConversationWebViewClient(Account account) { 61 mAccount = account; 62 } 63 setAccount(Account account)64 public void setAccount(Account account) { 65 mAccount = account; 66 } 67 setActivity(Activity activity)68 public void setActivity(Activity activity) { 69 mActivity = activity; 70 } 71 getActivity()72 public Activity getActivity() { 73 return mActivity; 74 } 75 76 /** 77 * Translates Content ID urls (CID urls) into provider queries for the associated attachment. 78 * With the attachment in hand, it's trivial to open a stream to the file containing the content 79 * of the attachment. 80 * 81 * @param uri the raw URI from the HTML document in the Webview 82 * @param message the message containing the HTML that is being rendered 83 * @return a response if a stream to the attachment file can be created from the CID URL; 84 * <tt>null</tt> if it cannot for any reason 85 */ loadCIDUri(Uri uri, ConversationMessage message)86 protected final WebResourceResponse loadCIDUri(Uri uri, ConversationMessage message) { 87 // if the url is not a CID url, we do nothing 88 if (!"cid".equals(uri.getScheme())) { 89 return null; 90 } 91 92 // cid urls can be translated to content urls 93 final String cid = uri.getSchemeSpecificPart(); 94 if (cid == null) { 95 return null; 96 } 97 98 if (message.attachmentByCidUri == null) { 99 return null; 100 } 101 102 final Uri queryUri = Uri.withAppendedPath(message.attachmentByCidUri, cid); 103 if (queryUri == null) { 104 return null; 105 } 106 107 // query for the attachment using its cid 108 final ContentResolver cr = getActivity().getContentResolver(); 109 final Cursor c = cr.query(queryUri, UIProvider.ATTACHMENT_PROJECTION, null, null, null); 110 if (c == null) { 111 return null; 112 } 113 114 // create the attachment from the cursor, if one was found 115 final Attachment target; 116 try { 117 if (!c.moveToFirst()) { 118 return null; 119 } 120 target = new Attachment(c); 121 } finally { 122 c.close(); 123 } 124 125 // try to return a response that includes a stream to the attachment data 126 try { 127 final ParcelFileDescriptor fd = cr.openFileDescriptor(target.contentUri, "r"); 128 final InputStream stream = new FileInputStream(fd.getFileDescriptor()); 129 return new WebResourceResponse(target.getContentType(), null, stream); 130 } catch (FileNotFoundException e) { 131 // if no attachment file was found return null to let webview handle it 132 return null; 133 } 134 } 135 136 @Override shouldOverrideUrlLoading(WebView view, String url)137 public boolean shouldOverrideUrlLoading(WebView view, String url) { 138 if (mActivity == null) { 139 return false; 140 } 141 142 final Uri uri = Uri.parse(url); 143 if (Utils.divertMailtoUri(mActivity, uri, mAccount)) { 144 return true; 145 } 146 147 final Intent intent; 148 if (mAccount != null && !Utils.isEmpty(mAccount.viewIntentProxyUri)) { 149 intent = generateProxyIntent(uri); 150 } else { 151 intent = new Intent(Intent.ACTION_VIEW, uri); 152 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName()); 153 intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); 154 } 155 156 boolean result = false; 157 try { 158 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 159 | Intent.FLAG_ACTIVITY_NO_ANIMATION); 160 mActivity.startActivity(intent); 161 result = true; 162 } catch (ActivityNotFoundException ex) { 163 // If no application can handle the URL, assume that the 164 // caller can handle it. 165 } 166 167 return result; 168 } 169 generateProxyIntent(Uri uri)170 private Intent generateProxyIntent(Uri uri) { 171 return generateProxyIntent( 172 mActivity, mAccount.viewIntentProxyUri, uri, mAccount.getEmailAddress()); 173 } 174 generateProxyIntent( Context context, Uri proxyUri, Uri uri, String accountName)175 public static Intent generateProxyIntent( 176 Context context, Uri proxyUri, Uri uri, String accountName) { 177 final Intent intent = new Intent(Intent.ACTION_VIEW, proxyUri); 178 intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ORIGINAL_URI, uri); 179 intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ACCOUNT_NAME, accountName); 180 181 PackageManager manager = null; 182 // We need to catch the exception to make CanvasConversationHeaderView 183 // test pass. Bug: http://b/issue?id=3470653. 184 try { 185 manager = context.getPackageManager(); 186 } catch (UnsupportedOperationException e) { 187 LogUtils.e(LOG_TAG, e, "Error getting package manager"); 188 } 189 190 if (manager != null) { 191 // Try and resolve the intent, to find an activity from this package 192 final List<ResolveInfo> resolvedActivities = manager.queryIntentActivities( 193 intent, PackageManager.MATCH_DEFAULT_ONLY); 194 195 final String packageName = context.getPackageName(); 196 197 // Now try and find one that came from this package, if one is not found, the UI 198 // provider must have specified an intent that is to be handled by a different apk. 199 // In that case, the class name will not be set on the intent, so the default 200 // intent resolution will be used. 201 for (ResolveInfo resolveInfo: resolvedActivities) { 202 final ActivityInfo activityInfo = resolveInfo.activityInfo; 203 if (packageName.equals(activityInfo.packageName)) { 204 intent.setClassName(activityInfo.packageName, activityInfo.name); 205 break; 206 } 207 } 208 } 209 210 return intent; 211 } 212 } 213