Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.gecko.gfx;
8 import org.mozilla.gecko.GeckoAccessibility;
9 import org.mozilla.gecko.GeckoAppShell;
10 import org.mozilla.gecko.GeckoEvent;
11 import org.mozilla.gecko.PrefsHelper;
12 import org.mozilla.gecko.R;
13 import org.mozilla.gecko.Tab;
14 import org.mozilla.gecko.Tabs;
15 import org.mozilla.gecko.TouchEventInterceptor;
16 import org.mozilla.gecko.ZoomConstraints;
17 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
18 import org.mozilla.gecko.mozglue.RobocopTarget;
19 import org.mozilla.gecko.EventDispatcher;
21 import android.content.Context;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Point;
27 import android.graphics.PointF;
28 import android.graphics.Rect;
29 import android.graphics.SurfaceTexture;
30 import android.os.Build;
31 import android.os.Handler;
32 import android.util.AttributeSet;
33 import android.util.DisplayMetrics;
34 import android.util.Log;
35 import android.view.KeyEvent;
36 import android.view.MotionEvent;
37 import android.view.SurfaceHolder;
38 import android.view.SurfaceView;
39 import android.view.TextureView;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.inputmethod.EditorInfo;
43 import android.view.inputmethod.InputConnection;
44 import android.widget.FrameLayout;
46 import java.nio.IntBuffer;
47 import java.util.ArrayList;
49 /**
50 * A view rendered by the layer compositor.
51 *
52 * Note that LayerView is accessed by Robocop via reflection.
53 */
54 public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener {
55 private static String LOGTAG = "GeckoLayerView";
57 private GeckoLayerClient mLayerClient;
58 private PanZoomController mPanZoomController;
59 private LayerMarginsAnimator mMarginsAnimator;
60 private GLController mGLController;
61 private InputConnectionHandler mInputConnectionHandler;
62 private LayerRenderer mRenderer;
63 /* Must be a PAINT_xxx constant */
64 private int mPaintState;
65 private int mBackgroundColor;
66 private boolean mFullScreen;
68 private SurfaceView mSurfaceView;
69 private TextureView mTextureView;
71 private Listener mListener;
73 /* This should only be modified on the Java UI thread. */
74 private final ArrayList<TouchEventInterceptor> mTouchInterceptors;
75 private final Overscroll mOverscroll;
77 /* Flags used to determine when to show the painted surface. */
78 public static final int PAINT_START = 0;
79 public static final int PAINT_BEFORE_FIRST = 1;
80 public static final int PAINT_AFTER_FIRST = 2;
82 public boolean shouldUseTextureView() {
83 // Disable TextureView support for now as it causes panning/zooming
84 // performance regressions (see bug 792259). Uncomment the code below
85 // once this bug is fixed.
86 return false;
88 /*
89 // we can only use TextureView on ICS or higher
90 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
91 Log.i(LOGTAG, "Not using TextureView: not on ICS+");
92 return false;
93 }
95 try {
96 // and then we can only use it if we have a hardware accelerated window
97 Method m = View.class.getMethod("isHardwareAccelerated", (Class[]) null);
98 return (Boolean) m.invoke(this);
99 } catch (Exception e) {
100 Log.i(LOGTAG, "Not using TextureView: caught exception checking for hw accel: " + e.toString());
101 return false;
102 } */
103 }
105 public LayerView(Context context, AttributeSet attrs) {
106 super(context, attrs);
108 mGLController = GLController.getInstance(this);
109 mPaintState = PAINT_START;
110 mBackgroundColor = Color.WHITE;
112 mTouchInterceptors = new ArrayList<TouchEventInterceptor>();
113 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
114 mOverscroll = new OverscrollEdgeEffect(this);
115 } else {
116 mOverscroll = null;
117 }
118 Tabs.registerOnTabsChangedListener(this);
119 }
121 public LayerView(Context context) {
122 this(context, null);
123 }
125 public void initializeView(EventDispatcher eventDispatcher) {
126 mLayerClient = new GeckoLayerClient(getContext(), this, eventDispatcher);
127 if (mOverscroll != null) {
128 mLayerClient.setOverscrollHandler(mOverscroll);
129 }
131 mPanZoomController = mLayerClient.getPanZoomController();
132 mMarginsAnimator = mLayerClient.getLayerMarginsAnimator();
134 mRenderer = new LayerRenderer(this);
135 mInputConnectionHandler = null;
137 setFocusable(true);
138 setFocusableInTouchMode(true);
140 GeckoAccessibility.setDelegate(this);
141 }
143 private Point getEventRadius(MotionEvent event) {
144 if (Build.VERSION.SDK_INT >= 9) {
145 return new Point((int)event.getToolMajor()/2,
146 (int)event.getToolMinor()/2);
147 }
149 float size = event.getSize();
150 DisplayMetrics displaymetrics = getContext().getResources().getDisplayMetrics();
151 size = size * Math.min(displaymetrics.heightPixels, displaymetrics.widthPixels);
152 return new Point((int)size, (int)size);
153 }
155 public void geckoConnected() {
156 // See if we want to force 16-bit colour before doing anything
157 PrefsHelper.getPref("gfx.android.rgb16.force", new PrefsHelper.PrefHandlerBase() {
158 @Override public void prefValue(String pref, boolean force16bit) {
159 if (force16bit) {
160 GeckoAppShell.setScreenDepthOverride(16);
161 }
162 }
163 });
165 mLayerClient.notifyGeckoReady();
166 addTouchInterceptor(new TouchEventInterceptor() {
167 private PointF mInitialTouchPoint = null;
169 @Override
170 public boolean onInterceptTouchEvent(View view, MotionEvent event) {
171 return false;
172 }
174 @Override
175 public boolean onTouch(View view, MotionEvent event) {
176 if (event == null) {
177 return true;
178 }
180 int action = event.getActionMasked();
181 PointF point = new PointF(event.getX(), event.getY());
182 if (action == MotionEvent.ACTION_DOWN) {
183 mInitialTouchPoint = point;
184 }
186 if (mInitialTouchPoint != null && action == MotionEvent.ACTION_MOVE) {
187 Point p = getEventRadius(event);
189 if (PointUtils.subtract(point, mInitialTouchPoint).length() <
190 Math.max(PanZoomController.CLICK_THRESHOLD, Math.min(Math.min(p.x, p.y), PanZoomController.PAN_THRESHOLD))) {
191 // Don't send the touchmove event if if the users finger hasn't moved far.
192 // Necessary for Google Maps to work correctly. See bug 771099.
193 return true;
194 } else {
195 mInitialTouchPoint = null;
196 }
197 }
199 GeckoAppShell.sendEventToGecko(GeckoEvent.createMotionEvent(event, false));
200 return true;
201 }
202 });
203 }
205 public void showSurface() {
206 // Fix this if TextureView support is turned back on above
207 mSurfaceView.setVisibility(View.VISIBLE);
208 }
210 public void hideSurface() {
211 // Fix this if TextureView support is turned back on above
212 mSurfaceView.setVisibility(View.INVISIBLE);
213 }
215 public void destroy() {
216 if (mLayerClient != null) {
217 mLayerClient.destroy();
218 }
219 if (mRenderer != null) {
220 mRenderer.destroy();
221 }
222 Tabs.unregisterOnTabsChangedListener(this);
223 }
225 public void addTouchInterceptor(final TouchEventInterceptor aTouchInterceptor) {
226 post(new Runnable() {
227 @Override
228 public void run() {
229 mTouchInterceptors.add(aTouchInterceptor);
230 }
231 });
232 }
234 public void removeTouchInterceptor(final TouchEventInterceptor aTouchInterceptor) {
235 post(new Runnable() {
236 @Override
237 public void run() {
238 mTouchInterceptors.remove(aTouchInterceptor);
239 }
240 });
241 }
243 private boolean runTouchInterceptors(MotionEvent event, boolean aOnTouch) {
244 boolean result = false;
245 for (TouchEventInterceptor i : mTouchInterceptors) {
246 if (aOnTouch) {
247 result |= i.onTouch(this, event);
248 } else {
249 result |= i.onInterceptTouchEvent(this, event);
250 }
251 }
253 return result;
254 }
256 @Override
257 public void dispatchDraw(final Canvas canvas) {
258 super.dispatchDraw(canvas);
260 // We must have a layer client to get valid viewport metrics
261 if (mLayerClient != null && mOverscroll != null) {
262 mOverscroll.draw(canvas, getViewportMetrics());
263 }
264 }
266 @Override
267 public boolean onTouchEvent(MotionEvent event) {
268 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
269 requestFocus();
270 }
272 if (runTouchInterceptors(event, false)) {
273 return true;
274 }
275 if (mPanZoomController != null && mPanZoomController.onTouchEvent(event)) {
276 return true;
277 }
278 if (runTouchInterceptors(event, true)) {
279 return true;
280 }
281 return false;
282 }
284 @Override
285 public boolean onHoverEvent(MotionEvent event) {
286 if (runTouchInterceptors(event, true)) {
287 return true;
288 }
289 return false;
290 }
292 @Override
293 public boolean onGenericMotionEvent(MotionEvent event) {
294 if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) {
295 return true;
296 }
297 return false;
298 }
300 @Override
301 protected void onAttachedToWindow() {
302 // This check should not be done before the view is attached to a window
303 // as hardware acceleration will not be enabled at that point.
304 // We must create and add the SurfaceView instance before the view tree
305 // is fully created to avoid flickering (see bug 801477).
306 if (shouldUseTextureView()) {
307 mTextureView = new TextureView(getContext());
308 mTextureView.setSurfaceTextureListener(new SurfaceTextureListener());
310 // The background is set to this color when the LayerView is
311 // created, and it will be shown immediately at startup. Shortly
312 // after, the tab's background color will be used before any content
313 // is shown.
314 mTextureView.setBackgroundColor(Color.WHITE);
315 addView(mTextureView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
316 } else {
317 // This will stop PropertyAnimator from creating a drawing cache (i.e. a bitmap)
318 // from a SurfaceView, which is just not possible (the bitmap will be transparent).
319 setWillNotCacheDrawing(false);
321 mSurfaceView = new LayerSurfaceView(getContext(), this);
322 mSurfaceView.setBackgroundColor(Color.WHITE);
323 addView(mSurfaceView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
325 SurfaceHolder holder = mSurfaceView.getHolder();
326 holder.addCallback(new SurfaceListener());
327 }
328 }
330 // Don't expose GeckoLayerClient to things outside this package; only expose it as an Object
331 GeckoLayerClient getLayerClient() { return mLayerClient; }
332 public Object getLayerClientObject() { return mLayerClient; }
334 public PanZoomController getPanZoomController() { return mPanZoomController; }
335 public LayerMarginsAnimator getLayerMarginsAnimator() { return mMarginsAnimator; }
337 public ImmutableViewportMetrics getViewportMetrics() {
338 return mLayerClient.getViewportMetrics();
339 }
341 public void abortPanning() {
342 if (mPanZoomController != null) {
343 mPanZoomController.abortPanning();
344 }
345 }
347 public PointF convertViewPointToLayerPoint(PointF viewPoint) {
348 return mLayerClient.convertViewPointToLayerPoint(viewPoint);
349 }
351 int getBackgroundColor() {
352 return mBackgroundColor;
353 }
355 @Override
356 public void setBackgroundColor(int newColor) {
357 mBackgroundColor = newColor;
358 requestRender();
359 }
361 public void setZoomConstraints(ZoomConstraints constraints) {
362 mLayerClient.setZoomConstraints(constraints);
363 }
365 public void setIsRTL(boolean aIsRTL) {
366 mLayerClient.setIsRTL(aIsRTL);
367 }
369 public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) {
370 mInputConnectionHandler = inputConnectionHandler;
371 mLayerClient.forceRedraw(null);
372 }
374 @Override
375 public Handler getHandler() {
376 if (mInputConnectionHandler != null)
377 return mInputConnectionHandler.getHandler(super.getHandler());
378 return super.getHandler();
379 }
381 @Override
382 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
383 if (mInputConnectionHandler != null)
384 return mInputConnectionHandler.onCreateInputConnection(outAttrs);
385 return null;
386 }
388 @Override
389 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
390 if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyPreIme(keyCode, event)) {
391 return true;
392 }
393 return false;
394 }
396 @Override
397 public boolean onKeyDown(int keyCode, KeyEvent event) {
398 if (mPanZoomController != null && mPanZoomController.onKeyEvent(event)) {
399 return true;
400 }
401 if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyDown(keyCode, event)) {
402 return true;
403 }
404 return false;
405 }
407 @Override
408 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
409 if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyLongPress(keyCode, event)) {
410 return true;
411 }
412 return false;
413 }
415 @Override
416 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
417 if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyMultiple(keyCode, repeatCount, event)) {
418 return true;
419 }
420 return false;
421 }
423 @Override
424 public boolean onKeyUp(int keyCode, KeyEvent event) {
425 if (mInputConnectionHandler != null && mInputConnectionHandler.onKeyUp(keyCode, event)) {
426 return true;
427 }
428 return false;
429 }
431 public boolean isIMEEnabled() {
432 if (mInputConnectionHandler != null) {
433 return mInputConnectionHandler.isIMEEnabled();
434 }
435 return false;
436 }
438 public void requestRender() {
439 if (mListener != null) {
440 mListener.renderRequested();
441 }
442 }
444 public void addLayer(Layer layer) {
445 mRenderer.addLayer(layer);
446 }
448 public void removeLayer(Layer layer) {
449 mRenderer.removeLayer(layer);
450 }
452 public void postRenderTask(RenderTask task) {
453 mRenderer.postRenderTask(task);
454 }
456 public void removeRenderTask(RenderTask task) {
457 mRenderer.removeRenderTask(task);
458 }
460 public int getMaxTextureSize() {
461 return mRenderer.getMaxTextureSize();
462 }
464 /** Used by robocop for testing purposes. Not for production use! */
465 @RobocopTarget
466 public IntBuffer getPixels() {
467 return mRenderer.getPixels();
468 }
470 /* paintState must be a PAINT_xxx constant. */
471 public void setPaintState(int paintState) {
472 mPaintState = paintState;
473 }
475 public int getPaintState() {
476 return mPaintState;
477 }
479 public LayerRenderer getRenderer() {
480 return mRenderer;
481 }
483 public void setListener(Listener listener) {
484 mListener = listener;
485 }
487 Listener getListener() {
488 return mListener;
489 }
491 public GLController getGLController() {
492 return mGLController;
493 }
495 private Bitmap getDrawable(String name) {
496 BitmapFactory.Options options = new BitmapFactory.Options();
497 options.inScaled = false;
498 Context context = getContext();
499 int resId = context.getResources().getIdentifier(name, "drawable", context.getPackageName());
500 return BitmapUtils.decodeResource(context, resId, options);
501 }
503 Bitmap getScrollbarImage() {
504 return getDrawable("scrollbar");
505 }
507 /* When using a SurfaceView (mSurfaceView != null), resizing happens in two
508 * phases. First, the LayerView changes size, then, often some frames later,
509 * the SurfaceView changes size. Because of this, we need to split the
510 * resize into two phases to avoid jittering.
511 *
512 * The first phase is the LayerView size change. mListener is notified so
513 * that a synchronous draw can be performed (otherwise a blank frame will
514 * appear).
515 *
516 * The second phase is the SurfaceView size change. At this point, the
517 * backing GL surface is resized and another synchronous draw is performed.
518 * Gecko is also sent the new window size, and this will likely cause an
519 * extra draw a few frames later, after it's re-rendered and caught up.
520 *
521 * In the case that there is no valid GL surface (for example, when
522 * resuming, or when coming back from the awesomescreen), or we're using a
523 * TextureView instead of a SurfaceView, the first phase is skipped.
524 */
525 private void onSizeChanged(int width, int height) {
526 if (!mGLController.isCompositorCreated()) {
527 return;
528 }
530 surfaceChanged(width, height);
532 if (mSurfaceView == null) {
533 return;
534 }
536 if (mListener != null) {
537 mListener.sizeChanged(width, height);
538 }
540 if (mOverscroll != null) {
541 mOverscroll.setSize(width, height);
542 }
543 }
545 private void surfaceChanged(int width, int height) {
546 mGLController.serverSurfaceChanged(width, height);
548 if (mListener != null) {
549 mListener.surfaceChanged(width, height);
550 }
552 if (mOverscroll != null) {
553 mOverscroll.setSize(width, height);
554 }
555 }
557 private void onDestroyed() {
558 mGLController.serverSurfaceDestroyed();
559 }
561 public Object getNativeWindow() {
562 if (mSurfaceView != null)
563 return mSurfaceView.getHolder();
565 return mTextureView.getSurfaceTexture();
566 }
568 @WrapElementForJNI(allowMultithread = true, stubName = "RegisterCompositorWrapper")
569 public static GLController registerCxxCompositor() {
570 try {
571 LayerView layerView = GeckoAppShell.getLayerView();
572 GLController controller = layerView.getGLController();
573 controller.compositorCreated();
574 return controller;
575 } catch (Exception e) {
576 Log.e(LOGTAG, "Error registering compositor!", e);
577 return null;
578 }
579 }
581 public interface Listener {
582 void renderRequested();
583 void sizeChanged(int width, int height);
584 void surfaceChanged(int width, int height);
585 }
587 private class SurfaceListener implements SurfaceHolder.Callback {
588 @Override
589 public void surfaceChanged(SurfaceHolder holder, int format, int width,
590 int height) {
591 onSizeChanged(width, height);
592 }
594 @Override
595 public void surfaceCreated(SurfaceHolder holder) {
596 }
598 @Override
599 public void surfaceDestroyed(SurfaceHolder holder) {
600 onDestroyed();
601 }
602 }
604 /* A subclass of SurfaceView to listen to layout changes, as
605 * View.OnLayoutChangeListener requires API level 11.
606 */
607 private class LayerSurfaceView extends SurfaceView {
608 LayerView mParent;
610 public LayerSurfaceView(Context aContext, LayerView aParent) {
611 super(aContext);
612 mParent = aParent;
613 }
615 @Override
616 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
617 if (changed) {
618 mParent.surfaceChanged(right - left, bottom - top);
619 }
620 }
621 }
623 private class SurfaceTextureListener implements TextureView.SurfaceTextureListener {
624 @Override
625 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
626 // We don't do this for surfaceCreated above because it is always followed by a surfaceChanged,
627 // but that is not the case here.
628 onSizeChanged(width, height);
629 }
631 @Override
632 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
633 onDestroyed();
634 return true; // allow Android to call release() on the SurfaceTexture, we are done drawing to it
635 }
637 @Override
638 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
639 onSizeChanged(width, height);
640 }
642 @Override
643 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
645 }
646 }
648 @RobocopTarget
649 public void addDrawListener(DrawListener listener) {
650 mLayerClient.addDrawListener(listener);
651 }
653 @RobocopTarget
654 public void removeDrawListener(DrawListener listener) {
655 mLayerClient.removeDrawListener(listener);
656 }
658 @RobocopTarget
659 public static interface DrawListener {
660 public void drawFinished();
661 }
663 @Override
664 public void setOverScrollMode(int overscrollMode) {
665 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
666 super.setOverScrollMode(overscrollMode);
667 }
668 if (mPanZoomController != null) {
669 mPanZoomController.setOverScrollMode(overscrollMode);
670 }
671 }
673 @Override
674 public int getOverScrollMode() {
675 if (mPanZoomController != null) {
676 return mPanZoomController.getOverScrollMode();
677 }
679 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
680 return super.getOverScrollMode();
681 }
682 return View.OVER_SCROLL_ALWAYS;
683 }
685 @Override
686 public void onFocusChanged (boolean gainFocus, int direction, Rect previouslyFocusedRect) {
687 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
688 GeckoAccessibility.onLayerViewFocusChanged(this, gainFocus);
689 }
691 public void setFullScreen(boolean fullScreen) {
692 mFullScreen = fullScreen;
693 }
695 public boolean isFullScreen() {
696 return mFullScreen;
697 }
699 @Override
700 public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
701 if (msg == Tabs.TabEvents.VIEWPORT_CHANGE && Tabs.getInstance().isSelectedTab(tab) && mLayerClient != null) {
702 setZoomConstraints(tab.getZoomConstraints());
703 setIsRTL(tab.getIsRTL());
704 }
705 }
707 // Public hooks for listening to metrics changing
709 public interface OnMetricsChangedListener {
710 public void onMetricsChanged(ImmutableViewportMetrics viewport);
711 public void onPanZoomStopped();
712 }
714 public void setOnMetricsChangedListener(OnMetricsChangedListener listener) {
715 mLayerClient.setOnMetricsChangedListener(listener);
716 }
717 }