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
18package com.android.mail.ui;
19
20import android.app.Activity;
21import android.content.ActivityNotFoundException;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ActivityInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.database.Cursor;
29import android.net.Uri;
30import android.os.ParcelFileDescriptor;
31import android.provider.Browser;
32import android.webkit.WebResourceResponse;
33import android.webkit.WebView;
34import android.webkit.WebViewClient;
35
36import com.android.mail.browse.ConversationMessage;
37import com.android.mail.providers.Account;
38import com.android.mail.providers.Attachment;
39import com.android.mail.providers.UIProvider;
40import com.android.mail.utils.LogTag;
41import com.android.mail.utils.LogUtils;
42import com.android.mail.utils.Utils;
43
44import java.io.FileInputStream;
45import java.io.FileNotFoundException;
46import java.io.InputStream;
47import 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 */
54public 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