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 5 package org.chromium.content_shell; 6 7 import android.content.Context; 8 import android.graphics.drawable.ClipDrawable; 9 import android.text.TextUtils; 10 import android.util.AttributeSet; 11 import android.view.KeyEvent; 12 import android.view.View; 13 import android.view.ViewGroup; 14 import android.view.inputmethod.EditorInfo; 15 import android.view.inputmethod.InputMethodManager; 16 import android.widget.EditText; 17 import android.widget.FrameLayout; 18 import android.widget.ImageButton; 19 import android.widget.LinearLayout; 20 import android.widget.TextView; 21 import android.widget.TextView.OnEditorActionListener; 22 23 import org.chromium.base.CalledByNative; 24 import org.chromium.base.JNINamespace; 25 import org.chromium.content.browser.ContentView; 26 import org.chromium.content.browser.ContentViewClient; 27 import org.chromium.content.browser.ContentViewCore; 28 import org.chromium.content.browser.ContentViewRenderView; 29 import org.chromium.content_public.browser.LoadUrlParams; 30 import org.chromium.content_public.browser.NavigationController; 31 import org.chromium.content_public.browser.WebContents; 32 import org.chromium.ui.base.WindowAndroid; 33 34 /** 35 * Container for the various UI components that make up a shell window. 36 */ 37 @JNINamespace("content") 38 public 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