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 
     27 import com.android.mail.R;
     28 import com.android.mail.utils.LogTag;
     29 import com.android.mail.utils.LogUtils;
     30 
     31 import java.util.Set;
     32 import java.util.concurrent.CopyOnWriteArraySet;
     33 
     34 public class ConversationWebView extends MailWebView implements ScrollNotifier {
     35     /** The initial delay when rendering in hardware layer. */
     36     private final int mWebviewInitialDelay;
     37 
     38     private Bitmap mBitmap;
     39     private Canvas mCanvas;
     40 
     41     private boolean mUseSoftwareLayer;
     42     /**
     43      * Whether this view is user-visible; we don't bother doing supplemental software drawing
     44      * if the view is off-screen.
     45      */
     46     private boolean mVisible;
     47 
     48     /** {@link Runnable} to be run when the page is rendered in hardware layer. */
     49     private final Runnable mNotifyPageRenderedInHardwareLayer = new Runnable() {
     50         @Override
     51         public void run() {
     52             // Switch to hardware layer.
     53             mUseSoftwareLayer = false;
     54             destroyBitmap();
     55             invalidate();
     56         }
     57     };
     58 
     59     @Override
     60     public void onDraw(Canvas canvas) {
     61         // Always render in hardware layer to avoid flicker when switch.
     62         super.onDraw(canvas);
     63 
     64         // Render in software layer on top if needed, and we're visible (i.e. it's worthwhile to
     65         // do all this)
     66         if (mUseSoftwareLayer && mVisible && getWidth() > 0 && getHeight() > 0) {
     67             if (mBitmap == null) {
     68                 try {
     69                     // Create an offscreen bitmap.
     70                     mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
     71                     mCanvas = new Canvas(mBitmap);
     72                 } catch (OutOfMemoryError e) {
     73                     // just give up
     74                     mBitmap = null;
     75                     mCanvas = null;
     76                     mUseSoftwareLayer = false;
     77                 }
     78             }
     79 
     80             if (mBitmap != null) {
     81                 final int x = getScrollX();
     82                 final int y = getScrollY();
     83 
     84                 mCanvas.save();
     85                 mCanvas.translate(-x, -y);
     86                 super.onDraw(mCanvas);
     87                 mCanvas.restore();
     88 
     89                 canvas.drawBitmap(mBitmap, x, y, null /* paint */);
     90             }
     91         }
     92     }
     93 
     94     @Override
     95     public void destroy() {
     96         destroyBitmap();
     97         removeCallbacks(mNotifyPageRenderedInHardwareLayer);
     98 
     99         super.destroy();
    100     }
    101 
    102     /**
    103      * Destroys the {@link Bitmap} used for software layer.
    104      */
    105     private void destroyBitmap() {
    106         if (mBitmap != null) {
    107             mBitmap = null;
    108             mCanvas = null;
    109         }
    110     }
    111 
    112     /**
    113      * Enable this WebView to also draw to an internal software canvas until
    114      * {@link #onRenderComplete()} is called. The software draw will happen every time
    115      * a normal {@link #onDraw(Canvas)} happens, and will overwrite whatever is normally drawn
    116      * (i.e. drawn in hardware) with the results of software rendering.
    117      * <p>
    118      * This is useful when you know that the WebView draws sooner to a software layer than it does
    119      * to its normal hardware layer.
    120      */
    121     public void setUseSoftwareLayer(boolean useSoftware) {
    122         mUseSoftwareLayer = useSoftware;
    123     }
    124 
    125     /**
    126      * Notifies the {@link ConversationWebView} that it has become visible. It can use this signal
    127      * to switch between software and hardware layer.
    128      */
    129     public void onRenderComplete() {
    130         if (mUseSoftwareLayer) {
    131             // Schedule to switch from software layer to hardware layer in 1s.
    132             postDelayed(mNotifyPageRenderedInHardwareLayer, mWebviewInitialDelay);
    133         }
    134     }
    135 
    136     public void onUserVisibilityChanged(boolean visible) {
    137         mVisible = visible;
    138     }
    139 
    140     private final int mViewportWidth;
    141     private final float mDensity;
    142 
    143     private final Set<ScrollListener> mScrollListeners =
    144             new CopyOnWriteArraySet<ScrollListener>();
    145 
    146     /**
    147      * True when WebView is handling a touch-- in between POINTER_DOWN and
    148      * POINTER_UP/POINTER_CANCEL.
    149      */
    150     private boolean mHandlingTouch;
    151     private boolean mIgnoringTouch;
    152 
    153     private static final String LOG_TAG = LogTag.getLogTag();
    154 
    155     public ConversationWebView(Context c) {
    156         this(c, null);
    157     }
    158 
    159     public ConversationWebView(Context c, AttributeSet attrs) {
    160         super(c, attrs);
    161 
    162         final Resources r = getResources();
    163         mViewportWidth = r.getInteger(R.integer.conversation_webview_viewport_px);
    164         mWebviewInitialDelay = r.getInteger(R.integer.webview_initial_delay);
    165         mDensity = r.getDisplayMetrics().density;
    166     }
    167 
    168     @Override
    169     public void addScrollListener(ScrollListener l) {
    170         mScrollListeners.add(l);
    171     }
    172 
    173     @Override
    174     public void removeScrollListener(ScrollListener l) {
    175         mScrollListeners.remove(l);
    176     }
    177 
    178     @Override
    179     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    180         super.onScrollChanged(l, t, oldl, oldt);
    181 
    182         for (ScrollListener listener : mScrollListeners) {
    183             listener.onNotifierScroll(t);
    184         }
    185     }
    186 
    187     @Override
    188     public boolean onTouchEvent(MotionEvent ev) {
    189         final int action = ev.getActionMasked();
    190 
    191         switch (action) {
    192             case MotionEvent.ACTION_DOWN:
    193                 mHandlingTouch = true;
    194                 break;
    195             case MotionEvent.ACTION_POINTER_DOWN:
    196                 LogUtils.d(LOG_TAG, "WebView disabling intercepts: POINTER_DOWN");
    197                 requestDisallowInterceptTouchEvent(true);
    198                 break;
    199             case MotionEvent.ACTION_CANCEL:
    200             case MotionEvent.ACTION_UP:
    201                 mHandlingTouch = false;
    202                 mIgnoringTouch = false;
    203                 break;
    204         }
    205 
    206         final boolean handled = mIgnoringTouch || super.onTouchEvent(ev);
    207 
    208         return handled;
    209     }
    210 
    211     public boolean isHandlingTouch() {
    212         return mHandlingTouch;
    213     }
    214 
    215     public int getViewportWidth() {
    216         return mViewportWidth;
    217     }
    218 
    219     /**
    220      * Returns the effective width available for HTML content in DP units. This width takes into
    221      * account the given margin (in screen px) by excluding it. This is not the same as DOM width,
    222      * since the document is rendered at CSS px={@link #mViewportWidth}.
    223      *
    224      * @param sideMarginPx HTML body margin, if any (in screen px)
    225      * @return width available for HTML content (in dp)
    226      */
    227     public int getWidthInDp(int sideMarginPx) {
    228         return (int) ((getWidth() - sideMarginPx * 2) / mDensity);
    229     }
    230 
    231     /**
    232      * Similar to {@link #getScale()}, except that it returns the initially expected scale, as
    233      * determined by the ratio of actual screen pixels to logical HTML pixels.
    234      * <p>This assumes that we are able to control the logical HTML viewport with a meta-viewport
    235      * tag.
    236      */
    237     public float getInitialScale() {
    238         final float scale;
    239         if (getSettings().getLoadWithOverviewMode()) {
    240             // in overview mode (aka auto-fit mode), the base ratio is screen px : viewport px
    241             scale = (float) getWidth() / getViewportWidth();
    242         } else {
    243             // in no-zoom mode, the base ratio is just screen px : mdpi css px (i.e. density)
    244             scale = mDensity;
    245         }
    246         return scale;
    247     }
    248 
    249     public int screenPxToWebPx(int screenPx) {
    250         return (int) (screenPx / getInitialScale());
    251     }
    252 
    253     public int webPxToScreenPx(int webPx) {
    254         return (int) (webPx * getInitialScale());
    255     }
    256 
    257     public float screenPxToWebPxError(int screenPx) {
    258         return screenPx / getInitialScale() - screenPxToWebPx(screenPx);
    259     }
    260 
    261     public float webPxToScreenPxError(int webPx) {
    262         return webPx * getInitialScale() - webPxToScreenPx(webPx);
    263     }
    264 
    265 }
    266