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