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