New layout optimization tool. Run layoutopt on the command line.
Change-Id: I8e4697e19ca8a203dc8a41b464f7cb46d52184b0
This commit is contained in:
5
tools/layoutopt/Android.mk
Normal file
5
tools/layoutopt/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Copyright 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
LAYOUTOPT_LOCAL_DIR := $(call my-dir)
|
||||||
|
include $(LAYOUTOPT_LOCAL_DIR)/libs/Android.mk
|
||||||
|
include $(LAYOUTOPT_LOCAL_DIR)/app/Android.mk
|
||||||
0
tools/layoutopt/MODULE_LICENSE_APACHE2
Normal file
0
tools/layoutopt/MODULE_LICENSE_APACHE2
Normal file
5
tools/layoutopt/app/Android.mk
Normal file
5
tools/layoutopt/app/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Copyright 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
LAYOUTOPT_LOCAL_DIR := $(call my-dir)
|
||||||
|
include $(LAYOUTOPT_LOCAL_DIR)/etc/Android.mk
|
||||||
|
include $(LAYOUTOPT_LOCAL_DIR)/src/Android.mk
|
||||||
3
tools/layoutopt/app/README
Normal file
3
tools/layoutopt/app/README
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Layout optimizer.
|
||||||
|
|
||||||
|
Simple command line front end for the uix library.
|
||||||
8
tools/layoutopt/app/etc/Android.mk
Normal file
8
tools/layoutopt/app/etc/Android.mk
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_PREBUILT_EXECUTABLES := layoutopt
|
||||||
|
include $(BUILD_HOST_PREBUILT)
|
||||||
|
|
||||||
63
tools/layoutopt/app/etc/layoutopt
Executable file
63
tools/layoutopt/app/etc/layoutopt
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Copyright 2009, The Android Open Source Project
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Set up prog to be the path of this script, including following symlinks,
|
||||||
|
# and set up progdir to be the fully-qualified pathname of its directory.
|
||||||
|
prog="$0"
|
||||||
|
while [ -h "${prog}" ]; do
|
||||||
|
newProg=`/bin/ls -ld "${prog}"`
|
||||||
|
newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
|
||||||
|
if expr "x${newProg}" : 'x/' >/dev/null; then
|
||||||
|
prog="${newProg}"
|
||||||
|
else
|
||||||
|
progdir=`dirname "${prog}"`
|
||||||
|
prog="${progdir}/${newProg}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
oldwd=`pwd`
|
||||||
|
progdir=`dirname "${prog}"`
|
||||||
|
cd "${progdir}"
|
||||||
|
progdir=`pwd`
|
||||||
|
prog="${progdir}"/`basename "${prog}"`
|
||||||
|
cd "${oldwd}"
|
||||||
|
|
||||||
|
jarfile=layoutopt.jar
|
||||||
|
frameworkdir="$progdir"
|
||||||
|
if [ ! -r "$frameworkdir/$jarfile" ]
|
||||||
|
then
|
||||||
|
frameworkdir=`dirname "$progdir"`/tools/lib
|
||||||
|
libdir=`dirname "$progdir"`/tools/lib
|
||||||
|
fi
|
||||||
|
if [ ! -r "$frameworkdir/$jarfile" ]
|
||||||
|
then
|
||||||
|
frameworkdir=`dirname "$progdir"`/framework
|
||||||
|
libdir=`dirname "$progdir"`/lib
|
||||||
|
fi
|
||||||
|
if [ ! -r "$frameworkdir/$jarfile" ]
|
||||||
|
then
|
||||||
|
echo `basename "$prog"`": can't find $jarfile"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$OSTYPE" = "cygwin" ] ; then
|
||||||
|
jarpath=`cygpath -w "$frameworkdir/$jarfile"`
|
||||||
|
progdir=`cygpath -w "$progdir"`
|
||||||
|
else
|
||||||
|
jarpath="$frameworkdir/$jarfile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
|
||||||
|
# might need more memory, e.g. -Xmx128M
|
||||||
|
exec java -Djava.ext.dirs="$frameworkdir" -jar "$jarpath" "$@"
|
||||||
48
tools/layoutopt/app/etc/layoutopt.bat
Executable file
48
tools/layoutopt/app/etc/layoutopt.bat
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
@echo off
|
||||||
|
rem Copyright (C) 2009 The Android Open Source Project
|
||||||
|
rem
|
||||||
|
rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
rem you may not use this file except in compliance with the License.
|
||||||
|
rem You may obtain a copy of the License at
|
||||||
|
rem
|
||||||
|
rem http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
rem
|
||||||
|
rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
rem See the License for the specific language governing permissions and
|
||||||
|
rem limitations under the License.
|
||||||
|
|
||||||
|
rem don't modify the caller's environment
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
rem Set up prog to be the path of this script, including following symlinks,
|
||||||
|
rem and set up progdir to be the fully-qualified pathname of its directory.
|
||||||
|
set prog=%~f0
|
||||||
|
|
||||||
|
rem Change current directory and drive to where the script is, to avoid
|
||||||
|
rem issues with directories containing whitespaces.
|
||||||
|
cd /d %~dp0
|
||||||
|
|
||||||
|
set jarfile=layoutopt.jar
|
||||||
|
set frameworkdir=
|
||||||
|
|
||||||
|
if exist %frameworkdir%%jarfile% goto JarFileOk
|
||||||
|
set frameworkdir=lib\
|
||||||
|
|
||||||
|
if exist %frameworkdir%%jarfile% goto JarFileOk
|
||||||
|
set frameworkdir=..\framework\
|
||||||
|
|
||||||
|
:JarFileOk
|
||||||
|
|
||||||
|
if debug NEQ "%1" goto NoDebug
|
||||||
|
set java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
|
||||||
|
shift 1
|
||||||
|
:NoDebug
|
||||||
|
|
||||||
|
set jarpath=%frameworkdir%%jarfile%
|
||||||
|
|
||||||
|
set javaextdirs=%swt_path%;%frameworkdir%
|
||||||
|
|
||||||
|
call java %java_debug% -Djava.ext.dirs=%javaextdirs% %jarpath% %*
|
||||||
|
|
||||||
2
tools/layoutopt/app/etc/manifest.txt
Normal file
2
tools/layoutopt/app/etc/manifest.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Main-Class: com.android.layoutopt.cli.Main
|
||||||
|
Class-Path: groovy-all-1.6.5.jar
|
||||||
15
tools/layoutopt/app/src/Android.mk
Normal file
15
tools/layoutopt/app/src/Android.mk
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||||
|
LOCAL_JAVA_RESOURCE_DIRS := resources
|
||||||
|
|
||||||
|
LOCAL_JAR_MANIFEST := ../etc/manifest.txt
|
||||||
|
LOCAL_JAVA_LIBRARIES := \
|
||||||
|
uix
|
||||||
|
LOCAL_MODULE := layoutopt
|
||||||
|
|
||||||
|
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||||
|
|
||||||
123
tools/layoutopt/app/src/com/android/layoutopt/cli/Main.java
Normal file
123
tools/layoutopt/app/src/com/android/layoutopt/cli/Main.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.cli;
|
||||||
|
|
||||||
|
import com.android.layoutopt.uix.LayoutAnalyzer;
|
||||||
|
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command line utility for the uix library.
|
||||||
|
*
|
||||||
|
* This is a simple CLI front-end for the uix library, used to
|
||||||
|
* analyze and optimize Android layout files.
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
/**
|
||||||
|
* Main entry point of the application.
|
||||||
|
*
|
||||||
|
* @param args One mandatory parameter, a path (absolute or relative)
|
||||||
|
* to an Android XML layout file
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Parameters p = checkParameters(args);
|
||||||
|
if (!p.valid) {
|
||||||
|
displayHelpMessage();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzeFiles(p.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void analyzeFiles(File[] files) {
|
||||||
|
LayoutAnalyzer analyzer = new LayoutAnalyzer();
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isFile()) {
|
||||||
|
analyze(analyzer, file);
|
||||||
|
} else {
|
||||||
|
analyzeFiles(file.listFiles());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void analyze(LayoutAnalyzer analyzer, File file) {
|
||||||
|
LayoutAnalysis analysis = analyzer.analyze(file);
|
||||||
|
System.out.println(analysis.getName());
|
||||||
|
for (LayoutAnalysis.Issue issue : analysis.getIssues()) {
|
||||||
|
System.out.print(String.format("\t%d:%d ", issue.getStartLine(), issue.getEndLine()));
|
||||||
|
System.out.println(issue.getDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exits the tool.
|
||||||
|
*/
|
||||||
|
private static void exit() {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays this tool's help message on the standard output.
|
||||||
|
*/
|
||||||
|
private static void displayHelpMessage() {
|
||||||
|
System.out.println("usage: layoutopt <directories/files to analyze>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a valid Parameters object. Parses the paramters if necessary
|
||||||
|
* and checks for errors.
|
||||||
|
*
|
||||||
|
* @param args The parameters passed from the CLI.
|
||||||
|
*/
|
||||||
|
private static Parameters checkParameters(String[] args) {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
|
||||||
|
if (args.length < 1) {
|
||||||
|
p.valid = false;
|
||||||
|
} else {
|
||||||
|
List<File> files = new ArrayList<File>();
|
||||||
|
for (String path : args) {
|
||||||
|
File file = new File(path);
|
||||||
|
if (file.exists()) {
|
||||||
|
files.add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.files = files.toArray(new File[files.size()]);
|
||||||
|
p.valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters parsed from the CLI.
|
||||||
|
*/
|
||||||
|
private static class Parameters {
|
||||||
|
/**
|
||||||
|
* True if this list of parameters is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean valid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paths (absolute or relative) to the files to be analyzed.
|
||||||
|
*/
|
||||||
|
File[] files;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
tools/layoutopt/libs/Android.mk
Normal file
5
tools/layoutopt/libs/Android.mk
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Copyright 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
DDMSLIBS_LOCAL_DIR := $(call my-dir)
|
||||||
|
include $(DDMSLIBS_LOCAL_DIR)/uix/Android.mk
|
||||||
|
|
||||||
4
tools/layoutopt/libs/uix/Android.mk
Normal file
4
tools/layoutopt/libs/uix/Android.mk
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Copyright 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
UIX_LOCAL_DIR := $(call my-dir)
|
||||||
|
include $(UIX_LOCAL_DIR)/src/Android.mk
|
||||||
13
tools/layoutopt/libs/uix/src/Android.mk
Normal file
13
tools/layoutopt/libs/uix/src/Android.mk
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Copyright 2009 The Android Open Source Project
|
||||||
|
#
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||||
|
LOCAL_JAVA_RESOURCE_DIRS := resources
|
||||||
|
|
||||||
|
LOCAL_MODULE := uix
|
||||||
|
LOCAL_JAVA_LIBRARIES := \
|
||||||
|
groovy-all-1.6.5
|
||||||
|
|
||||||
|
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the results of a layout analysis. Instances of this class are
|
||||||
|
* generated by {@link com.android.layoutopt.uix.LayoutAnalyzer}.
|
||||||
|
*
|
||||||
|
* @see com.android.layoutopt.uix.LayoutAnalyzer
|
||||||
|
*/
|
||||||
|
public class LayoutAnalysis {
|
||||||
|
/**
|
||||||
|
* Default layout analysis used to describe a problem with the
|
||||||
|
* analysis process.
|
||||||
|
*/
|
||||||
|
static final LayoutAnalysis ERROR = new LayoutAnalysis("");
|
||||||
|
static {
|
||||||
|
ERROR.mAnalyzed = false;
|
||||||
|
ERROR.addIssue("The layout could not be analyzed. Check if you specified a valid "
|
||||||
|
+ "XML layout, if the specified file exists, etc.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Issue> mIssues = new ArrayList<Issue>();
|
||||||
|
private String mName;
|
||||||
|
private boolean mAnalyzed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new analysis. An analysis is always considered invalid by default.
|
||||||
|
*
|
||||||
|
* @see #validate()
|
||||||
|
* @see #isValid()
|
||||||
|
*/
|
||||||
|
LayoutAnalysis(String name) {
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of this analysis.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName(String name) {
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an issue to the layout analysis.
|
||||||
|
*
|
||||||
|
* @param description Description of the issue.
|
||||||
|
*/
|
||||||
|
public void addIssue(String description) {
|
||||||
|
mIssues.add(new Issue(description));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an issue to the layout analysis.
|
||||||
|
*
|
||||||
|
* @param node The layout node containing the issue.
|
||||||
|
* @param description Description of the issue.
|
||||||
|
*/
|
||||||
|
public void addIssue(LayoutNode node, String description) {
|
||||||
|
mIssues.add(new Issue(node, description));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of issues found during the analysis.
|
||||||
|
*
|
||||||
|
* @return A non-null array of {@link com.android.layoutopt.uix.LayoutAnalysis.Issue}.
|
||||||
|
*/
|
||||||
|
public Issue[] getIssues() {
|
||||||
|
return mIssues.toArray(new Issue[mIssues.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the layout was analyzed. If this method returns false,
|
||||||
|
* a probleme occured during the analysis (missing file, invalid document, etc.)
|
||||||
|
*
|
||||||
|
* @return True if the layout was analyzed, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return mAnalyzed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the analysis. This must be call before this analysis can
|
||||||
|
* be considered valid.
|
||||||
|
*/
|
||||||
|
void validate() {
|
||||||
|
mAnalyzed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an issue discovered during the analysis process.
|
||||||
|
* An issue provides a human-readable description as well as optional solutions.
|
||||||
|
*/
|
||||||
|
public static class Issue {
|
||||||
|
private final String mDescription;
|
||||||
|
private final LayoutNode mNode;
|
||||||
|
|
||||||
|
Issue(String description) {
|
||||||
|
mNode = null;
|
||||||
|
if (description == null) {
|
||||||
|
throw new IllegalArgumentException("The description must be non-null");
|
||||||
|
}
|
||||||
|
mDescription = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(LayoutNode node, String description) {
|
||||||
|
mNode = node;
|
||||||
|
if (description == null) {
|
||||||
|
throw new IllegalArgumentException("The description must be non-null");
|
||||||
|
}
|
||||||
|
mDescription = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes this issue to the user.
|
||||||
|
*
|
||||||
|
* @return A String describing the issue, always non-null.
|
||||||
|
*/
|
||||||
|
public String getDescription() {
|
||||||
|
return mDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start line of this node.
|
||||||
|
*
|
||||||
|
* @return The start line or -1 if the line is unknown.
|
||||||
|
*/
|
||||||
|
public int getStartLine() {
|
||||||
|
return mNode == null ? -1 : mNode.getStartLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the end line of this node.
|
||||||
|
*
|
||||||
|
* @return The end line or -1 if the line is unknown.
|
||||||
|
*/
|
||||||
|
public int getEndLine() {
|
||||||
|
return mNode == null ? -1 : mNode.getEndLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import com.android.layoutopt.uix.xml.XmlDocumentBuilder;
|
||||||
|
import com.android.layoutopt.uix.rules.Rule;
|
||||||
|
import com.android.layoutopt.uix.rules.GroovyRule;
|
||||||
|
import com.android.layoutopt.uix.util.IOUtilities;
|
||||||
|
import groovy.lang.GroovyClassLoader;
|
||||||
|
import groovy.lang.GroovyShell;
|
||||||
|
import groovy.lang.Script;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analysis engine used to discover inefficiencies in Android XML
|
||||||
|
* layout documents.
|
||||||
|
*
|
||||||
|
* Anaylizing an Android XML layout produces a list of explicit messages
|
||||||
|
* as well as possible solutions.
|
||||||
|
*/
|
||||||
|
public class LayoutAnalyzer {
|
||||||
|
private static final String RULES_PREFIX = "rules/";
|
||||||
|
|
||||||
|
private final XmlDocumentBuilder mBuilder = new XmlDocumentBuilder();
|
||||||
|
private final List<Rule> mRules = new ArrayList<Rule>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new layout analyzer. This constructor takes no argument
|
||||||
|
* and will use the default options.
|
||||||
|
*/
|
||||||
|
public LayoutAnalyzer() {
|
||||||
|
loadRules();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRules() {
|
||||||
|
ClassLoader parent = getClass().getClassLoader();
|
||||||
|
GroovyClassLoader loader = new GroovyClassLoader(parent);
|
||||||
|
GroovyShell shell = new GroovyShell(loader);
|
||||||
|
|
||||||
|
URL jar = getClass().getProtectionDomain().getCodeSource().getLocation();
|
||||||
|
ZipFile zip = null;
|
||||||
|
try {
|
||||||
|
zip = new ZipFile(new File(jar.toURI()));
|
||||||
|
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||||
|
while (entries.hasMoreElements()) {
|
||||||
|
ZipEntry entry = entries.nextElement();
|
||||||
|
if (!entry.isDirectory() && entry.getName().startsWith(RULES_PREFIX)) {
|
||||||
|
loadRule(shell, entry.getName(), zip.getInputStream(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (zip != null) zip.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRule(GroovyShell shell, String name, InputStream stream) {
|
||||||
|
try {
|
||||||
|
Script script = shell.parse(stream);
|
||||||
|
mRules.add(new GroovyRule(name, script));
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Could not load rule " + name + ":");
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
IOUtilities.close(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRule(Rule rule) {
|
||||||
|
if (rule == null) {
|
||||||
|
throw new IllegalArgumentException("A rule must be non-null");
|
||||||
|
}
|
||||||
|
mRules.add(rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes the specified file.
|
||||||
|
*
|
||||||
|
* @param file The file to analyze.
|
||||||
|
*
|
||||||
|
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||||
|
* cannot be null.
|
||||||
|
*/
|
||||||
|
public LayoutAnalysis analyze(File file) {
|
||||||
|
if (file != null && file.exists()) {
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = new FileInputStream(file);
|
||||||
|
return analyze(file.getPath(), in);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// Ignore, cannot happen
|
||||||
|
} finally {
|
||||||
|
IOUtilities.close(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutAnalysis.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes the specified XML stream.
|
||||||
|
*
|
||||||
|
* @param stream The stream to analyze.
|
||||||
|
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||||
|
* cannot be null.
|
||||||
|
*/
|
||||||
|
public LayoutAnalysis analyze(InputStream stream) {
|
||||||
|
return analyze("<unknown>", stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LayoutAnalysis analyze(String name, InputStream stream) {
|
||||||
|
try {
|
||||||
|
Document document = mBuilder.parse(stream);
|
||||||
|
return analyze(name, document);
|
||||||
|
} catch (SAXException e) {
|
||||||
|
// Ignore
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
return LayoutAnalysis.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes the specified XML document.
|
||||||
|
*
|
||||||
|
* @param content The XML document to analyze.
|
||||||
|
*
|
||||||
|
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||||
|
* cannot be null.
|
||||||
|
*/
|
||||||
|
public LayoutAnalysis analyze(String content) {
|
||||||
|
return analyze("<unknown>", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes the specified XML document.
|
||||||
|
*
|
||||||
|
* @param name The name of the document.
|
||||||
|
* @param content The XML document to analyze.
|
||||||
|
*
|
||||||
|
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||||
|
* cannot be null.
|
||||||
|
*/
|
||||||
|
public LayoutAnalysis analyze(String name, String content) {
|
||||||
|
try {
|
||||||
|
Document document = mBuilder.parse(content);
|
||||||
|
return analyze(name, document);
|
||||||
|
} catch (SAXException e) {
|
||||||
|
// Ignore
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
return LayoutAnalysis.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes the specified XML document.
|
||||||
|
*
|
||||||
|
* @param document The XML document to analyze.
|
||||||
|
*
|
||||||
|
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||||
|
* cannot be null.
|
||||||
|
*/
|
||||||
|
public LayoutAnalysis analyze(Document document) {
|
||||||
|
return analyze("<unknown>", document);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes the specified XML document.
|
||||||
|
*
|
||||||
|
* @param name The name of the document.
|
||||||
|
* @param document The XML document to analyze.
|
||||||
|
*
|
||||||
|
* @return A {@link com.android.layoutopt.uix.LayoutAnalysis} which
|
||||||
|
* cannot be null.
|
||||||
|
*/
|
||||||
|
public LayoutAnalysis analyze(String name, Document document) {
|
||||||
|
LayoutAnalysis analysis = new LayoutAnalysis(name);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Element root = document.getDocumentElement();
|
||||||
|
analyze(analysis, root);
|
||||||
|
} finally {
|
||||||
|
analysis.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void analyze(LayoutAnalysis analysis, Node node) {
|
||||||
|
NodeList list = node.getChildNodes();
|
||||||
|
int count = list.getLength();
|
||||||
|
|
||||||
|
// Depth first
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Node child = list.item(i);
|
||||||
|
if (child.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
analyze(analysis, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyRules(analysis, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyRules(LayoutAnalysis analysis, Node node) {
|
||||||
|
LayoutNode layoutNode = new LayoutNode(node);
|
||||||
|
for (Rule rule : mRules) {
|
||||||
|
rule.run(analysis, layoutNode, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix;
|
||||||
|
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NamedNodeMap;
|
||||||
|
import org.w3c.dom.Attr;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import com.android.layoutopt.uix.xml.XmlDocumentBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper class for W3C Node objects. Provides extra utilities specific
|
||||||
|
* to Android XML layouts.
|
||||||
|
*/
|
||||||
|
public class LayoutNode {
|
||||||
|
private static final String ANDROID_LAYOUT_WIDTH = "android:layout_width";
|
||||||
|
private static final String ANDROID_LAYOUT_HEIGHT = "android:layout_height";
|
||||||
|
private static final String VALUE_FILL_PARENT = "fill_parent";
|
||||||
|
private static final String VALUE_WRAP_CONTENT = "wrap_content";
|
||||||
|
|
||||||
|
private Map<String, String> mAttributes;
|
||||||
|
private final Element mNode;
|
||||||
|
private LayoutNode[] mChildren;
|
||||||
|
|
||||||
|
LayoutNode(Node node) {
|
||||||
|
if (node == null) throw new IllegalArgumentException("The node cannot be null");
|
||||||
|
if (node.getNodeType() != Node.ELEMENT_NODE) {
|
||||||
|
throw new IllegalArgumentException("The node must be an element type");
|
||||||
|
}
|
||||||
|
mNode = (Element) node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start line of this node.
|
||||||
|
*
|
||||||
|
* @return The start line or -1 if the line is unknown.
|
||||||
|
*/
|
||||||
|
public int getStartLine() {
|
||||||
|
final Object data = mNode.getUserData(XmlDocumentBuilder.NODE_START_LINE);
|
||||||
|
return data == null ? -1 : (Integer) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the end line of this node.
|
||||||
|
*
|
||||||
|
* @return The end line or -1 if the line is unknown.
|
||||||
|
*/
|
||||||
|
public int getEndLine() {
|
||||||
|
final Object data = mNode.getUserData(XmlDocumentBuilder.NODE_END_LINE);
|
||||||
|
return data == null ? -1 : (Integer) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the wrapped W3C XML node object.
|
||||||
|
*
|
||||||
|
* @return An XML node.
|
||||||
|
*/
|
||||||
|
public Node getNode() {
|
||||||
|
return mNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the node is of the specified type.
|
||||||
|
*
|
||||||
|
* @param name The name of the node.
|
||||||
|
*
|
||||||
|
* @return True if this node has the same name as tagName, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean is(String name) {
|
||||||
|
return mNode.getNodeName().equals(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the node has declared the specified attribute.
|
||||||
|
*
|
||||||
|
* @param attribute The name of the attribute to check.
|
||||||
|
*
|
||||||
|
* @return True if the attribute is specified, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean has(String attribute) {
|
||||||
|
return mNode.hasAttribute(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this node is the document root.
|
||||||
|
*
|
||||||
|
* @return True if the wrapped node is the root of the document,
|
||||||
|
* false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isRoot() {
|
||||||
|
return mNode == mNode.getOwnerDocument().getDocumentElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this node's width is fill_parent.
|
||||||
|
*/
|
||||||
|
public boolean isWidthFillParent() {
|
||||||
|
return mNode.getAttribute(ANDROID_LAYOUT_WIDTH).equals(VALUE_FILL_PARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this node's width is wrap_content.
|
||||||
|
*/
|
||||||
|
public boolean isWidthWrapContent() {
|
||||||
|
return mNode.getAttribute(ANDROID_LAYOUT_WIDTH).equals(VALUE_WRAP_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this node's height is fill_parent.
|
||||||
|
*/
|
||||||
|
public boolean isHeightFillParent() {
|
||||||
|
return mNode.getAttribute(ANDROID_LAYOUT_HEIGHT).equals(VALUE_FILL_PARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this node's height is wrap_content.
|
||||||
|
*/
|
||||||
|
public boolean isHeightWrapContent() {
|
||||||
|
return mNode.getAttribute(ANDROID_LAYOUT_HEIGHT).equals(VALUE_WRAP_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of all the attributes declared for this node.
|
||||||
|
*
|
||||||
|
* The name of the attributes contains the namespace.
|
||||||
|
*
|
||||||
|
* @return A map of [name, value] describing the attributes of this node.
|
||||||
|
*/
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
if (mAttributes == null) {
|
||||||
|
NamedNodeMap attributes = mNode.getAttributes();
|
||||||
|
int count = attributes.getLength();
|
||||||
|
mAttributes = new HashMap<String, String>(count);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Node node = attributes.item(i);
|
||||||
|
Attr attribute = (Attr) node;
|
||||||
|
mAttributes.put(attribute.getName(), attribute.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the children of this node.
|
||||||
|
*/
|
||||||
|
public LayoutNode[] getChildren() {
|
||||||
|
if (mChildren == null) {
|
||||||
|
NodeList list = mNode.getChildNodes();
|
||||||
|
int count = list.getLength();
|
||||||
|
List<LayoutNode> children = new ArrayList<LayoutNode>(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Node child = list.item(i);
|
||||||
|
if (child.getNodeType() == Node.ELEMENT_NODE) {
|
||||||
|
children.add(new LayoutNode(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mChildren = children.toArray(new LayoutNode[children.size()]);
|
||||||
|
}
|
||||||
|
return mChildren;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix.groovy;
|
||||||
|
|
||||||
|
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||||
|
import com.android.layoutopt.uix.LayoutNode;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import groovy.lang.GString;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support class for Groovy rules. This class adds new Groovy capabilities
|
||||||
|
* to {@link com.android.layoutopt.uix.LayoutAnalysis} and {@link org.w3c.dom.Node}.
|
||||||
|
*/
|
||||||
|
public class LayoutAnalysisCategory {
|
||||||
|
/**
|
||||||
|
* xmlNode.isRoot()
|
||||||
|
*/
|
||||||
|
public static boolean isRoot(Node node) {
|
||||||
|
return node.getOwnerDocument().getDocumentElement() == node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* xmlNode.is("tagName")
|
||||||
|
*/
|
||||||
|
public static boolean is(Node node, String name) {
|
||||||
|
return node.getNodeName().equals(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* xmlNode.depth()
|
||||||
|
*/
|
||||||
|
public static int depth(Node node) {
|
||||||
|
int maxDepth = 0;
|
||||||
|
NodeList list = node.getChildNodes();
|
||||||
|
int count = list.getLength();
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
maxDepth = Math.max(maxDepth, depth(list.item(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxDepth + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* analysis << "The issue"
|
||||||
|
*/
|
||||||
|
public static LayoutAnalysis leftShift(LayoutAnalysis analysis, GString description) {
|
||||||
|
analysis.addIssue(description.toString());
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* analysis << [node: node, description: "The issue"]
|
||||||
|
*/
|
||||||
|
public static LayoutAnalysis leftShift(LayoutAnalysis analysis, Map issue) {
|
||||||
|
analysis.addIssue((LayoutNode) issue.get("node"), issue.get("description").toString());
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix.rules;
|
||||||
|
|
||||||
|
import groovy.lang.Script;
|
||||||
|
import groovy.lang.Binding;
|
||||||
|
import groovy.lang.Closure;
|
||||||
|
import groovy.xml.dom.DOMCategory;
|
||||||
|
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||||
|
import com.android.layoutopt.uix.LayoutNode;
|
||||||
|
import com.android.layoutopt.uix.groovy.LayoutAnalysisCategory;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.codehaus.groovy.runtime.GroovyCategorySupport;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of a rule using a Groovy script.
|
||||||
|
*/
|
||||||
|
public class GroovyRule implements Rule {
|
||||||
|
private final String mName;
|
||||||
|
private final Script mScript;
|
||||||
|
private final Binding mBinding;
|
||||||
|
private final Closure mClosure;
|
||||||
|
private final List<Class> mCategories;
|
||||||
|
|
||||||
|
public GroovyRule(String name, Script script) {
|
||||||
|
mName = name;
|
||||||
|
mScript = script;
|
||||||
|
mBinding = new Binding();
|
||||||
|
mScript.setBinding(mBinding);
|
||||||
|
mClosure = new Closure(this) {
|
||||||
|
@Override
|
||||||
|
public Object call() {
|
||||||
|
return mScript.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mCategories = new ArrayList<Class>();
|
||||||
|
Collections.addAll(mCategories, DOMCategory.class, LayoutAnalysisCategory.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(LayoutAnalysis analysis, LayoutNode node, Node xml) {
|
||||||
|
mBinding.setVariable("analysis", analysis);
|
||||||
|
mBinding.setVariable("node", node);
|
||||||
|
mBinding.setVariable("xml", xml);
|
||||||
|
|
||||||
|
GroovyCategorySupport.use(mCategories, mClosure);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix.rules;
|
||||||
|
|
||||||
|
import com.android.layoutopt.uix.LayoutAnalysis;
|
||||||
|
import com.android.layoutopt.uix.LayoutNode;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that define an analysis rule.
|
||||||
|
*/
|
||||||
|
public interface Rule {
|
||||||
|
/**
|
||||||
|
* Returns the name of the rule.
|
||||||
|
*
|
||||||
|
* @return A non-null String.
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the rule for the specified node. The rule must add any detected
|
||||||
|
* issue to the analysis.
|
||||||
|
*
|
||||||
|
* @param analysis The resulting analysis.
|
||||||
|
* @param node The layout node to analyse.
|
||||||
|
* @param xml The original XML node.
|
||||||
|
*/
|
||||||
|
void run(LayoutAnalysis analysis, LayoutNode node, Node xml);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix.util;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various utilities related to I/O operations.
|
||||||
|
*/
|
||||||
|
public class IOUtilities {
|
||||||
|
private IOUtilities() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely close a Closeable object, like an InputStream.
|
||||||
|
*
|
||||||
|
* @param stream The object to close.
|
||||||
|
*
|
||||||
|
* @return True if the object is null or was closed properly,
|
||||||
|
* false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean close(Closeable stream) {
|
||||||
|
if (stream != null) {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.layoutopt.uix.xml;
|
||||||
|
|
||||||
|
import com.sun.org.apache.xerces.internal.parsers.DOMParser;
|
||||||
|
import com.sun.org.apache.xerces.internal.xni.XMLLocator;
|
||||||
|
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
|
||||||
|
import com.sun.org.apache.xerces.internal.xni.Augmentations;
|
||||||
|
import com.sun.org.apache.xerces.internal.xni.XNIException;
|
||||||
|
import com.sun.org.apache.xerces.internal.xni.QName;
|
||||||
|
import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.xml.sax.SAXNotRecognizedException;
|
||||||
|
import org.xml.sax.SAXNotSupportedException;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses XML documents. This class tries to add meta-data in the resulting DOM
|
||||||
|
* trees to indicate the start and end line numbers of each node.
|
||||||
|
*/
|
||||||
|
public class XmlDocumentBuilder {
|
||||||
|
/**
|
||||||
|
* Name of the node user data containing the start line number of the node.
|
||||||
|
*
|
||||||
|
* @see Node#getUserData(String)
|
||||||
|
*/
|
||||||
|
public static final String NODE_START_LINE = "startLine";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the node user data containing the end line number of the node.
|
||||||
|
*
|
||||||
|
* @see Node#getUserData(String)
|
||||||
|
*/
|
||||||
|
public static final String NODE_END_LINE = "endLine";
|
||||||
|
|
||||||
|
private final DocumentBuilder mBuilder;
|
||||||
|
private boolean mHasLineNumbersSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new XML document builder.
|
||||||
|
*/
|
||||||
|
public XmlDocumentBuilder() {
|
||||||
|
try {
|
||||||
|
Class.forName("com.sun.org.apache.xerces.internal.parsers.DOMParser");
|
||||||
|
mHasLineNumbersSupport = true;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mHasLineNumbersSupport) {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
try {
|
||||||
|
mBuilder = factory.newDocumentBuilder();
|
||||||
|
} catch (ParserConfigurationException e) {
|
||||||
|
throw new IllegalStateException("Could not initialize the XML parser");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mBuilder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the XML documents created by this class are annotated
|
||||||
|
* with line numbers.
|
||||||
|
*
|
||||||
|
* @return True if the parsed documents contain line numbers meta-data,
|
||||||
|
* false otherwise.
|
||||||
|
*
|
||||||
|
* @see #NODE_START_LINE
|
||||||
|
* @see #NODE_END_LINE
|
||||||
|
*/
|
||||||
|
public boolean isHasLineNumbersSupport() {
|
||||||
|
return mHasLineNumbersSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document parse(InputStream inputStream) throws SAXException, IOException {
|
||||||
|
if (!mHasLineNumbersSupport) {
|
||||||
|
return mBuilder.parse(inputStream);
|
||||||
|
} else {
|
||||||
|
DOMParser parser = new LineNumberDOMParser();
|
||||||
|
parser.parse(new InputSource(inputStream));
|
||||||
|
return parser.getDocument();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document parse(String content) throws SAXException, IOException {
|
||||||
|
if (!mHasLineNumbersSupport) {
|
||||||
|
return mBuilder.parse(content);
|
||||||
|
} else {
|
||||||
|
DOMParser parser = new LineNumberDOMParser();
|
||||||
|
parser.parse(content);
|
||||||
|
return parser.getDocument();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Document parse(File file) throws SAXException, IOException {
|
||||||
|
return parse(new FileInputStream(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LineNumberDOMParser extends DOMParser {
|
||||||
|
private static final String FEATURE_NODE_EXPANSION =
|
||||||
|
"http://apache.org/xml/features/dom/defer-node-expansion";
|
||||||
|
private static final String CURRENT_NODE =
|
||||||
|
"http://apache.org/xml/properties/dom/current-element-node";
|
||||||
|
|
||||||
|
private XMLLocator mLocator;
|
||||||
|
private LinkedList<Node> mStack = new LinkedList<Node>();
|
||||||
|
|
||||||
|
private LineNumberDOMParser() {
|
||||||
|
try {
|
||||||
|
setFeature(FEATURE_NODE_EXPANSION, false);
|
||||||
|
} catch (SAXNotRecognizedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (SAXNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startDocument(XMLLocator xmlLocator, String s,
|
||||||
|
NamespaceContext namespaceContext, Augmentations augmentations)
|
||||||
|
throws XNIException {
|
||||||
|
super.startDocument(xmlLocator, s, namespaceContext, augmentations);
|
||||||
|
|
||||||
|
mLocator = xmlLocator;
|
||||||
|
mStack.add(setNodeLineNumber(NODE_START_LINE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node setNodeLineNumber(String tag) {
|
||||||
|
Node node = null;
|
||||||
|
try {
|
||||||
|
node = (Node) getProperty(CURRENT_NODE);
|
||||||
|
} catch (SAXNotRecognizedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (SAXNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node != null) {
|
||||||
|
node.setUserData(tag, mLocator.getLineNumber(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startElement(QName qName, XMLAttributes xmlAttributes,
|
||||||
|
Augmentations augmentations) throws XNIException {
|
||||||
|
super.startElement(qName, xmlAttributes, augmentations);
|
||||||
|
mStack.add(setNodeLineNumber(NODE_START_LINE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endElement(QName qName, Augmentations augmentations) throws XNIException {
|
||||||
|
super.endElement(qName, augmentations);
|
||||||
|
Node node = mStack.removeLast();
|
||||||
|
if (node != null) {
|
||||||
|
node.setUserData(NODE_END_LINE, mLocator.getLineNumber(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Rule: MergeRootFrameLayout
|
||||||
|
//
|
||||||
|
// Description: Checks whether the root node of the XML document can be
|
||||||
|
// replaced with a <merge /> tag.
|
||||||
|
//
|
||||||
|
// Conditions:
|
||||||
|
// - The node is the root of the document
|
||||||
|
// - The node is a FrameLayout
|
||||||
|
// - The node is fill_parent in both orientation *or* it has no layout_gravity
|
||||||
|
// - The node does not have a background nor a foreground
|
||||||
|
|
||||||
|
if (xml.isRoot() && xml.is("FrameLayout") && !xml.'@android:background' &&
|
||||||
|
!xml.'@android:foreground' && ((node.isWidthFillParent() &&
|
||||||
|
node.isHeightFillParent()) || !xml.'@android:layout_gravity')) {
|
||||||
|
analysis << [node: node, description: "The root-level <FrameLayout/> can be replaced with <merge/>"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Rule: TooManyLevels
|
||||||
|
//
|
||||||
|
// Description: Checks whether the layout has too many nested groups.
|
||||||
|
//
|
||||||
|
// Conditions:
|
||||||
|
// - The depth of the layout is > 10
|
||||||
|
|
||||||
|
if (xml.isRoot() && (depth = xml.depth()) > 10) {
|
||||||
|
analysis << "This layout has too many nested layouts: ${depth} levels!"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Rule: TooManyViews
|
||||||
|
//
|
||||||
|
// Description: Checks whether the layout has too many views.
|
||||||
|
//
|
||||||
|
// Conditions:
|
||||||
|
// - The document contains more than 80 views
|
||||||
|
|
||||||
|
if (xml.isRoot && (size = xml.'**'.size()) > 80) {
|
||||||
|
analysis << "This layout has too many views: ${size} views!"
|
||||||
|
}
|
||||||
7
tools/layoutopt/samples/simple.xml
Normal file
7
tools/layoutopt/samples/simple.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent" />
|
||||||
85
tools/layoutopt/samples/too_deep.xml
Normal file
85
tools/layoutopt/samples/too_deep.xml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
413
tools/layoutopt/samples/too_many.xml
Normal file
413
tools/layoutopt/samples/too_many.xml
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ok" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
Reference in New Issue
Block a user