118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu/* 218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * Copyright (C) 2012 The Android Open Source Project 318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * 418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * Licensed under the Apache License, Version 2.0 (the "License"); 518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * you may not use this file except in compliance with the License. 618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * You may obtain a copy of the License at 718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * 818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * http://www.apache.org/licenses/LICENSE-2.0 918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * 1018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * Unless required by applicable law or agreed to in writing, software 1118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * distributed under the License is distributed on an "AS IS" BASIS, 1218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * See the License for the specific language governing permissions and 1418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * limitations under the License. 1518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu */ 1618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 1718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhupackage com.android.commands.uiautomator; 1818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 1918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.app.UiAutomation; 2018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.graphics.Point; 2118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.hardware.display.DisplayManagerGlobal; 2218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.os.Environment; 2318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.view.Display; 2418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport android.view.accessibility.AccessibilityNodeInfo; 2518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 2618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport com.android.commands.uiautomator.Launcher.Command; 2718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport com.android.uiautomator.core.AccessibilityNodeInfoDumper; 2818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport com.android.uiautomator.core.UiAutomationShellWrapper; 2918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 3018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport java.io.File; 3118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhuimport java.util.concurrent.TimeoutException; 3218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 3318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu/** 3418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * Implementation of the dump subcommand 3518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * 3618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu * This creates an XML dump of current UI hierarchy 3718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu */ 3818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhupublic class DumpCommand extends Command { 3918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 4018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu private static final File DEFAULT_DUMP_FILE = new File( 4118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu Environment.getLegacyExternalStorageDirectory(), "window_dump.xml"); 4218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 4318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu public DumpCommand() { 4418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu super("dump"); 4518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 4618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 4718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu @Override 4818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu public String shortHelp() { 4918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu return "creates an XML dump of current UI hierarchy"; 5018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 5118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 5218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu @Override 5318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu public String detailedOptions() { 5418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu return " dump [--verbose][file]\n" 5518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu + " [--compressed]: dumps compressed layout information.\n" 5618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu + " [file]: the location where the dumped XML should be stored, default is\n " 5718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu + DEFAULT_DUMP_FILE.getAbsolutePath() + "\n"; 5818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 5918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 6018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu @Override 6118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu public void run(String[] args) { 6218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu File dumpFile = DEFAULT_DUMP_FILE; 6318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu boolean verboseMode = true; 6418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 6518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu for (String arg : args) { 6618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu if (arg.equals("--compressed")) 6718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu verboseMode = false; 6818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu else if (!arg.startsWith("-")) { 6918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu dumpFile = new File(arg); 7018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 7118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 7218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 7318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper(); 7418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu automationWrapper.connect(); 7518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu if (verboseMode) { 7618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu // default 7718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu automationWrapper.setCompressedLayoutHierarchy(false); 7818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } else { 7918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu automationWrapper.setCompressedLayoutHierarchy(true); 8018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 8118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 8218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu // It appears that the bridge needs time to be ready. Making calls to the 8318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu // bridge immediately after connecting seems to cause exceptions. So let's also 8418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu // do a wait for idle in case the app is busy. 8518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu try { 8618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu UiAutomation uiAutomation = automationWrapper.getUiAutomation(); 8718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu uiAutomation.waitForIdle(1000, 1000 * 10); 8818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow(); 8918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu if (info == null) { 9018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu System.err.println("ERROR: null root node returned by UiTestAutomationBridge."); 9118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu return; 9218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 9318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu 9418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu Display display = 9518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); 9618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu int rotation = display.getRotation(); 9718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu Point size = new Point(); 9818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu display.getSize(size); 9918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y); 10018b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } catch (TimeoutException re) { 10118b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu System.err.println("ERROR: could not get idle state."); 10218b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu return; 10318b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } finally { 10418b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu automationWrapper.disconnect(); 10518b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 10618b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu System.out.println( 10718b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu String.format("UI hierchary dumped to: %s", dumpFile.getAbsolutePath())); 10818b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu } 10918b892c723e812a7e111f102d2b0c0782b116bb6Guang Zhu} 110