1// Copyright 2012 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.content_shell;
6
7import android.content.Context;
8import android.graphics.drawable.ClipDrawable;
9import android.text.TextUtils;
10import android.util.AttributeSet;
11import android.view.KeyEvent;
12import android.view.View;
13import android.view.ViewGroup;
14import android.view.inputmethod.EditorInfo;
15import android.view.inputmethod.InputMethodManager;
16import android.widget.EditText;
17import android.widget.FrameLayout;
18import android.widget.ImageButton;
19import android.widget.LinearLayout;
20import android.widget.TextView;
21import android.widget.TextView.OnEditorActionListener;
22
23import org.chromium.base.CalledByNative;
24import org.chromium.base.JNINamespace;
25import org.chromium.content.browser.ContentView;
26import org.chromium.content.browser.ContentViewClient;
27import org.chromium.content.browser.ContentViewCore;
28import org.chromium.content.browser.ContentViewRenderView;
29import org.chromium.content_public.browser.LoadUrlParams;
30import org.chromium.content_public.browser.NavigationController;
31import org.chromium.content_public.browser.WebContents;
32import org.chromium.ui.base.WindowAndroid;
33
34/**
35 * Container for the various UI components that make up a shell window.
36 */
37@JNINamespace("content")
38public class Shell extends LinearLayout {
39
40    private static final long COMPLETED_PROGRESS_TIMEOUT_MS = 200;
41
42    private final Runnable mClearProgressRunnable = new Runnable() {
43        @Override
44        public void run() {
45            mProgressDrawable.setLevel(0);
46        }
47    };
48
49    private ContentViewCore mContentViewCore;
50    private WebContents mWebContents;
51    private NavigationController mNavigationController;
52    private ContentViewClient mContentViewClient;
53    private EditText mUrlTextView;
54    private ImageButton mPrevButton;
55    private ImageButton mNextButton;
56    private ImageButton mStopButton;
57    private ImageButton mReloadButton;
58
59    private ClipDrawable mProgressDrawable;
60
61    private long mNativeShell;
62    private ContentViewRenderView mContentViewRenderView;
63    private WindowAndroid mWindow;
64
65    private boolean mLoading = false;
66
67    /**
68     * Constructor for inflating via XML.
69     */
70    public Shell(Context context, AttributeSet attrs) {
71        super(context, attrs);
72    }
73
74    /**
75     * Set the SurfaceView being renderered to as soon as it is available.
76     */
77    public void setContentViewRenderView(ContentViewRenderView contentViewRenderView) {
78        FrameLayout contentViewHolder = (FrameLayout) findViewById(R.id.contentview_holder);
79        if (contentViewRenderView == null) {
80            if (mContentViewRenderView != null) {
81                contentViewHolder.removeView(mContentViewRenderView);
82            }
83        } else {
84            contentViewHolder.addView(contentViewRenderView,
85                    new FrameLayout.LayoutParams(
86                            FrameLayout.LayoutParams.MATCH_PARENT,
87                            FrameLayout.LayoutParams.MATCH_PARENT));
88        }
89        mContentViewRenderView = contentViewRenderView;
90    }
91
92    /**
93     * Initializes the Shell for use.
94     *
95     * @param nativeShell The pointer to the native Shell object.
96     * @param window The owning window for this shell.
97     * @param client The {@link ContentViewClient} to be bound to any current or new
98     *               {@link ContentViewCore}s associated with this shell.
99     */
100    public void initialize(long nativeShell, WindowAndroid window, ContentViewClient client) {
101        mNativeShell = nativeShell;
102        mWindow = window;
103        mContentViewClient = client;
104    }
105
106    /**
107     * Closes the shell and cleans up the native instance, which will handle destroying all
108     * dependencies.
109     */
110    public void close() {
111        if (mNativeShell == 0) return;
112        nativeCloseShell(mNativeShell);
113    }
114
115    @CalledByNative
116    private void onNativeDestroyed() {
117        mWindow = null;
118        mNativeShell = 0;
119        mContentViewCore.destroy();
120    }
121
122    /**
123     * @return Whether the Shell has been destroyed.
124     * @see #onNativeDestroyed()
125     */
126    public boolean isDestroyed() {
127        return mNativeShell == 0;
128    }
129
130    /**
131     * @return Whether or not the Shell is loading content.
132     */
133    public boolean isLoading() {
134        return mLoading;
135    }
136
137    @Override
138    protected void onFinishInflate() {
139        super.onFinishInflate();
140
141        mProgressDrawable = (ClipDrawable) findViewById(R.id.toolbar).getBackground();
142        initializeUrlField();
143        initializeNavigationButtons();
144    }
145
146    private void initializeUrlField() {
147        mUrlTextView = (EditText) findViewById(R.id.url);
148        mUrlTextView.setOnEditorActionListener(new OnEditorActionListener() {
149            @Override
150            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
151                if ((actionId != EditorInfo.IME_ACTION_GO) && (event == null ||
152                        event.getKeyCode() != KeyEvent.KEYCODE_ENTER ||
153                        event.getAction() != KeyEvent.ACTION_DOWN)) {
154                    return false;
155                }
156                loadUrl(mUrlTextView.getText().toString());
157                setKeyboardVisibilityForUrl(false);
158                mContentViewCore.getContainerView().requestFocus();
159                return true;
160            }
161        });
162        mUrlTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
163            @Override
164            public void onFocusChange(View v, boolean hasFocus) {
165                setKeyboardVisibilityForUrl(hasFocus);
166                mNextButton.setVisibility(hasFocus ? GONE : VISIBLE);
167                mPrevButton.setVisibility(hasFocus ? GONE : VISIBLE);
168                if (!hasFocus) {
169                    mUrlTextView.setText(mWebContents.getUrl());
170                }
171            }
172        });
173        mUrlTextView.setOnKeyListener(new OnKeyListener() {
174            @Override
175            public boolean onKey(View v, int keyCode, KeyEvent event) {
176                if (keyCode == KeyEvent.KEYCODE_BACK) {
177                    mContentViewCore.getContainerView().requestFocus();
178                    return true;
179                }
180                return false;
181            }
182        });
183    }
184
185    /**
186     * Loads an URL.  This will perform minimal amounts of sanitizing of the URL to attempt to
187     * make it valid.
188     *
189     * @param url The URL to be loaded by the shell.
190     */
191    public void loadUrl(String url) {
192        if (url == null) return;
193
194        if (TextUtils.equals(url, mWebContents.getUrl())) {
195            mNavigationController.reload(true);
196        } else {
197            mNavigationController.loadUrl(new LoadUrlParams(sanitizeUrl(url)));
198        }
199        mUrlTextView.clearFocus();
200        // TODO(aurimas): Remove this when crbug.com/174541 is fixed.
201        mContentViewCore.getContainerView().clearFocus();
202        mContentViewCore.getContainerView().requestFocus();
203    }
204
205    /**
206     * Given an URL, this performs minimal sanitizing to ensure it will be valid.
207     * @param url The url to be sanitized.
208     * @return The sanitized URL.
209     */
210    public static String sanitizeUrl(String url) {
211        if (url == null) return null;
212        if (url.startsWith("www.") || url.indexOf(":") == -1) url = "http://" + url;
213        return url;
214    }
215
216    private void initializeNavigationButtons() {
217        mPrevButton = (ImageButton) findViewById(R.id.prev);
218        mPrevButton.setOnClickListener(new OnClickListener() {
219            @Override
220            public void onClick(View v) {
221                if (mNavigationController.canGoBack()) mNavigationController.goBack();
222            }
223        });
224
225        mNextButton = (ImageButton) findViewById(R.id.next);
226        mNextButton.setOnClickListener(new OnClickListener() {
227            @Override
228            public void onClick(View v) {
229                if (mNavigationController.canGoForward()) mNavigationController.goForward();
230            }
231        });
232        mStopButton = (ImageButton) findViewById(R.id.stop);
233        mStopButton.setOnClickListener(new OnClickListener() {
234            @Override
235            public void onClick(View v) {
236                if (mLoading) mWebContents.stop();
237            }
238        });
239        mReloadButton = (ImageButton) findViewById(R.id.reload);
240        mReloadButton.setOnClickListener(new OnClickListener() {
241            @Override
242            public void onClick(View v) {
243                mNavigationController.reload(true);
244            }
245        });
246    }
247
248    @SuppressWarnings("unused")
249    @CalledByNative
250    private void onUpdateUrl(String url) {
251        mUrlTextView.setText(url);
252    }
253
254    @SuppressWarnings("unused")
255    @CalledByNative
256    private void onLoadProgressChanged(double progress) {
257        removeCallbacks(mClearProgressRunnable);
258        mProgressDrawable.setLevel((int) (10000.0 * progress));
259        if (progress == 1.0) postDelayed(mClearProgressRunnable, COMPLETED_PROGRESS_TIMEOUT_MS);
260    }
261
262    @CalledByNative
263    private void toggleFullscreenModeForTab(boolean enterFullscreen) {
264    }
265
266    @CalledByNative
267    private boolean isFullscreenForTabOrPending() {
268        return false;
269    }
270
271    @SuppressWarnings("unused")
272    @CalledByNative
273    private void setIsLoading(boolean loading) {
274        mLoading = loading;
275    }
276
277    /**
278     * Initializes the ContentView based on the native tab contents pointer passed in.
279     * @param nativeWebContents The pointer to the native tab contents object.
280     */
281    @SuppressWarnings("unused")
282    @CalledByNative
283    private void initFromNativeTabContents(long nativeWebContents) {
284        Context context = getContext();
285        mContentViewCore = new ContentViewCore(context);
286        ContentView cv = ContentView.newInstance(context, mContentViewCore);
287        mContentViewCore.initialize(cv, cv, nativeWebContents, mWindow);
288        mContentViewCore.setContentViewClient(mContentViewClient);
289        mWebContents = mContentViewCore.getWebContents();
290        mNavigationController = mWebContents.getNavigationController();
291        if (getParent() != null) mContentViewCore.onShow();
292        if (mWebContents.getUrl() != null) {
293            mUrlTextView.setText(mWebContents.getUrl());
294        }
295        ((FrameLayout) findViewById(R.id.contentview_holder)).addView(cv,
296                new FrameLayout.LayoutParams(
297                        FrameLayout.LayoutParams.MATCH_PARENT,
298                        FrameLayout.LayoutParams.MATCH_PARENT));
299        cv.requestFocus();
300        mContentViewRenderView.setCurrentContentViewCore(mContentViewCore);
301    }
302
303    /**
304     * Enable/Disable navigation(Prev/Next) button if navigation is allowed/disallowed
305     * in respective direction.
306     * @param controlId Id of button to update
307     * @param enabled enable/disable value
308     */
309    @CalledByNative
310    private void enableUiControl(int controlId, boolean enabled) {
311        if (controlId == 0) mPrevButton.setEnabled(enabled);
312        else if (controlId == 1) mNextButton.setEnabled(enabled);
313        else if (controlId == 2) {
314            mStopButton.setVisibility(enabled ? VISIBLE : GONE);
315            mReloadButton.setVisibility(enabled ? GONE : VISIBLE);
316        }
317    }
318
319    /**
320     * @return The {@link ViewGroup} currently shown by this Shell.
321     */
322    public ViewGroup getContentView() {
323        return mContentViewCore.getContainerView();
324    }
325
326    /**
327     * @return The {@link ContentViewCore} currently managing the view shown by this Shell.
328     */
329    public ContentViewCore getContentViewCore() {
330        return mContentViewCore;
331    }
332
333    private void setKeyboardVisibilityForUrl(boolean visible) {
334        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
335                Context.INPUT_METHOD_SERVICE);
336        if (visible) {
337            imm.showSoftInput(mUrlTextView, InputMethodManager.SHOW_IMPLICIT);
338        } else {
339            imm.hideSoftInputFromWindow(mUrlTextView.getWindowToken(), 0);
340        }
341    }
342
343    private static native void nativeCloseShell(long shellPtr);
344}
345