1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.android_webview;
6
7import android.content.ContentResolver;
8import android.content.Context;
9import android.net.Uri;
10import android.os.AsyncTask;
11import android.os.Handler;
12import android.os.Message;
13import android.provider.MediaStore;
14import android.util.Log;
15import android.view.KeyEvent;
16import android.view.View;
17import android.webkit.ConsoleMessage;
18import android.webkit.ValueCallback;
19
20import org.chromium.base.ContentUriUtils;
21import org.chromium.base.ThreadUtils;
22import org.chromium.content.browser.ContentVideoView;
23import org.chromium.content.browser.ContentViewCore;
24
25/**
26 * Adapts the AwWebContentsDelegate interface to the AwContentsClient interface.
27 * This class also serves a secondary function of routing certain callbacks from the content layer
28 * to specific listener interfaces.
29 */
30class AwWebContentsDelegateAdapter extends AwWebContentsDelegate {
31    private static final String TAG = "AwWebContentsDelegateAdapter";
32
33    final AwContentsClient mContentsClient;
34    View mContainerView;
35    final Context mContext;
36
37    public AwWebContentsDelegateAdapter(AwContentsClient contentsClient,
38            View containerView, Context context) {
39        mContentsClient = contentsClient;
40        setContainerView(containerView);
41        mContext = context;
42    }
43
44    public void setContainerView(View containerView) {
45        mContainerView = containerView;
46    }
47
48    @Override
49    public void onLoadProgressChanged(int progress) {
50        mContentsClient.onProgressChanged(progress);
51    }
52
53    @Override
54    public void handleKeyboardEvent(KeyEvent event) {
55        if (event.getAction() == KeyEvent.ACTION_DOWN) {
56            int direction;
57            switch (event.getKeyCode()) {
58                case KeyEvent.KEYCODE_DPAD_DOWN:
59                    direction = View.FOCUS_DOWN;
60                    break;
61                case KeyEvent.KEYCODE_DPAD_UP:
62                    direction = View.FOCUS_UP;
63                    break;
64                case KeyEvent.KEYCODE_DPAD_LEFT:
65                    direction = View.FOCUS_LEFT;
66                    break;
67                case KeyEvent.KEYCODE_DPAD_RIGHT:
68                    direction = View.FOCUS_RIGHT;
69                    break;
70                default:
71                    direction = 0;
72                    break;
73            }
74            if (direction != 0 && tryToMoveFocus(direction)) return;
75        }
76        mContentsClient.onUnhandledKeyEvent(event);
77    }
78
79    @Override
80    public boolean takeFocus(boolean reverse) {
81        int direction =
82            (reverse == (mContainerView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL)) ?
83            View.FOCUS_RIGHT : View.FOCUS_LEFT;
84        if (tryToMoveFocus(direction)) return true;
85        direction = reverse ? View.FOCUS_UP : View.FOCUS_DOWN;
86        return tryToMoveFocus(direction);
87    }
88
89    private boolean tryToMoveFocus(int direction) {
90        View focus = mContainerView.focusSearch(direction);
91        return focus != null && focus != mContainerView && focus.requestFocus();
92    }
93
94    @Override
95    public boolean addMessageToConsole(int level, String message, int lineNumber,
96            String sourceId) {
97        ConsoleMessage.MessageLevel messageLevel = ConsoleMessage.MessageLevel.DEBUG;
98        switch(level) {
99            case LOG_LEVEL_TIP:
100                messageLevel = ConsoleMessage.MessageLevel.TIP;
101                break;
102            case LOG_LEVEL_LOG:
103                messageLevel = ConsoleMessage.MessageLevel.LOG;
104                break;
105            case LOG_LEVEL_WARNING:
106                messageLevel = ConsoleMessage.MessageLevel.WARNING;
107                break;
108            case LOG_LEVEL_ERROR:
109                messageLevel = ConsoleMessage.MessageLevel.ERROR;
110                break;
111            default:
112                Log.w(TAG, "Unknown message level, defaulting to DEBUG");
113                break;
114        }
115
116        return mContentsClient.onConsoleMessage(
117                new ConsoleMessage(message, sourceId, lineNumber, messageLevel));
118    }
119
120    @Override
121    public void onUpdateUrl(String url) {
122        // TODO: implement
123    }
124
125    @Override
126    public void openNewTab(String url, String extraHeaders, byte[] postData, int disposition,
127            boolean isRendererInitiated) {
128        // This is only called in chrome layers.
129        assert false;
130    }
131
132    @Override
133    public void closeContents() {
134        mContentsClient.onCloseWindow();
135    }
136
137    @Override
138    public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) {
139        // TODO(mkosiba) We should be using something akin to the JsResultReceiver as the
140        // callback parameter (instead of ContentViewCore) and implement a way of converting
141        // that to a pair of messages.
142        final int msgContinuePendingReload = 1;
143        final int msgCancelPendingReload = 2;
144
145        // TODO(sgurun) Remember the URL to cancel the reload behavior
146        // if it is different than the most recent NavigationController entry.
147        final Handler handler = new Handler(ThreadUtils.getUiThreadLooper()) {
148            @Override
149            public void handleMessage(Message msg) {
150                switch(msg.what) {
151                    case msgContinuePendingReload: {
152                        contentViewCore.getWebContents().getNavigationController()
153                                .continuePendingReload();
154                        break;
155                    }
156                    case msgCancelPendingReload: {
157                        contentViewCore.getWebContents().getNavigationController()
158                                .cancelPendingReload();
159                        break;
160                    }
161                    default:
162                        throw new IllegalStateException(
163                                "WebContentsDelegateAdapter: unhandled message " + msg.what);
164                }
165            }
166        };
167
168        Message resend = handler.obtainMessage(msgContinuePendingReload);
169        Message dontResend = handler.obtainMessage(msgCancelPendingReload);
170        mContentsClient.onFormResubmission(dontResend, resend);
171    }
172
173    @Override
174    public void runFileChooser(final int processId, final int renderId, final int modeFlags,
175            String acceptTypes, String title, String defaultFilename, boolean capture) {
176        AwContentsClient.FileChooserParams params = new AwContentsClient.FileChooserParams();
177        params.mode = modeFlags;
178        params.acceptTypes = acceptTypes;
179        params.title = title;
180        params.defaultFilename = defaultFilename;
181        params.capture = capture;
182
183        mContentsClient.showFileChooser(new ValueCallback<String[]>() {
184            boolean mCompleted = false;
185            @Override
186            public void onReceiveValue(String[] results) {
187                if (mCompleted) {
188                    throw new IllegalStateException("Duplicate showFileChooser result");
189                }
190                mCompleted = true;
191                if (results == null) {
192                    nativeFilesSelectedInChooser(
193                            processId, renderId, modeFlags, null, null);
194                    return;
195                }
196                GetDisplayNameTask task = new GetDisplayNameTask(
197                        mContext.getContentResolver(), processId, renderId, modeFlags, results);
198                task.execute();
199            }
200        }, params);
201    }
202
203    @Override
204    public boolean addNewContents(boolean isDialog, boolean isUserGesture) {
205        return mContentsClient.onCreateWindow(isDialog, isUserGesture);
206    }
207
208    @Override
209    public void activateContents() {
210        mContentsClient.onRequestFocus();
211    }
212
213    @Override
214    public void toggleFullscreenModeForTab(boolean enterFullscreen) {
215        if (!enterFullscreen) {
216            ContentVideoView videoView = ContentVideoView.getContentVideoView();
217            if (videoView != null) videoView.exitFullscreen(false);
218        }
219    }
220
221    private static class GetDisplayNameTask extends AsyncTask<Void, Void, String[]> {
222        final int mProcessId;
223        final int mRenderId;
224        final int mModeFlags;
225        final String[] mFilePaths;
226        final ContentResolver mContentResolver;
227
228        public GetDisplayNameTask(ContentResolver contentResolver, int processId, int renderId,
229                                  int modeFlags, String[] filePaths) {
230            mProcessId = processId;
231            mRenderId = renderId;
232            mModeFlags = modeFlags;
233            mFilePaths = filePaths;
234            mContentResolver = contentResolver;
235        }
236
237        @Override
238        protected String[] doInBackground(Void...voids) {
239            String[] displayNames = new String[mFilePaths.length];
240            for (int i = 0; i < mFilePaths.length; i++) {
241                displayNames[i] = resolveFileName(mFilePaths[i]);
242            }
243            return displayNames;
244        }
245
246        @Override
247        protected void onPostExecute(String[] result) {
248            nativeFilesSelectedInChooser(mProcessId, mRenderId, mModeFlags, mFilePaths, result);
249        }
250
251        /**
252         * @return the display name of a path if it is a content URI and is present in the database
253         * or an empty string otherwise.
254         */
255        private String resolveFileName(String filePath) {
256            if (mContentResolver == null || filePath == null) return "";
257            Uri uri = Uri.parse(filePath);
258            return ContentUriUtils.getDisplayName(
259                    uri, mContentResolver, MediaStore.MediaColumns.DISPLAY_NAME);
260        }
261    }
262}
263