1/*
2 * Copyright (C) 2013 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
18package com.android.mail.browse;
19
20import android.content.Context;
21import android.os.Handler;
22import android.os.Looper;
23import android.util.AttributeSet;
24import android.view.MotionEvent;
25import android.webkit.WebView;
26
27import com.android.mail.utils.Clock;
28import com.android.mail.utils.LogTag;
29import com.android.mail.utils.LogUtils;
30import com.android.mail.utils.Throttle;
31
32/**
33 * A WebView designed to live within a {@link MessageScrollView}.
34 */
35public class MessageWebView extends WebView implements MessageScrollView.Touchable {
36
37    private static final String LOG_TAG = LogTag.getLogTag();
38
39    private static Handler sMainThreadHandler;
40
41    private boolean mTouched;
42
43    private static final int MIN_RESIZE_INTERVAL = 200;
44    private static final int MAX_RESIZE_INTERVAL = 300;
45    private final Clock mClock = Clock.INSTANCE;
46
47    private final Throttle mThrottle = new Throttle("MessageWebView",
48            new Runnable() {
49                @Override public void run() {
50                    performSizeChangeDelayed();
51                }
52            }, getMainThreadHandler(),
53            MIN_RESIZE_INTERVAL, MAX_RESIZE_INTERVAL);
54
55    private int mRealWidth;
56    private int mRealHeight;
57    private boolean mIgnoreNext;
58    private long mLastSizeChangeTime = -1;
59
60    public MessageWebView(Context c) {
61        this(c, null);
62    }
63
64    public MessageWebView(Context c, AttributeSet attrs) {
65        super(c, attrs);
66    }
67
68    @Override
69    public boolean wasTouched() {
70        return mTouched;
71    }
72
73    @Override
74    public void clearTouched() {
75        mTouched = false;
76    }
77
78    @Override
79    public boolean onTouchEvent(MotionEvent event) {
80        mTouched = true;
81        final boolean handled = super.onTouchEvent(event);
82        LogUtils.d(MessageScrollView.LOG_TAG,"OUT WebView.onTouch, returning handled=%s ev=%s",
83                handled, event);
84        return handled;
85    }
86
87    @Override
88    protected void onSizeChanged(int w, int h, int ow, int oh) {
89        mRealWidth = w;
90        mRealHeight = h;
91        final long now = mClock.getTime();
92        boolean recentlySized = (now - mLastSizeChangeTime < MIN_RESIZE_INTERVAL);
93
94        // It's known that the previous resize event may cause a resize event immediately. If
95        // this happens sufficiently close to the last resize event, drop it on the floor.
96        if (mIgnoreNext) {
97            mIgnoreNext = false;
98            if (recentlySized) {
99                    LogUtils.w(LOG_TAG, "Suppressing size change in MessageWebView");
100                return;
101            }
102        }
103
104        if (recentlySized) {
105            mThrottle.onEvent();
106        } else {
107            // It's been a sufficiently long time - just perform the resize as normal. This should
108            // be the normal code path.
109            performSizeChange(ow, oh);
110        }
111    }
112
113    private void performSizeChange(int ow, int oh) {
114        super.onSizeChanged(mRealWidth, mRealHeight, ow, oh);
115        mLastSizeChangeTime = mClock.getTime();
116    }
117
118    private void performSizeChangeDelayed() {
119        mIgnoreNext = true;
120        performSizeChange(getWidth(), getHeight());
121    }
122
123    /**
124     * @return a {@link Handler} tied to the main thread.
125     */
126    public static Handler getMainThreadHandler() {
127        if (sMainThreadHandler == null) {
128            // No need to synchronize -- it's okay to create an extra Handler, which will be used
129            // only once and then thrown away.
130            sMainThreadHandler = new Handler(Looper.getMainLooper());
131        }
132        return sMainThreadHandler;
133    }
134}
135