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 
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
shouldOverrideUrlLoading(WebView view, String url)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 
generateProxyIntent(Uri uri)167     private Intent generateProxyIntent(Uri uri) {
168         return generateProxyIntent(
169                 mActivity, mAccount.viewIntentProxyUri, uri, mAccount.getEmailAddress());
170     }
171 
generateProxyIntent( Context context, Uri proxyUri, Uri uri, String accountName)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