1a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu/* 2a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * Copyright (C) 2012 The Android Open Source Project 3a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * 4a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * Licensed under the Apache License, Version 2.0 (the "License"); 5a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * you may not use this file except in compliance with the License. 6a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * You may obtain a copy of the License at 7a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * 8a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * http://www.apache.org/licenses/LICENSE-2.0 9a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * 10a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * Unless required by applicable law or agreed to in writing, software 11a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * distributed under the License is distributed on an "AS IS" BASIS, 12a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * See the License for the specific language governing permissions and 14a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * limitations under the License. 15a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu */ 16a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 17a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhupackage com.android.commands.uiautomator; 18a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 19104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganovimport android.app.UiAutomation; 2047d54d40309083809bf04ba07a5b7191fbca9351Guang Zhuimport android.graphics.Point; 2147d54d40309083809bf04ba07a5b7191fbca9351Guang Zhuimport android.hardware.display.DisplayManagerGlobal; 22a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhuimport android.os.Environment; 2347d54d40309083809bf04ba07a5b7191fbca9351Guang Zhuimport android.view.Display; 24e3ee63d53a69f5f0efe12a993f5599ce255d8d12Guang Zhuimport android.view.accessibility.AccessibilityNodeInfo; 25a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 26a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhuimport com.android.commands.uiautomator.Launcher.Command; 27a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhuimport com.android.uiautomator.core.AccessibilityNodeInfoDumper; 28104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganovimport com.android.uiautomator.core.UiAutomationShellWrapper; 29a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 30a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhuimport java.io.File; 31104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganovimport java.util.concurrent.TimeoutException; 32a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 33a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu/** 34a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * Implementation of the dump subcommand 35a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * 36a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu * This creates an XML dump of current UI hierarchy 37a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu */ 38a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhupublic class DumpCommand extends Command { 39a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 40a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu private static final File DEFAULT_DUMP_FILE = new File( 41e3ee63d53a69f5f0efe12a993f5599ce255d8d12Guang Zhu Environment.getLegacyExternalStorageDirectory(), "window_dump.xml"); 42a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 43a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu public DumpCommand() { 44a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu super("dump"); 45a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu } 46a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 47a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu @Override 48a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu public String shortHelp() { 49a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu return "creates an XML dump of current UI hierarchy"; 50a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu } 51a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 52a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu @Override 53a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu public String detailedOptions() { 54f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz return " dump [--verbose][file]\n" 55dec12c69b4e09679f691050d8fdcab0a5e474258Guang Zhu + " [--compressed]: dumps compressed layout information.\n" 56a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu + " [file]: the location where the dumped XML should be stored, default is\n " 57a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu + DEFAULT_DUMP_FILE.getAbsolutePath() + "\n"; 58a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu } 59a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu 60a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu @Override 61a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu public void run(String[] args) { 62a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu File dumpFile = DEFAULT_DUMP_FILE; 63dec12c69b4e09679f691050d8fdcab0a5e474258Guang Zhu boolean verboseMode = true; 64f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz 65f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz for (String arg : args) { 66dec12c69b4e09679f691050d8fdcab0a5e474258Guang Zhu if (arg.equals("--compressed")) 67dec12c69b4e09679f691050d8fdcab0a5e474258Guang Zhu verboseMode = false; 68f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz else if (!arg.startsWith("-")) { 69f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz dumpFile = new File(arg); 70f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz } 71a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu } 72f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz 73104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov UiAutomationShellWrapper automationWrapper = new UiAutomationShellWrapper(); 74104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov automationWrapper.connect(); 75f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz if (verboseMode) { 76dec12c69b4e09679f691050d8fdcab0a5e474258Guang Zhu // default 77f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz automationWrapper.setCompressedLayoutHierarchy(false); 78f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz } else { 79f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz automationWrapper.setCompressedLayoutHierarchy(true); 80f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz } 81f0743b0acc99079bd2ef2f808d9cd71fe7bc50bdAdam Momtaz 82a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu // It appears that the bridge needs time to be ready. Making calls to the 83a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu // bridge immediately after connecting seems to cause exceptions. So let's also 84a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu // do a wait for idle in case the app is busy. 85104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov try { 86104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov UiAutomation uiAutomation = automationWrapper.getUiAutomation(); 87104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov uiAutomation.waitForIdle(1000, 1000 * 10); 88104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow(); 89104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov if (info == null) { 90104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov System.err.println("ERROR: null root node returned by UiTestAutomationBridge."); 91104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov return; 92104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov } 9347d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu 9447d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu Display display = 9547d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY); 9647d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu int rotation = display.getRotation(); 9747d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu Point size = new Point(); 9847d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu display.getSize(size); 9947d54d40309083809bf04ba07a5b7191fbca9351Guang Zhu AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y); 100104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov } catch (TimeoutException re) { 101104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov System.err.println("ERROR: could not get idle state."); 102e3ee63d53a69f5f0efe12a993f5599ce255d8d12Guang Zhu return; 103104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov } finally { 104104ac2482e8b44fb5e5fb817b5724331e4ec7fbeSvetoslav Ganov automationWrapper.disconnect(); 105e3ee63d53a69f5f0efe12a993f5599ce255d8d12Guang Zhu } 106a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu System.out.println( 107a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu String.format("UI hierchary dumped to: %s", dumpFile.getAbsolutePath())); 108a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu } 109a5c65de4744dca37b46de0acf8be11a9c24cc91bGuang Zhu} 110