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