• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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