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