1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); you 5 * may not use this file except in compliance with the License. You may obtain a 6 * copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.ide.eclipse.tests.functests.layoutRendering; 18 19 import com.android.SdkConstants; 20 import com.android.ide.common.rendering.LayoutLibrary; 21 import com.android.ide.common.rendering.api.ActionBarCallback; 22 import com.android.ide.common.rendering.api.AdapterBinding; 23 import com.android.ide.common.rendering.api.HardwareConfig; 24 import com.android.ide.common.rendering.api.ILayoutPullParser; 25 import com.android.ide.common.rendering.api.IProjectCallback; 26 import com.android.ide.common.rendering.api.RenderSession; 27 import com.android.ide.common.rendering.api.ResourceReference; 28 import com.android.ide.common.rendering.api.ResourceValue; 29 import com.android.ide.common.rendering.api.SessionParams; 30 import com.android.ide.common.rendering.api.SessionParams.RenderingMode; 31 import com.android.ide.common.resources.ResourceItem; 32 import com.android.ide.common.resources.ResourceRepository; 33 import com.android.ide.common.resources.ResourceResolver; 34 import com.android.ide.common.resources.configuration.DensityQualifier; 35 import com.android.ide.common.resources.configuration.FolderConfiguration; 36 import com.android.ide.common.resources.configuration.KeyboardStateQualifier; 37 import com.android.ide.common.resources.configuration.NavigationMethodQualifier; 38 import com.android.ide.common.resources.configuration.NavigationStateQualifier; 39 import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; 40 import com.android.ide.common.resources.configuration.ScreenHeightQualifier; 41 import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; 42 import com.android.ide.common.resources.configuration.ScreenRatioQualifier; 43 import com.android.ide.common.resources.configuration.ScreenSizeQualifier; 44 import com.android.ide.common.resources.configuration.ScreenWidthQualifier; 45 import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier; 46 import com.android.ide.common.resources.configuration.TextInputMethodQualifier; 47 import com.android.ide.common.resources.configuration.TouchScreenQualifier; 48 import com.android.ide.common.sdk.LoadStatus; 49 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 50 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 51 import com.android.ide.eclipse.tests.SdkLoadingTestCase; 52 import com.android.io.FolderWrapper; 53 import com.android.resources.Density; 54 import com.android.resources.Keyboard; 55 import com.android.resources.KeyboardState; 56 import com.android.resources.Navigation; 57 import com.android.resources.NavigationState; 58 import com.android.resources.ResourceType; 59 import com.android.resources.ScreenOrientation; 60 import com.android.resources.ScreenRatio; 61 import com.android.resources.ScreenSize; 62 import com.android.resources.TouchScreen; 63 import com.android.sdklib.IAndroidTarget; 64 import com.android.util.Pair; 65 66 import org.kxml2.io.KXmlParser; 67 import org.xmlpull.v1.XmlPullParser; 68 import org.xmlpull.v1.XmlPullParserException; 69 70 import java.io.File; 71 import java.io.FileReader; 72 import java.io.IOException; 73 import java.util.HashMap; 74 import java.util.Map; 75 76 import javax.imageio.ImageIO; 77 78 public class ApiDemosRenderingTest extends SdkLoadingTestCase { 79 80 /** 81 * Custom parser that implements {@link ILayoutPullParser} (which itself extends 82 * {@link XmlPullParser}). 83 */ 84 private final static class TestParser extends KXmlParser implements ILayoutPullParser { 85 /** 86 * Since we're not going to go through the result of the rendering/layout, we can return 87 * null for the View Key. 88 */ 89 @Override 90 public Object getViewCookie() { 91 return null; 92 } 93 94 @Override 95 public ILayoutPullParser getParser(String layoutName) { 96 return null; 97 } 98 } 99 100 private final static class ProjectCallBack implements IProjectCallback { 101 // resource id counter. 102 // We start at 0x7f000000 to avoid colliding with the framework id 103 // since we have no access to the project R.java and we need to generate them automatically. 104 private int mIdCounter = 0x7f000000; 105 106 // in some cases, the id that getResourceValue(String type, String name) returns 107 // will be sent back to get the type/name. This map stores the id/type/name we generate 108 // to be able to do the reverse resolution. 109 private Map<Integer, Pair<ResourceType, String>> mResourceMap = 110 new HashMap<Integer, Pair<ResourceType, String>>(); 111 112 private boolean mCustomViewAttempt = false; 113 114 @Override 115 public String getNamespace() { 116 // TODO: read from the ApiDemos manifest. 117 return "com.example.android.apis"; 118 } 119 120 @Override 121 @SuppressWarnings("unchecked") 122 public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs) 123 throws ClassNotFoundException, Exception { 124 mCustomViewAttempt = true; 125 return null; 126 } 127 128 @Override 129 public Integer getResourceId(ResourceType type, String name) { 130 Integer result = ++mIdCounter; 131 mResourceMap.put(result, Pair.of(type, name)); 132 return result; 133 } 134 135 @Override 136 public Pair<ResourceType, String> resolveResourceId(int id) { 137 return mResourceMap.get(id); 138 } 139 140 @Override 141 public String resolveResourceId(int[] id) { 142 return null; 143 } 144 145 @Override 146 public ILayoutPullParser getParser(String layoutName) { 147 return null; 148 } 149 150 @Override 151 public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, 152 ResourceReference itemRef, int fullPosition, int typePosition, 153 int fullChildPosition, int typeChildPosition, 154 ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue) { 155 return null; 156 } 157 158 @Override 159 public AdapterBinding getAdapterBinding(ResourceReference adapterView, 160 Object adapterCookie, Object viewObject) { 161 return null; 162 } 163 164 @Override 165 public ILayoutPullParser getParser(ResourceValue layoutResource) { 166 return null; 167 } 168 169 @Override 170 public ActionBarCallback getActionBarCallback() { 171 return new ActionBarCallback(); 172 } 173 } 174 175 public void testApiDemos() throws IOException, XmlPullParserException { 176 findApiDemos(); 177 } 178 179 private void findApiDemos() throws IOException, XmlPullParserException { 180 IAndroidTarget[] targets = getSdk().getTargets(); 181 182 for (IAndroidTarget target : targets) { 183 String path = target.getPath(IAndroidTarget.SAMPLES); 184 File samples = new File(path); 185 if (samples.isDirectory()) { 186 File[] files = samples.listFiles(); 187 for (File file : files) { 188 if ("apidemos".equalsIgnoreCase(file.getName())) { 189 testSample(target, file); 190 return; 191 } 192 } 193 } 194 } 195 196 fail("Failed to find ApiDemos!"); 197 } 198 199 private void testSample(IAndroidTarget target, File sampleProject) throws IOException, XmlPullParserException { 200 AndroidTargetData data = getSdk().getTargetData(target); 201 if (data == null) { 202 fail("No AndroidData!"); 203 } 204 205 LayoutLibrary layoutLib = data.getLayoutLibrary(); 206 if (layoutLib.getStatus() != LoadStatus.LOADED) { 207 fail("Fail to load the bridge: " + layoutLib.getLoadMessage()); 208 } 209 210 FolderWrapper resFolder = new FolderWrapper(sampleProject, SdkConstants.FD_RES); 211 if (resFolder.exists() == false) { 212 fail("Sample project has no res folder!"); 213 } 214 215 // look for the layout folder 216 File layoutFolder = new File(resFolder, SdkConstants.FD_RES_LAYOUT); 217 if (layoutFolder.isDirectory() == false) { 218 fail("Sample project has no layout folder!"); 219 } 220 221 // first load the project's target framework resource 222 ResourceRepository framework = ResourceManager.getInstance().loadFrameworkResources(target); 223 224 // now load the project resources 225 ResourceRepository project = new ResourceRepository(resFolder, false) { 226 @Override 227 protected ResourceItem createResourceItem(String name) { 228 return new ResourceItem(name); 229 } 230 231 }; 232 233 // Create a folder configuration that will be used for the rendering: 234 FolderConfiguration config = getConfiguration(); 235 236 // get the configured resources 237 Map<ResourceType, Map<String, ResourceValue>> configuredFramework = 238 framework.getConfiguredResources(config); 239 Map<ResourceType, Map<String, ResourceValue>> configuredProject = 240 project.getConfiguredResources(config); 241 242 boolean saveFiles = System.getenv("save_file") != null; 243 244 // loop on the layouts and render them 245 File[] layouts = layoutFolder.listFiles(); 246 for (File layout : layouts) { 247 // create a parser for the layout file 248 TestParser parser = new TestParser(); 249 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); 250 parser.setInput(new FileReader(layout)); 251 252 System.out.println("Rendering " + layout.getName()); 253 254 ProjectCallBack projectCallBack = new ProjectCallBack(); 255 256 ResourceResolver resolver = ResourceResolver.create( 257 configuredProject, configuredFramework, 258 "Theme", false /*isProjectTheme*/); 259 260 HardwareConfig hardwareConfig = new HardwareConfig( 261 320, 262 480, 263 Density.MEDIUM, 264 160, //xdpi 265 160, // ydpi 266 ScreenSize.NORMAL, 267 ScreenOrientation.PORTRAIT, 268 false /*software buttons */); 269 270 RenderSession session = layoutLib.createSession(new SessionParams( 271 parser, 272 RenderingMode.NORMAL, 273 null /*projectKey*/, 274 hardwareConfig, 275 resolver, 276 projectCallBack, 277 1, // minSdkVersion 278 1, // targetSdkVersion 279 null //logger 280 )); 281 282 if (session.getResult().isSuccess() == false) { 283 if (projectCallBack.mCustomViewAttempt == false) { 284 System.out.println("FAILED"); 285 fail(String.format("Rendering %1$s: %2$s", layout.getName(), 286 session.getResult().getErrorMessage())); 287 } else { 288 System.out.println("Ignore custom views for now"); 289 } 290 } else { 291 if (saveFiles) { 292 File tmp = File.createTempFile(layout.getName(), ".png"); 293 ImageIO.write(session.getImage(), "png", tmp); 294 } 295 System.out.println("Success!"); 296 } 297 } 298 } 299 300 /** 301 * Returns a config. This must be a valid config like a device would return. This is to 302 * prevent issues where some resources don't exist in all cases and not in the default 303 * (for instance only available in hdpi and mdpi but not in default). 304 * @return 305 */ 306 private FolderConfiguration getConfiguration() { 307 FolderConfiguration config = new FolderConfiguration(); 308 309 // this matches an ADP1. 310 config.addQualifier(new SmallestScreenWidthQualifier(320)); 311 config.addQualifier(new ScreenWidthQualifier(320)); 312 config.addQualifier(new ScreenHeightQualifier(480)); 313 config.addQualifier(new ScreenSizeQualifier(ScreenSize.NORMAL)); 314 config.addQualifier(new ScreenRatioQualifier(ScreenRatio.NOTLONG)); 315 config.addQualifier(new ScreenOrientationQualifier(ScreenOrientation.PORTRAIT)); 316 config.addQualifier(new DensityQualifier(Density.MEDIUM)); 317 config.addQualifier(new TouchScreenQualifier(TouchScreen.FINGER)); 318 config.addQualifier(new KeyboardStateQualifier(KeyboardState.HIDDEN)); 319 config.addQualifier(new TextInputMethodQualifier(Keyboard.QWERTY)); 320 config.addQualifier(new NavigationStateQualifier(NavigationState.HIDDEN)); 321 config.addQualifier(new NavigationMethodQualifier(Navigation.TRACKBALL)); 322 config.addQualifier(new ScreenDimensionQualifier(480, 320)); 323 324 config.updateScreenWidthAndHeight(); 325 326 return config; 327 } 328 } 329