Home | History | Annotate | Download | only in browse
      1 /*
      2  * Copyright (C) 2012 Google Inc.
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mail.browse;
     19 
     20 import android.content.Context;
     21 import android.content.res.Resources;
     22 import android.graphics.Bitmap;
     23 import android.graphics.Canvas;
     24 import android.util.AttributeSet;
     25 import android.view.MotionEvent;
     26 import android.view.ScaleGestureDetector;
     27 import android.view.ScaleGestureDetector.OnScaleGestureListener;
     28 
     29 import com.android.mail.R;
     30 import com.android.mail.utils.LogTag;
     31 import com.android.mail.utils.LogUtils;
     32 
     33 import java.util.Set;
     34 import java.util.concurrent.CopyOnWriteArraySet;
     35 
     36 public class ConversationWebView extends MailWebView implements ScrollNotifier {
     37     /** The initial delay when rendering in hardware layer. */
     38     private final int mWebviewInitialDelay;
     39 
     40     private Bitmap mBitmap;
     41     private Canvas mCanvas;
     42 
     43     private boolean mUseSoftwareLayer;
     44     /**
     45      * Whether this view is user-visible; we don't bother doing supplemental software drawing
     46      * if the view is off-screen.
     47      */
     48     private boolean mVisible;
     49 
     50     /** {@link Runnable} to be run when the page is rendered in hardware layer. */
     51     private final Runnable mNotifyPageRenderedInHardwareLayer = new Runnable() {
     52         @Override
     53         public void run() {
     54             // Switch to hardware layer.
     55             mUseSoftwareLayer = false;
     56             destroyBitmap();
     57             invalidate();
     58         }
     59     };
     60 
     61     @Override
     62     public void onDraw(Canvas canvas) {
     63         // Always render in hardware layer to avoid flicker when switch.
     64         super.onDraw(canvas);
     65 
     66         // Render in software layer on top if needed, and we're visible (i.e. it's worthwhile to
     67         // do all this)
     68         if (mUseSoftwareLayer && mVisible && getWidth() > 0 && getHeight() > 0) {
     69             if (mBitmap == null) {
     70                 try {
     71                     // Create an offscreen bitmap.
     72                     mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
     73                     mCanvas = new Canvas(mBitmap);
     74                 } catch (OutOfMemoryError e) {
     75                     // just give up
     76                     mBitmap = null;
     77                     mCanvas = null;
     78                     mUseSoftwareLayer = false;
     79                 }
     80             }
     81 
     82             if (mBitmap != null) {
     83                 final int x = getScrollX();
     84                 final int y = getScrollY();
     85 
     86                 mCanvas.save();
     87                 mCanvas.translate(-x, -y);
     88                 super.onDraw(mCanvas);
     89                 mCanvas.restore();
     90 
     91                 canvas.drawBitmap(mBitmap, x, y, null /* paint */);
     92             }
     93         }
     94     }
     95 
     96     @Override
     97     public void destroy() {
     98         destroyBitmap();
     99         removeCallbacks(mNotifyPageRenderedInHardwareLayer);
    100 
    101         super.destroy();
    102     }
    103 
    104     /**
    105      * Destroys the {@link Bitmap} used for software layer.
    106      */
    107     private void destroyBitmap() {
    108         if (mBitmap != null) {
    109             mBitmap = null;
    110             mCanvas = null;
    111         }
    112     }
    113 
    114     /**
    115      * Enable this WebView to also draw to an internal software canvas until
    116      * {@link #onRenderComplete()} is called. The software draw will happen every time
    117      * a normal {@link #onDraw(Canvas)} happens, and will overwrite whatever is normally drawn
    118      * (i.e. drawn in hardware) with the results of software rendering.
    119      * <p>
    120      * This is useful when you know that the WebView draws sooner to a software layer than it does
    121      * to its normal hardware layer.
    122      */
    123     public void setUseSoftwareLayer(boolean useSoftware) {
    124         mUseSoftwareLayer = useSoftware;
    125     }
    126 
    127     /**
    128      * Notifies the {@link ConversationWebView} that it has become visible. It can use this signal
    129      * to switch between software and hardware layer.
    130      */
    131     public void onRenderComplete() {
    132         if (mUseSoftwareLayer) {
    133             // Schedule to switch from software layer to hardware layer in 1s.
    134             postDelayed(mNotifyPageRenderedInHardwareLayer, mWebviewInitialDelay);
    135         }
    136     }
    137 
    138     public void onUserVisibilityChanged(boolean visible) {
    139         mVisible = visible;
    140     }
    141 
    142     private ScaleGestureDetector mScaleDetector;
    143 
    144     private final int mViewportWidth;
    145     private final float mDensity;
    146 
    147     private final Set<ScrollListener> mScrollListeners =
    148             new CopyOnWriteArraySet<ScrollListener>();
    149 
    150     /**
    151      * True when WebView is handling a touch-- in between POINTER_DOWN and
    152      * POINTER_UP/POINTER_CANCEL.
    153      */
    154     private boolean mHandlingTouch;
    155     private boolean mIgnoringTouch;
    156 
    157     private static final String LOG_TAG = LogTag.getLogTag();
    158 
    159     public ConversationWebView(Context c) {
    160         this(c, null);
    161     }
    162 
    163     public ConversationWebView(Context c, AttributeSet attrs) {
    164         super(c, attrs);
    165 
    166         final Resources r = getResources();
    167         mViewportWidth = r.getInteger(R.integer.conversation_webview_viewport_px);
    168         mWebviewInitialDelay = r.getInteger(R.integer.webview_initial_delay);
    169         mDensity = r.getDisplayMetrics().density;
    170     }
    171 
    172     @Override
    173     public void addScrollListener(ScrollListener l) {
    174         mScrollListeners.add(l);
    175     }
    176 
    177     @Override
    178     public void removeScrollListener(ScrollListener l) {
    179         mScrollListeners.remove(l);
    180     }
    181 
    182     public void setOnScaleGestureListener(OnScaleGestureListener l) {
    183         if (l == null) {
    184             mScaleDetector = null;
    185         } else {
    186             mScaleDetector = new ScaleGestureDetector(getContext(), l);
    187         }
    188     }
    189 
    190     @Override
    191     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    192         super.onScrollChanged(l, t, oldl, oldt);
    193 
    194         for (ScrollListener listener : mScrollListeners) {
    195             listener.onNotifierScroll(l, t);
    196         }
    197     }
    198 
    199     @Override
    200     public boolean onTouchEvent(MotionEvent ev) {
    201         final int action = ev.getActionMasked();
    202 
    203         switch (action) {
    204             case MotionEvent.ACTION_DOWN:
    205                 mHandlingTouch = true;
    206                 break;
    207             case MotionEvent.ACTION_POINTER_DOWN:
    208                 LogUtils.d(LOG_TAG, "WebView disabling intercepts: POINTER_DOWN");
    209                 requestDisallowInterceptTouchEvent(true);
    210                 if (mScaleDetector != null) {
    211                     mIgnoringTouch = true;
    212                     final MotionEvent fakeCancel = MotionEvent.obtain(ev);
    213                     fakeCancel.setAction(MotionEvent.ACTION_CANCEL);
    214                     super.onTouchEvent(fakeCancel);
    215                 }
    216                 break;
    217             case MotionEvent.ACTION_CANCEL:
    218             case MotionEvent.ACTION_UP:
    219                 mHandlingTouch = false;
    220                 mIgnoringTouch = false;
    221                 break;
    222         }
    223 
    224         final boolean handled = mIgnoringTouch || super.onTouchEvent(ev);
    225 
    226         if (mScaleDetector != null) {
    227             mScaleDetector.onTouchEvent(ev);
    228         }
    229 
    230         return handled;
    231     }
    232 
    233     public boolean isHandlingTouch() {
    234         return mHandlingTouch;
    235     }
    236 
    237     public int getViewportWidth() {
    238         return mViewportWidth;
    239     }
    240 
    241     /**
    242      * Similar to {@link #getScale()}, except that it returns the initially expected scale, as
    243      * determined by the ratio of actual screen pixels to logical HTML pixels.
    244      * <p>This assumes that we are able to control the logical HTML viewport with a meta-viewport
    245      * tag.
    246      */
    247     public float getInitialScale() {
    248         // an HTML meta-viewport width of "device-width" and unspecified (medium) density means
    249         // that the default scale is effectively the screen density.
    250         return mDensity;
    251     }
    252 
    253     public int screenPxToWebPx(int screenPx) {
    254         return (int) (screenPx / getInitialScale());
    255     }
    256 
    257     public int webPxToScreenPx(int webPx) {
    258         return (int) (webPx * getInitialScale());
    259     }
    260 
    261     public float screenPxToWebPxError(int screenPx) {
    262         return screenPx / getInitialScale() - screenPxToWebPx(screenPx);
    263     }
    264 
    265     public float webPxToScreenPxError(int webPx) {
    266         return webPx * getInitialScale() - webPxToScreenPx(webPx);
    267     }
    268 
    269 }
    270