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