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 60 public AbstractConversationWebViewClient(Account account) { 61 mAccount = account; 62 } 63 64 public void setAccount(Account account) { 65 mAccount = account; 66 } 67 68 public void setActivity(Activity activity) { 69 mActivity = activity; 70 } 71 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 */ 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 104 // query for the attachment using its cid 105 final ContentResolver cr = getActivity().getContentResolver(); 106 final Cursor c = cr.query(queryUri, UIProvider.ATTACHMENT_PROJECTION, null, null, null); 107 if (c == null) { 108 return null; 109 } 110 111 // create the attachment from the cursor, if one was found 112 final Attachment target; 113 try { 114 if (!c.moveToFirst()) { 115 return null; 116 } 117 target = new Attachment(c); 118 } finally { 119 c.close(); 120 } 121 122 // try to return a response that includes a stream to the attachment data 123 try { 124 final ParcelFileDescriptor fd = cr.openFileDescriptor(target.contentUri, "r"); 125 final InputStream stream = new FileInputStream(fd.getFileDescriptor()); 126 return new WebResourceResponse(target.getContentType(), null, stream); 127 } catch (FileNotFoundException e) { 128 // if no attachment file was found return null to let webview handle it 129 return null; 130 } 131 } 132 133 @Override 134 public boolean shouldOverrideUrlLoading(WebView view, String url) { 135 if (mActivity == null) { 136 return false; 137 } 138 139 final Uri uri = Uri.parse(url); 140 if (Utils.divertMailtoUri(mActivity, uri, mAccount)) { 141 return true; 142 } 143 144 final Intent intent; 145 if (mAccount != null && !Utils.isEmpty(mAccount.viewIntentProxyUri)) { 146 intent = generateProxyIntent(uri); 147 } else { 148 intent = new Intent(Intent.ACTION_VIEW, uri); 149 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName()); 150 intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); 151 } 152 153 boolean result = false; 154 try { 155 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 156 | Intent.FLAG_ACTIVITY_NO_ANIMATION); 157 mActivity.startActivity(intent); 158 result = true; 159 } catch (ActivityNotFoundException ex) { 160 // If no application can handle the URL, assume that the 161 // caller can handle it. 162 } 163 164 return result; 165 } 166 167 private Intent generateProxyIntent(Uri uri) { 168 return generateProxyIntent( 169 mActivity, mAccount.viewIntentProxyUri, uri, mAccount.getEmailAddress()); 170 } 171 172 public static Intent generateProxyIntent( 173 Context context, Uri proxyUri, Uri uri, String accountName) { 174 final Intent intent = new Intent(Intent.ACTION_VIEW, proxyUri); 175 intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ORIGINAL_URI, uri); 176 intent.putExtra(UIProvider.ViewProxyExtras.EXTRA_ACCOUNT_NAME, accountName); 177 178 PackageManager manager = null; 179 // We need to catch the exception to make CanvasConversationHeaderView 180 // test pass. Bug: http://b/issue?id=3470653. 181 try { 182 manager = context.getPackageManager(); 183 } catch (UnsupportedOperationException e) { 184 LogUtils.e(LOG_TAG, e, "Error getting package manager"); 185 } 186 187 if (manager != null) { 188 // Try and resolve the intent, to find an activity from this package 189 final List<ResolveInfo> resolvedActivities = manager.queryIntentActivities( 190 intent, PackageManager.MATCH_DEFAULT_ONLY); 191 192 final String packageName = context.getPackageName(); 193 194 // Now try and find one that came from this package, if one is not found, the UI 195 // provider must have specified an intent that is to be handled by a different apk. 196 // In that case, the class name will not be set on the intent, so the default 197 // intent resolution will be used. 198 for (ResolveInfo resolveInfo: resolvedActivities) { 199 final ActivityInfo activityInfo = resolveInfo.activityInfo; 200 if (packageName.equals(activityInfo.packageName)) { 201 intent.setClassName(activityInfo.packageName, activityInfo.name); 202 break; 203 } 204 } 205 } 206 207 return intent; 208 } 209 } 210