1// Copyright (c) 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
5package org.chromium.android_webview.test;
6
7import android.app.Activity;
8import android.content.pm.ActivityInfo;
9import android.test.suitebuilder.annotation.SmallTest;
10import android.view.View;
11import android.view.ViewConfiguration;
12
13import org.chromium.android_webview.AwContents;
14import org.chromium.android_webview.AwSettings;
15import org.chromium.base.ThreadUtils;
16import org.chromium.base.test.util.DisabledTest;
17import org.chromium.base.test.util.Feature;
18import org.chromium.content.browser.test.util.Criteria;
19import org.chromium.content.browser.test.util.CriteriaHelper;
20
21import java.util.concurrent.Callable;
22
23/**
24 * A test suite for zooming-related methods and settings.
25 */
26public class AwZoomTest extends AwTestBase {
27    private static final long TEST_TIMEOUT_MS = 20000L;
28    private static final int CHECK_INTERVAL_MS = 100;
29
30    private TestAwContentsClient mContentsClient;
31    private AwContents mAwContents;
32
33    @Override
34    public void setUp() throws Exception {
35        super.setUp();
36        mContentsClient = new TestAwContentsClient();
37        final AwTestContainerView testContainerView =
38                createAwTestContainerViewOnMainSync(mContentsClient);
39        mAwContents = testContainerView.getAwContents();
40    }
41
42    private String getZoomableHtml() {
43        return "<html><head><meta name=\"viewport\" content=\"" +
44                "width=device-width, minimum-scale=0.5, maximum-scale=2.0, initial-scale=0.5" +
45                "\"/></head><body>Zoomable</body></html>";
46    }
47
48    private String getNonZoomableHtml() {
49        // This page can't be zoomed because its viewport fully occupies
50        // view area and is explicitly made non user-scalable.
51        return "<html><head>" +
52                "<meta name=\"viewport\" " +
53                "content=\"width=device-width,height=device-height," +
54                "initial-scale=1,maximum-scale=1,user-scalable=no\">" +
55                "</head><body>Non-zoomable</body></html>";
56    }
57
58    private boolean isMultiTouchZoomSupportedOnUiThread() throws Throwable {
59        return runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
60            @Override
61            public Boolean call() throws Exception {
62                return mAwContents.isMultiTouchZoomSupported();
63            }
64        });
65    }
66
67    private int getVisibilityOnUiThread(final View view) throws Throwable {
68        return runTestOnUiThreadAndGetResult(new Callable<Integer>() {
69            @Override
70            public Integer call() throws Exception {
71                return view.getVisibility();
72            }
73        });
74    }
75
76    private boolean canZoomInOnUiThread() throws Throwable {
77        return runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
78            @Override
79            public Boolean call() throws Exception {
80                return mAwContents.canZoomIn();
81            }
82        });
83    }
84
85    private boolean canZoomOutOnUiThread() throws Throwable {
86        return runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
87            @Override
88            public Boolean call() throws Exception {
89                return mAwContents.canZoomOut();
90            }
91        });
92    }
93
94    private View getZoomControlsOnUiThread() throws Throwable {
95        return runTestOnUiThreadAndGetResult(new Callable<View>() {
96            @Override
97            public View call() throws Exception {
98                return mAwContents.getZoomControlsForTest();
99            }
100        });
101    }
102
103    private void invokeZoomPickerOnUiThread() throws Throwable {
104        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
105            @Override
106            public void run() {
107                mAwContents.invokeZoomPicker();
108            }
109        });
110    }
111
112    private boolean zoomInOnUiThreadAndWait() throws Throwable {
113        final float previousScale = getPixelScaleOnUiThread(mAwContents);
114        if (!runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
115            @Override
116            public Boolean call() throws Exception {
117                return mAwContents.zoomIn();
118            }
119           }))
120            return false;
121        // The zoom level is updated asynchronously.
122        return waitForScaleChange(previousScale);
123    }
124
125    private boolean zoomOutOnUiThreadAndWait() throws Throwable {
126        final float previousScale = getPixelScaleOnUiThread(mAwContents);
127        if (!runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
128            @Override
129            public Boolean call() throws Exception {
130                return mAwContents.zoomOut();
131            }
132           }))
133            return false;
134        // The zoom level is updated asynchronously.
135        return waitForScaleChange(previousScale);
136    }
137
138    private boolean waitForScaleChange(final float previousScale) throws Throwable {
139        return CriteriaHelper.pollForCriteria(new Criteria() {
140                @Override
141                public boolean isSatisfied() {
142                    try {
143                        return previousScale != getPixelScaleOnUiThread(mAwContents);
144                    } catch (Throwable t) {
145                        t.printStackTrace();
146                        fail("Failed to getPixelScaleOnUiThread: " + t.toString());
147                        return false;
148                    }
149                }
150            }, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS);
151    }
152
153    private boolean waitUntilCanNotZoom() throws Throwable {
154        return CriteriaHelper.pollForCriteria(new Criteria() {
155                @Override
156                public boolean isSatisfied() {
157                    try {
158                        return !canZoomInOnUiThread() && !canZoomOutOnUiThread();
159                    } catch (Throwable t) {
160                        t.printStackTrace();
161                        fail("Failed to query canZoomIn/Out: " + t.toString());
162                        return false;
163                    }
164                }
165            }, TEST_TIMEOUT_MS, CHECK_INTERVAL_MS);
166    }
167
168    private void runMagnificationTest(boolean supportZoom) throws Throwable {
169        int onScaleChangedCallCount = mContentsClient.getOnScaleChangedHelper().getCallCount();
170        loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
171                getZoomableHtml(), "text/html", false);
172        mContentsClient.getOnScaleChangedHelper().waitForCallback(onScaleChangedCallCount);
173        getAwSettingsOnUiThread(mAwContents).setSupportZoom(supportZoom);
174        assertTrue("Should be able to zoom in", canZoomInOnUiThread());
175        assertFalse("Should not be able to zoom out", canZoomOutOnUiThread());
176
177        while (canZoomInOnUiThread()) {
178            assertTrue(zoomInOnUiThreadAndWait());
179        }
180        assertTrue("Should be able to zoom out", canZoomOutOnUiThread());
181
182        while (canZoomOutOnUiThread()) {
183            assertTrue(zoomOutOnUiThreadAndWait());
184        }
185        assertTrue("Should be able to zoom in", canZoomInOnUiThread());
186    }
187
188    /*
189    @SmallTest
190    @Feature({"AndroidWebView"})
191    http://crbug.com/239144
192    */
193    @DisabledTest
194    public void testMagnification() throws Throwable {
195        runMagnificationTest(true);
196    }
197
198    // According to Android CTS test, zoomIn/Out must work
199    // even if supportZoom is turned off.
200    /*
201    @SmallTest
202    @Feature({"AndroidWebView"})
203    http://crbug.com/239144
204    */
205    @DisabledTest
206    public void testMagnificationWithZoomSupportOff() throws Throwable {
207        runMagnificationTest(false);
208    }
209
210    @SmallTest
211    @Feature({"AndroidWebView"})
212    public void testZoomUsingMultiTouch() throws Throwable {
213        AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
214        loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
215                getZoomableHtml(), "text/html", false);
216
217        assertTrue(webSettings.supportZoom());
218        assertFalse(webSettings.getBuiltInZoomControls());
219        assertFalse(isMultiTouchZoomSupportedOnUiThread());
220
221        webSettings.setBuiltInZoomControls(true);
222        assertTrue(isMultiTouchZoomSupportedOnUiThread());
223
224        webSettings.setSupportZoom(false);
225        assertFalse(isMultiTouchZoomSupportedOnUiThread());
226    }
227
228    /*
229    @SmallTest
230    @Feature({"AndroidWebView"})
231    http://crbug.com/239144
232    */
233    @DisabledTest
234    public void testZoomControls() throws Throwable {
235        AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
236        int onScaleChangedCallCount = mContentsClient.getOnScaleChangedHelper().getCallCount();
237        loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
238                getZoomableHtml(), "text/html", false);
239        mContentsClient.getOnScaleChangedHelper().waitForCallback(onScaleChangedCallCount);
240        // It must be possible to zoom in (or zoom out) for zoom controls to be shown
241        assertTrue("Should be able to zoom in", canZoomInOnUiThread());
242
243        assertTrue(webSettings.supportZoom());
244        webSettings.setBuiltInZoomControls(true);
245        webSettings.setDisplayZoomControls(false);
246
247        // With DisplayZoomControls set to false, attempts to display zoom
248        // controls must be ignored.
249        assertNull(getZoomControlsOnUiThread());
250        invokeZoomPickerOnUiThread();
251        assertNull(getZoomControlsOnUiThread());
252
253        webSettings.setDisplayZoomControls(true);
254        assertNull(getZoomControlsOnUiThread());
255        invokeZoomPickerOnUiThread();
256        View zoomControls = getZoomControlsOnUiThread();
257        assertEquals(View.VISIBLE, getVisibilityOnUiThread(zoomControls));
258    }
259
260    @SmallTest
261    @Feature({"AndroidWebView"})
262    public void testZoomControlsOnNonZoomableContent() throws Throwable {
263        AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
264        loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
265                getNonZoomableHtml(), "text/html", false);
266
267        // ContentView must update itself according to the viewport setup.
268        waitUntilCanNotZoom();
269
270        assertTrue(webSettings.supportZoom());
271        webSettings.setBuiltInZoomControls(true);
272        webSettings.setDisplayZoomControls(true);
273        assertNull(getZoomControlsOnUiThread());
274        invokeZoomPickerOnUiThread();
275        View zoomControls = getZoomControlsOnUiThread();
276        assertEquals(View.GONE, getVisibilityOnUiThread(zoomControls));
277    }
278
279    @SmallTest
280    @Feature({"AndroidWebView"})
281    public void testZoomControlsOnOrientationChange() throws Throwable {
282        AwSettings webSettings = getAwSettingsOnUiThread(mAwContents);
283        loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
284                getZoomableHtml(), "text/html", false);
285
286        assertTrue(webSettings.supportZoom());
287        webSettings.setBuiltInZoomControls(true);
288        webSettings.setDisplayZoomControls(true);
289        invokeZoomPickerOnUiThread();
290
291        // Now force an orientation change, and try to display the zoom picker
292        // again. Make sure that we don't crash when the ZoomPicker registers
293        // it's receiver.
294
295        Activity activity = getActivity();
296        int orientation = activity.getRequestedOrientation();
297        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
298        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
299        activity.setRequestedOrientation(orientation);
300        invokeZoomPickerOnUiThread();
301
302        // We may crash shortly (as the zoom picker has a short delay in it before
303        // it tries to register it's BroadcastReceiver), so sleep to verify we don't.
304        // The delay is encoded in ZoomButtonsController#ZOOM_CONTROLS_TIMEOUT,
305        // if that changes we may need to update this test.
306        Thread.sleep(ViewConfiguration.getZoomControlsTimeout());
307    }
308}
309