summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt2/Android.mk1
-rw-r--r--tools/aapt2/Main.cpp49
-rw-r--r--tools/aapt2/ProguardRules.cpp240
-rw-r--r--tools/aapt2/ProguardRules.h58
-rw-r--r--tools/aapt2/Source.h5
-rw-r--r--tools/aapt2/Util.cpp45
-rw-r--r--tools/aapt2/Util.h17
-rw-r--r--tools/aapt2/Util_test.cpp40
-rw-r--r--tools/aapt2/data/AndroidManifest.xml3
-rw-r--r--tools/aapt2/data/Makefile3
-rw-r--r--tools/aapt2/data/res/layout/main.xml3
-rw-r--r--tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml8
-rw-r--r--tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml8
-rw-r--r--tools/layoutlib/.idea/modules.xml4
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java4
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java4
-rw-r--r--tools/layoutlib/bridge/src/android/view/BridgeInflater.java8
-rw-r--r--tools/layoutlib/bridge/src/android/view/ShadowPainter.java10
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java11
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java65
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java15
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java99
-rw-r--r--tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java94
-rw-r--r--tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java31
-rw-r--r--tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java200
-rw-r--r--tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml14
-rw-r--r--tools/orientationplot/README.txt2
-rwxr-xr-xtools/orientationplot/orientationplot.py2
28 files changed, 1026 insertions, 17 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index d311cd9..10f8150 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -40,6 +40,7 @@ sources := \
ManifestParser.cpp \
ManifestValidator.cpp \
Png.cpp \
+ ProguardRules.cpp \
ResChunkPullParser.cpp \
Resource.cpp \
ResourceParser.cpp \
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index de2dafc..41c229d 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -28,6 +28,7 @@
#include "ManifestValidator.h"
#include "NameMangler.h"
#include "Png.h"
+#include "ProguardRules.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceTableResolver.h"
@@ -300,6 +301,9 @@ struct AaptOptions {
// Directory to in which to generate R.java.
Maybe<Source> generateJavaClass;
+ // File in which to produce proguard rules.
+ Maybe<Source> generateProguardRules;
+
// Whether to output verbose details about
// compilation.
bool verbose = false;
@@ -417,7 +421,8 @@ bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>&
bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
- const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) {
+ const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
+ proguard::KeepSet* keepSet) {
SourceLogger logger(item.source);
std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
if (!root) {
@@ -435,6 +440,10 @@ bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t
xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
}
+ if (options.generateProguardRules) {
+ proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
+ }
+
BigBuffer outBuffer(1024);
Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
item.originalPackage, resolver,
@@ -509,7 +518,7 @@ bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outA
bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
- const android::ResTable& table, ZipFile* outApk) {
+ const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
if (options.verbose) {
Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
}
@@ -557,6 +566,11 @@ bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver
}
}
+ if (options.generateProguardRules) {
+ proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
+ keepSet);
+ }
+
BigBuffer outBuffer(1024);
if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
resolver, {}, &outBuffer)) {
@@ -805,8 +819,10 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
return false;
}
+ proguard::KeepSet keepSet;
+
android::ResTable binTable;
- if (!compileManifest(options, resolver, apkFiles, binTable, &outApk)) {
+ if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
return false;
}
@@ -826,7 +842,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
assert(uncompressedData);
if (!linkXml(options, outTable, resolver, item, uncompressedData,
- entry->getUncompressedLen(), &outApk, &linkQueue)) {
+ entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
<< std::endl;
return false;
@@ -883,6 +899,26 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
}
}
+ // Generate the Proguard rules file.
+ if (options.generateProguardRules) {
+ const Source& outPath = options.generateProguardRules.value();
+
+ if (options.verbose) {
+ Logger::note(outPath) << "writing proguard rules." << std::endl;
+ }
+
+ std::ofstream fout(outPath.path);
+ if (!fout) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ if (!proguard::writeKeepSet(&fout, keepSet)) {
+ Logger::error(outPath) << "failed to write proguard rules." << std::endl;
+ return false;
+ }
+ }
+
outTable->getValueStringPool().prune();
outTable->getValueStringPool().sort(
[](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
@@ -1072,6 +1108,11 @@ static AaptOptions prepareArgs(int argc, char** argv) {
options.generateJavaClass = Source{ arg.toString() };
});
+ flag::optionalFlag("--proguard", "file in which to output proguard rules",
+ [&options](const StringPiece& arg) {
+ options.generateProguardRules = Source{ arg.toString() };
+ });
+
flag::optionalSwitch("--static-lib", "generate a static Android library", true,
&isStaticLib);
diff --git a/tools/aapt2/ProguardRules.cpp b/tools/aapt2/ProguardRules.cpp
new file mode 100644
index 0000000..e89fb7c
--- /dev/null
+++ b/tools/aapt2/ProguardRules.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#include "ProguardRules.h"
+#include "Util.h"
+#include "XmlDom.h"
+
+#include <memory>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+class BaseVisitor : public xml::Visitor {
+public:
+ BaseVisitor(const Source& source, KeepSet* keepSet) : mSource(source), mKeepSet(keepSet) {
+ }
+
+ virtual void visit(xml::Text*) override {};
+
+ virtual void visit(xml::Namespace* node) override {
+ for (const auto& child : node->children) {
+ child->accept(this);
+ }
+ }
+
+ virtual void visit(xml::Element* node) override {
+ if (!node->namespaceUri.empty()) {
+ Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+ node->namespaceUri);
+ if (maybePackage) {
+ // This is a custom view, let's figure out the class name from this.
+ std::u16string package = maybePackage.value() + u"." + node->name;
+ if (util::isJavaClassName(package)) {
+ addClass(node->lineNumber, package);
+ }
+ }
+ } else if (util::isJavaClassName(node->name)) {
+ addClass(node->lineNumber, node->name);
+ }
+
+ for (const auto& child: node->children) {
+ child->accept(this);
+ }
+ }
+
+protected:
+ void addClass(size_t lineNumber, const std::u16string& className) {
+ mKeepSet->addClass(mSource.line(lineNumber), className);
+ }
+
+ void addMethod(size_t lineNumber, const std::u16string& methodName) {
+ mKeepSet->addMethod(mSource.line(lineNumber), methodName);
+ }
+
+private:
+ Source mSource;
+ KeepSet* mKeepSet;
+};
+
+struct LayoutVisitor : public BaseVisitor {
+ LayoutVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkClass = false;
+ bool checkName = false;
+ if (node->namespaceUri.empty()) {
+ checkClass = node->name == u"view" || node->name == u"fragment";
+ } else if (node->namespaceUri == kSchemaAndroid) {
+ checkName = node->name == u"fragment";
+ }
+
+ for (const auto& attr : node->attributes) {
+ if (checkClass && attr.namespaceUri.empty() && attr.name == u"class" &&
+ util::isJavaClassName(attr.value)) {
+ addClass(node->lineNumber, attr.value);
+ } else if (checkName && attr.namespaceUri == kSchemaAndroid && attr.name == u"name" &&
+ util::isJavaClassName(attr.value)) {
+ addClass(node->lineNumber, attr.value);
+ } else if (attr.namespaceUri == kSchemaAndroid && attr.name == u"onClick") {
+ addMethod(node->lineNumber, attr.value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct XmlResourceVisitor : public BaseVisitor {
+ XmlResourceVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkFragment = false;
+ if (node->namespaceUri.empty()) {
+ checkFragment = node->name == u"PreferenceScreen" || node->name == u"header";
+ }
+
+ if (checkFragment) {
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"fragment");
+ if (attr && util::isJavaClassName(attr->value)) {
+ addClass(node->lineNumber, attr->value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct TransitionVisitor : public BaseVisitor {
+ TransitionVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ bool checkClass = node->namespaceUri.empty() &&
+ (node->name == u"transition" || node->name == u"pathMotion");
+ if (checkClass) {
+ xml::Attribute* attr = node->findAttribute({}, u"class");
+ if (attr && util::isJavaClassName(attr->value)) {
+ addClass(node->lineNumber, attr->value);
+ }
+ }
+
+ BaseVisitor::visit(node);
+ }
+};
+
+struct ManifestVisitor : public BaseVisitor {
+ ManifestVisitor(const Source& source, KeepSet* keepSet) : BaseVisitor(source, keepSet) {
+ }
+
+ virtual void visit(xml::Element* node) override {
+ if (node->namespaceUri.empty()) {
+ bool getName = false;
+ if (node->name == u"manifest") {
+ xml::Attribute* attr = node->findAttribute({}, u"package");
+ if (attr) {
+ mPackage = attr->value;
+ }
+ } else if (node->name == u"application") {
+ getName = true;
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"backupAgent");
+ if (attr) {
+ Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
+ attr->value);
+ if (result) {
+ addClass(node->lineNumber, result.value());
+ }
+ }
+ } else if (node->name == u"activity" || node->name == u"service" ||
+ node->name == u"receiver" || node->name == u"provider" ||
+ node->name == u"instrumentation") {
+ getName = true;
+ }
+
+ if (getName) {
+ xml::Attribute* attr = node->findAttribute(kSchemaAndroid, u"name");
+ if (attr) {
+ Maybe<std::u16string> result = util::getFullyQualifiedClassName(mPackage,
+ attr->value);
+ if (result) {
+ addClass(node->lineNumber, result.value());
+ }
+ }
+ }
+ }
+ BaseVisitor::visit(node);
+ }
+
+ std::u16string mPackage;
+};
+
+bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet) {
+ ManifestVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ return true;
+}
+
+bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
+ KeepSet* keepSet) {
+ switch (type) {
+ case ResourceType::kLayout: {
+ LayoutVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kXml: {
+ XmlResourceVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ case ResourceType::kTransition: {
+ TransitionVisitor visitor(source, keepSet);
+ node->accept(&visitor);
+ break;
+ }
+
+ default:
+ break;
+ }
+ return true;
+}
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet) {
+ for (const auto& entry : keepSet.mKeepSet) {
+ for (const SourceLine& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+ }
+
+ for (const auto& entry : keepSet.mKeepMethodSet) {
+ for (const SourceLine& source : entry.second) {
+ *out << "// Referenced at " << source << "\n";
+ }
+ *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
+ }
+ return true;
+}
+
+} // namespace proguard
+} // namespace aapt
diff --git a/tools/aapt2/ProguardRules.h b/tools/aapt2/ProguardRules.h
new file mode 100644
index 0000000..bbb3e64
--- /dev/null
+++ b/tools/aapt2/ProguardRules.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef AAPT_PROGUARD_RULES_H
+#define AAPT_PROGUARD_RULES_H
+
+#include "Resource.h"
+#include "Source.h"
+#include "XmlDom.h"
+
+#include <map>
+#include <ostream>
+#include <set>
+#include <string>
+
+namespace aapt {
+namespace proguard {
+
+class KeepSet {
+public:
+ inline void addClass(const SourceLine& source, const std::u16string& className) {
+ mKeepSet[className].insert(source);
+ }
+
+ inline void addMethod(const SourceLine& source, const std::u16string& methodName) {
+ mKeepMethodSet[methodName].insert(source);
+ }
+
+private:
+ friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+ std::map<std::u16string, std::set<SourceLine>> mKeepSet;
+ std::map<std::u16string, std::set<SourceLine>> mKeepMethodSet;
+};
+
+bool collectProguardRulesForManifest(const Source& source, xml::Node* node, KeepSet* keepSet);
+bool collectProguardRules(ResourceType type, const Source& source, xml::Node* node,
+ KeepSet* keepSet);
+
+bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
+
+} // namespace proguard
+} // namespace aapt
+
+#endif // AAPT_PROGUARD_RULES_H
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 10c75aa..3606488 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -19,6 +19,7 @@
#include <ostream>
#include <string>
+#include <tuple>
namespace aapt {
@@ -80,6 +81,10 @@ inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& s
return out << source.path << ":" << source.line << ":" << source.column;
}
+inline bool operator<(const SourceLine& lhs, const SourceLine& rhs) {
+ return std::tie(lhs.path, lhs.line) < std::tie(rhs.path, rhs.line);
+}
+
} // namespace aapt
#endif // AAPT_SOURCE_H
diff --git a/tools/aapt2/Util.cpp b/tools/aapt2/Util.cpp
index 7adaf1e..03ecd1a 100644
--- a/tools/aapt2/Util.cpp
+++ b/tools/aapt2/Util.cpp
@@ -102,6 +102,51 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16
return endIter;
}
+bool isJavaClassName(const StringPiece16& str) {
+ size_t pieces = 0;
+ for (const StringPiece16& piece : tokenize(str, u'.')) {
+ pieces++;
+ if (piece.empty()) {
+ return false;
+ }
+
+ // Can't have starting or trailing $ character.
+ if (piece.data()[0] == u'$' || piece.data()[piece.size() - 1] == u'$') {
+ return false;
+ }
+
+ if (findNonAlphaNumericAndNotInSet(piece, u"$_") != piece.end()) {
+ return false;
+ }
+ }
+ return pieces >= 2;
+}
+
+Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
+ const StringPiece16& className) {
+ if (className.empty()) {
+ return {};
+ }
+
+ if (util::isJavaClassName(className)) {
+ return className.toString();
+ }
+
+ if (package.empty()) {
+ return {};
+ }
+
+ std::u16string result(package.data(), package.size());
+ if (className.data()[0] != u'.') {
+ result += u'.';
+ }
+ result.append(className.data(), className.size());
+ if (!isJavaClassName(result)) {
+ return {};
+ }
+ return result;
+}
+
static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
char16_t code = 0;
for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
diff --git a/tools/aapt2/Util.h b/tools/aapt2/Util.h
index 6015d82..9cdb152 100644
--- a/tools/aapt2/Util.h
+++ b/tools/aapt2/Util.h
@@ -78,6 +78,23 @@ StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16
const StringPiece16& allowedChars);
/**
+ * Tests that the string is a valid Java class name.
+ */
+bool isJavaClassName(const StringPiece16& str);
+
+/**
+ * Converts the class name to a fully qualified class name from the given `package`. Ex:
+ *
+ * asdf --> package.asdf
+ * .asdf --> package.asdf
+ * .a.b --> package.a.b
+ * asdf.adsf --> asdf.adsf
+ */
+Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package,
+ const StringPiece16& className);
+
+
+/**
* Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
* This will be present in C++14 and can be removed then.
*/
diff --git a/tools/aapt2/Util_test.cpp b/tools/aapt2/Util_test.cpp
index c16f6bb..0b08d24 100644
--- a/tools/aapt2/Util_test.cpp
+++ b/tools/aapt2/Util_test.cpp
@@ -93,4 +93,44 @@ TEST(UtilTest, TokenizeInput) {
ASSERT_EQ(tokenizer.end(), iter);
}
+TEST(UtilTest, IsJavaClassName) {
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"android.test.Class$Inner"));
+ EXPECT_TRUE(util::isJavaClassName(u"android_test.test.Class"));
+ EXPECT_TRUE(util::isJavaClassName(u"_android_.test._Class_"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.$Inner"));
+ EXPECT_FALSE(util::isJavaClassName(u"android.test.Inner$"));
+ EXPECT_FALSE(util::isJavaClassName(u".test.Class"));
+ EXPECT_FALSE(util::isJavaClassName(u"android"));
+}
+
+TEST(UtilTest, FullyQualifiedClassName) {
+ Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.asdf");
+
+ res = util::getFullyQualifiedClassName(u"android", u".asdf");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.asdf");
+
+ res = util::getFullyQualifiedClassName(u"android", u".a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"android.a.b");
+
+ res = util::getFullyQualifiedClassName(u"android", u"a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"a.b");
+ ASSERT_TRUE(res);
+ EXPECT_EQ(res.value(), u"a.b");
+
+ res = util::getFullyQualifiedClassName(u"", u"");
+ ASSERT_FALSE(res);
+
+ res = util::getFullyQualifiedClassName(u"android", u"./Apple");
+ ASSERT_FALSE(res);
+}
+
+
} // namespace aapt
diff --git a/tools/aapt2/data/AndroidManifest.xml b/tools/aapt2/data/AndroidManifest.xml
index c017a0d..8533c28 100644
--- a/tools/aapt2/data/AndroidManifest.xml
+++ b/tools/aapt2/data/AndroidManifest.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app">
- <application>
+ <application
+ android:name=".Activity">
</application>
</manifest>
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
index ce5201b..3387135 100644
--- a/tools/aapt2/data/Makefile
+++ b/tools/aapt2/data/Makefile
@@ -15,6 +15,7 @@ LOCAL_RESOURCE_DIR := res
LOCAL_LIBS := lib/out/package.apk
LOCAL_OUT := out
LOCAL_GEN := out/gen
+LOCAL_PROGUARD := out/proguard.rule
##
# AAPT2 custom rules.
@@ -57,7 +58,7 @@ $(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
- $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS)
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) --proguard $(LOCAL_PROGUARD) -v
# R.java: gen/com/android/app/R.java <- out/resources.arsc
# No action since R.java is generated when out/resources.arsc is.
diff --git a/tools/aapt2/data/res/layout/main.xml b/tools/aapt2/data/res/layout/main.xml
index 77ccedb..50a51d9 100644
--- a/tools/aapt2/data/res/layout/main.xml
+++ b/tools/aapt2/data/res/layout/main.xml
@@ -5,11 +5,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <fragment class="android.test.sample.App$Inner" />
+
<variable name="user" type="com.android.User" />
<View xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/me"
android:layout_width="1dp"
+ android:onClick="doClick"
android:text="@{user.name}"
android:layout_height="match_parent"
app:layout_width="@support:bool/allow"
diff --git a/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml b/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml
new file mode 100644
index 0000000..0450be3
--- /dev/null
+++ b/tools/layoutlib/.idea/artifacts/studio_android_widgets_jar.xml
@@ -0,0 +1,8 @@
+<component name="ArtifactManager">
+ <artifact type="jar" name="studio-android-widgets:jar">
+ <output-path>$PROJECT_DIR$/out/artifacts/studio_android_widgets_jar</output-path>
+ <root id="archive" name="studio-android-widgets.jar">
+ <element id="module-output" name="studio-android-widgets" />
+ </root>
+ </artifact>
+</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml b/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml
new file mode 100644
index 0000000..a844ca3
--- /dev/null
+++ b/tools/layoutlib/.idea/artifacts/studio_android_widgets_src_jar.xml
@@ -0,0 +1,8 @@
+<component name="ArtifactManager">
+ <artifact type="jar" name="studio-android-widgets-src:jar">
+ <output-path>$PROJECT_DIR$/out/artifacts/studio_android_widgets_src_jar</output-path>
+ <root id="archive" name="studio-android-widgets-src.jar">
+ <element id="dir-copy" path="$PROJECT_DIR$/studio-custom-widgets/src" />
+ </root>
+ </artifact>
+</component> \ No newline at end of file
diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml
index 684f4fd..9bdc381 100644
--- a/tools/layoutlib/.idea/modules.xml
+++ b/tools/layoutlib/.idea/modules.xml
@@ -4,7 +4,7 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" />
<module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" />
+ <module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" />
</modules>
</component>
-</project>
-
+</project> \ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
index 703719c..cbc30c3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java
@@ -23,6 +23,8 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.Shader.TileMode;
+import java.awt.image.ColorModel;
+
/**
* Delegate implementing the native methods of android.graphics.LinearGradient
*
@@ -158,7 +160,7 @@ public final class LinearGradient_Delegate extends Gradient_Delegate {
java.awt.image.ColorModel colorModel) {
mCanvasMatrix = canvasMatrix;
mLocalMatrix = localMatrix;
- mColorModel = colorModel;
+ mColorModel = colorModel.hasAlpha() ? colorModel : ColorModel.getRGBdefault();
}
@Override
diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
index 04e423b..9881a38 100644
--- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java
@@ -23,6 +23,8 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.graphics.Shader.TileMode;
+import java.awt.image.ColorModel;
+
/**
* Delegate implementing the native methods of android.graphics.RadialGradient
*
@@ -146,7 +148,7 @@ public class RadialGradient_Delegate extends Gradient_Delegate {
java.awt.image.ColorModel colorModel) {
mCanvasMatrix = canvasMatrix;
mLocalMatrix = localMatrix;
- mColorModel = colorModel;
+ mColorModel = colorModel.hasAlpha() ? colorModel : ColorModel.getRGBdefault();
}
@Override
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 32ee9e8..6767a07 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -22,6 +22,7 @@ import com.android.ide.common.rendering.api.MergeCookie;
import com.android.ide.common.rendering.api.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.impl.ParserFactory;
@@ -234,6 +235,13 @@ public final class BridgeInflater extends LayoutInflater {
if (viewKey != null) {
bc.addViewKey(view, viewKey);
}
+ String scrollPos = attrs.getAttributeValue(BridgeConstants.NS_RESOURCES, "scrollY");
+ if (scrollPos != null) {
+ if (scrollPos.endsWith("px")) {
+ int value = Integer.parseInt(scrollPos.substring(0, scrollPos.length() - 2));
+ bc.setScrollYPos(view, value);
+ }
+ }
}
}
diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
index c734ea1..f09fffd 100644
--- a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
@@ -34,12 +34,15 @@ public class ShadowPainter {
* new image. This method attempts to mimic the same visual characteristics as the rectangular
* shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)}
* and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}.
+ * <p/>
+ * If shadowSize is less or equals to 1, no shadow will be painted and the source image will be
+ * returned instead.
*
* @param source the source image
* @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
* #SMALL_SHADOW_SIZE}}
*
- * @return a new image with the shadow painted in
+ * @return an image with the shadow painted in or the source image if shadowSize <= 1
*/
@NonNull
public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
@@ -60,12 +63,13 @@ public class ShadowPainter {
* @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque
* @param shadowRgb the RGB int to use for the shadow color
*
- * @return a new image with the source image on top of its shadow
+ * @return a new image with the source image on top of its shadow when shadowSize > 0 or the
+ * source image otherwise
*/
@SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"}) // Imported code
public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
float shadowOpacity, int shadowRgb) {
- if (shadowSize == 0) {
+ if (shadowSize <= 0) {
return source;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index a2518fa..6be5a95 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -37,6 +37,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.Nullable;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -125,6 +126,7 @@ public final class BridgeContext extends Context {
private final LayoutlibCallback mLayoutlibCallback;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
+ private final HashMap<View, Integer> mScrollYPos = new HashMap<View, Integer>();
private Resources.Theme mTheme;
@@ -1738,4 +1740,13 @@ public final class BridgeContext extends Context {
// pass
return new File[0];
}
+
+ public void setScrollYPos(@NonNull View view, int scrollPos) {
+ mScrollYPos.put(view, scrollPos);
+ }
+
+ public int getScrollYPos(@NonNull View view) {
+ Integer pos = mScrollYPos.get(view);
+ return pos != null ? pos : 0;
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
new file mode 100644
index 0000000..0426907
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/DesignLibUtil.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 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.layoutlib.bridge.android.support;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.View;
+
+import java.lang.reflect.Method;
+
+import static com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod;
+import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke;
+
+/**
+ * Utility class for working with the design support lib.
+ */
+public class DesignLibUtil {
+
+ private static final String PKG_PREFIX = "android.support.design.widget.";
+ public static final String CN_COORDINATOR_LAYOUT = PKG_PREFIX + "CoordinatorLayout";
+ public static final String CN_APPBAR_LAYOUT = PKG_PREFIX + "AppBarLayout";
+ public static final String CN_COLLAPSING_TOOLBAR_LAYOUT =
+ PKG_PREFIX + "CollapsingToolbarLayout";
+ public static final String CN_TOOLBAR = "android.support.v7.widget.Toolbar";
+ public static final int SCROLL_AXIS_VERTICAL = 1 << 1;
+
+ /**
+ * Tries to set the title of a view. This is used to set the title in a
+ * CollapsingToolbarLayout.
+ * <p/>
+ * Any exceptions thrown during the process are logged in {@link Bridge#getLog()}
+ */
+ public static void setTitle(@NonNull View view, @Nullable String title) {
+ if (title == null) {
+ return;
+ }
+ try {
+ Method setTitle = getMethod(view.getClass(), "setTitle", CharSequence.class);
+ if (setTitle != null) {
+ invoke(setTitle, view, title);
+ }
+ } catch (ReflectionException e) {
+ Bridge.getLog().warning(LayoutLog.TAG_INFO,
+ "Error occurred while trying to set title.", e);
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index c34f9b5..a39eb4d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -616,6 +616,8 @@ public class GcSnapshot {
return;
}
+ int x = 0;
+ int y = 0;
int width;
int height;
Rectangle clipBounds = originalGraphics.getClipBounds();
@@ -626,6 +628,8 @@ public class GcSnapshot {
}
// If we have clipBounds available, use them as they will always be
// smaller than the full layer size.
+ x = clipBounds.x;
+ y = clipBounds.y;
width = clipBounds.width;
height = clipBounds.height;
} else {
@@ -646,13 +650,20 @@ public class GcSnapshot {
true /*compositeOnly*/, forceMode);
try {
// The main draw operation.
+ // We translate the operation to take into account that the rendering does not
+ // know about the clipping area.
+ imageGraphics.translate(-x, -y);
drawable.draw(imageGraphics, paint);
// Apply the color filter.
+ // Restore the original coordinates system and apply the filter only to the
+ // clipped area.
+ imageGraphics.translate(x, y);
filter.applyFilter(imageGraphics, width, height);
- // Draw the tinted image on the main layer.
- configuredGraphics.drawImage(image, 0, 0, null);
+ // Draw the tinted image on the main layer using as start point the clipping
+ // upper left coordinates.
+ configuredGraphics.drawImage(image, x, y, null);
layer.change();
} finally {
// dispose Graphics2D objects
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index ff77b58..d571d35 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -44,6 +44,7 @@ import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
import com.android.layoutlib.bridge.android.RenderParamsFlags;
+import com.android.layoutlib.bridge.android.support.DesignLibUtil;
import com.android.layoutlib.bridge.android.support.RecyclerViewUtil;
import com.android.layoutlib.bridge.bars.AppCompatActionBar;
import com.android.layoutlib.bridge.bars.BridgeActionBar;
@@ -148,7 +149,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
private int mTitleBarSize;
private int mActionBarSize;
-
// information being returned through the API
private BufferedImage mImage;
private List<ViewInfo> mViewInfoList;
@@ -424,6 +424,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// post-inflate process. For now this supports TabHost/TabWidget
postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null);
+ setActiveToolbar(view, context, params);
+
// get the background drawable
if (mWindowBackground != null) {
Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
@@ -544,6 +546,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
// now do the layout.
mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
+ handleScrolling(mViewRoot);
+
if (params.isLayoutOnly()) {
// delete the canvas and image to reset them on the next full rendering
mImage = null;
@@ -1350,6 +1354,99 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
/**
+ * If the root layout is a CoordinatorLayout with an AppBar:
+ * Set the title of the AppBar to the title of the activity context.
+ */
+ private void setActiveToolbar(View view, BridgeContext context, SessionParams params) {
+ View coordinatorLayout = findChildView(view, DesignLibUtil.CN_COORDINATOR_LAYOUT);
+ if (coordinatorLayout == null) {
+ return;
+ }
+ View appBar = findChildView(coordinatorLayout, DesignLibUtil.CN_APPBAR_LAYOUT);
+ if (appBar == null) {
+ return;
+ }
+ ViewGroup collapsingToolbar =
+ (ViewGroup) findChildView(appBar, DesignLibUtil.CN_COLLAPSING_TOOLBAR_LAYOUT);
+ if (collapsingToolbar == null) {
+ return;
+ }
+ if (!hasToolbar(collapsingToolbar)) {
+ return;
+ }
+ RenderResources res = context.getRenderResources();
+ String title = params.getAppLabel();
+ ResourceValue titleValue = res.findResValue(title, false);
+ if (titleValue != null && titleValue.getValue() != null) {
+ title = titleValue.getValue();
+ }
+ DesignLibUtil.setTitle(collapsingToolbar, title);
+ }
+
+ private View findChildView(View view, String className) {
+ if (!(view instanceof ViewGroup)) {
+ return null;
+ }
+ ViewGroup group = (ViewGroup) view;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ if (isInstanceOf(group.getChildAt(i), className)) {
+ return group.getChildAt(i);
+ }
+ }
+ return null;
+ }
+
+ private boolean hasToolbar(View collapsingToolbar) {
+ if (!(collapsingToolbar instanceof ViewGroup)) {
+ return false;
+ }
+ ViewGroup group = (ViewGroup) collapsingToolbar;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ if (isInstanceOf(group.getChildAt(i), DesignLibUtil.CN_TOOLBAR)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Set the vertical scroll position on all the components with the "scrollY" attribute. If the
+ * component supports nested scrolling attempt that first, then use the unconsumed scroll part
+ * to scroll the content in the component.
+ */
+ private void handleScrolling(View view) {
+ BridgeContext context = getContext();
+ int scrollPos = context.getScrollYPos(view);
+ if (scrollPos != 0) {
+ if (view.isNestedScrollingEnabled()) {
+ int[] consumed = new int[2];
+ if (view.startNestedScroll(DesignLibUtil.SCROLL_AXIS_VERTICAL)) {
+ view.dispatchNestedPreScroll(0, scrollPos, consumed, null);
+ view.dispatchNestedScroll(consumed[0], consumed[1], 0, scrollPos, null);
+ view.stopNestedScroll();
+ scrollPos -= consumed[1];
+ }
+ }
+ if (scrollPos != 0) {
+ view.scrollBy(0, scrollPos);
+ } else {
+ view.scrollBy(0, scrollPos);
+ }
+ } else {
+ view.scrollBy(0, scrollPos);
+ }
+
+ if (!(view instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup group = (ViewGroup) view;
+ for (int i = 0; i < group.getChildCount(); i++) {
+ View child = group.getChildAt(i);
+ handleScrolling(child);
+ }
+ }
+
+ /**
* Check if the object is an instance of a class named {@code className}. This doesn't work
* for interfaces.
*/
diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java
new file mode 100644
index 0000000..ecf39b3
--- /dev/null
+++ b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ErrorCatcher.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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.tools.idea.editors.theme.widgets;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * {@link ViewGroup} that wraps another view and catches any possible exceptions that the child view
+ * might generate.
+ * This is used by the theme editor to stop custom views from breaking the preview.
+ */
+// TODO: This view is just a temporary solution that will be replaced by adding a try / catch
+// for custom views in the ClassConverter
+public class ErrorCatcher extends ViewGroup {
+ public ErrorCatcher(Context context) {
+ super(context);
+ }
+
+ public ErrorCatcher(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ErrorCatcher(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ErrorCatcher(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ assert getChildCount() == 1 : "ErrorCatcher can only have one child";
+
+ View child = getChildAt(0);
+ try {
+ measureChild(child, widthMeasureSpec, heightMeasureSpec);
+
+ setMeasuredDimension(resolveSize(child.getMeasuredWidth(), widthMeasureSpec),
+ resolveSize(child.getMeasuredHeight(), heightMeasureSpec));
+ } catch (Throwable t) {
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to do onMeasure for view " +
+ child.getClass().getCanonicalName(), t);
+ setMeasuredDimension(resolveSize(0, widthMeasureSpec),
+ resolveSize(0, heightMeasureSpec));
+ }
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ try {
+ return super.drawChild(canvas, child, drawingTime);
+ } catch (Throwable t) {
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to draw for view " +
+ child.getClass().getCanonicalName(), t);
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ assert getChildCount() == 1 : "ErrorCatcher can only have one child";
+
+ View child = getChildAt(0);
+ try {
+ child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
+ } catch (Throwable e) {
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN, "Failed to do onLayout for view " +
+ child.getClass().getCanonicalName(), e);
+ }
+ }
+}
diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java
new file mode 100644
index 0000000..4320157
--- /dev/null
+++ b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/PressedButton.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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.tools.idea.editors.theme.widgets;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+@SuppressWarnings("unused")
+public class PressedButton extends Button {
+ public PressedButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setPressed(true);
+ jumpDrawablesToCurrentState();
+ }
+}
diff --git a/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java
new file mode 100644
index 0000000..af89910
--- /dev/null
+++ b/tools/layoutlib/studio-custom-widgets/src/com/android/tools/idea/editors/theme/widgets/ThemePreviewLayout.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 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.tools.idea.editors.theme.widgets;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Custom layout used in the theme editor to display the component preview. It arranges the child
+ * Views as a grid of cards.
+ * <p/>
+ * The Views are measured and the maximum width and height are used to dimension all the child
+ * components. Any margin attributes from the children are ignored and only the item_margin element
+ * is used.
+ */
+@SuppressWarnings("unused")
+public class ThemePreviewLayout extends ViewGroup {
+ private final int mMaxColumns;
+ private final int mMaxColumnWidth;
+ private final int mMinColumnWidth;
+ private final int mItemHorizontalMargin;
+ private final int mItemVerticalMargin;
+
+ /** Item width to use for every card component. This includes margins. */
+ private int mItemWidth;
+ /** Item height to use for every card component. This includes margins. */
+ private int mItemHeight;
+
+ /** Calculated number of columns */
+ private int mNumColumns;
+
+ public ThemePreviewLayout(Context context) {
+ this(context, null);
+ }
+
+ public ThemePreviewLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ThemePreviewLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ if (attrs == null) {
+ mMaxColumnWidth = Integer.MAX_VALUE;
+ mMinColumnWidth = 0;
+ mMaxColumns = Integer.MAX_VALUE;
+ mItemHorizontalMargin = 0;
+ mItemVerticalMargin = 0;
+ return;
+ }
+
+ DisplayMetrics dm = getResources().getDisplayMetrics();
+ int maxColumnWidth = attrs.getAttributeIntValue(null, "max_column_width", Integer
+ .MAX_VALUE);
+ int minColumnWidth = attrs.getAttributeIntValue(null, "min_column_width", 0);
+ int itemHorizontalMargin = attrs.getAttributeIntValue(null, "item_horizontal_margin", 0);
+ int itemVerticalMargin = attrs.getAttributeIntValue(null, "item_vertical_margin", 0);
+
+ mMaxColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ maxColumnWidth,
+ dm);
+ mMinColumnWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ minColumnWidth,
+ dm);
+ mItemHorizontalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ itemHorizontalMargin,
+ dm);
+ mItemVerticalMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ itemVerticalMargin,
+ dm);
+ mMaxColumns = attrs.getAttributeIntValue(null, "max_columns", Integer.MAX_VALUE);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Measure the column size.
+ // The column has a minimum width that will be used to calculate the maximum number of
+ // columns that we can fit in the available space.
+ //
+ // Once we have the maximum number of columns, we will span all columns width evenly to fill
+ // all the available space.
+ int wSize = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
+
+ // Calculate the desired width of all columns and take the maximum.
+ // This step can be skipped if we have a fixed column height so we do not have to
+ // dynamically calculate it.
+ int childWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ int itemWidth = 0;
+ int itemHeight = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+
+ if (v.getVisibility() == GONE) {
+ continue;
+ }
+
+ measureChild(v, childWidthSpec, childHeightSpec);
+
+ itemWidth = Math.max(itemWidth, v.getMeasuredWidth());
+ itemHeight = Math.max(itemHeight, v.getMeasuredHeight());
+ }
+
+ itemWidth = Math.min(Math.max(itemWidth, mMinColumnWidth), mMaxColumnWidth);
+ mNumColumns = Math.min((int) Math.ceil((double) wSize / itemWidth), mMaxColumns);
+
+ // Check how much space this distribution would take taking into account the margins.
+ // If it's bigger than what we have, remove one column.
+ int wSizeNeeded = mNumColumns * itemWidth + (mNumColumns - 1) * mItemHorizontalMargin;
+ if (wSizeNeeded > wSize && mNumColumns > 1) {
+ mNumColumns--;
+ }
+
+ if (getChildCount() < mNumColumns) {
+ mNumColumns = getChildCount();
+ }
+ if (mNumColumns == 0) {
+ mNumColumns = 1;
+ }
+
+ // Inform each child of the measurement
+ childWidthSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY);
+ childHeightSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+
+ if (v.getVisibility() == GONE) {
+ continue;
+ }
+
+ measureChild(v, childWidthSpec, childHeightSpec);
+ }
+
+ // Calculate the height of the first column to measure our own size
+ int firstColumnItems = getChildCount() / mNumColumns + ((getChildCount() % mNumColumns) > 0
+ ? 1 : 0);
+
+ int horizontalMarginsTotalWidth = (mNumColumns - 1) * mItemHorizontalMargin;
+ int verticalMarginsTotalHeight = (firstColumnItems - 1) * mItemVerticalMargin;
+ int totalWidth = mNumColumns * itemWidth + horizontalMarginsTotalWidth +
+ mPaddingRight + mPaddingLeft;
+ int totalHeight = firstColumnItems * itemHeight + verticalMarginsTotalHeight +
+ mPaddingBottom + mPaddingTop;
+
+ setMeasuredDimension(resolveSize(totalWidth, widthMeasureSpec),
+ resolveSize(totalHeight, heightMeasureSpec));
+
+ mItemWidth = itemWidth;
+ mItemHeight = itemHeight;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int itemsPerColumn = getChildCount() / mNumColumns;
+ // The remainder items are distributed one per column.
+ int remainderItems = getChildCount() % mNumColumns;
+
+ int x = mPaddingLeft;
+ int y = mPaddingTop;
+ int position = 1;
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+ v.layout(x,
+ y,
+ x + mItemWidth,
+ y + mItemHeight);
+
+ if (position == itemsPerColumn + (remainderItems > 0 ? 1 : 0)) {
+ // Break column
+ position = 1;
+ remainderItems--;
+ x += mItemWidth + mItemHorizontalMargin;
+ y = mPaddingTop;
+ } else {
+ position++;
+ y += mItemHeight + mItemVerticalMargin;
+ }
+ }
+ }
+}
+
+
diff --git a/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml b/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml
new file mode 100644
index 0000000..b0363d7
--- /dev/null
+++ b/tools/layoutlib/studio-custom-widgets/studio-android-widgets.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" />
+ <orderEntry type="library" name="framework.jar" level="project" />
+ <orderEntry type="module" module-name="bridge" />
+ </component>
+</module> \ No newline at end of file
diff --git a/tools/orientationplot/README.txt b/tools/orientationplot/README.txt
index d53f65e..958207d 100644
--- a/tools/orientationplot/README.txt
+++ b/tools/orientationplot/README.txt
@@ -9,6 +9,8 @@ PREREQUISITES
2. numpy
3. matplotlib
+eg. sudo apt-get install python-numpy python-matplotlib
+
USAGE
-----
diff --git a/tools/orientationplot/orientationplot.py b/tools/orientationplot/orientationplot.py
index 6fc3922..77ed074 100755
--- a/tools/orientationplot/orientationplot.py
+++ b/tools/orientationplot/orientationplot.py
@@ -440,7 +440,7 @@ class Plotter:
# Notice
print "Window Orientation Listener plotting tool"
print "-----------------------------------------\n"
-print "Please turn on the Window Orientation Listener logging in Development Settings."
+print "Please turn on the Window Orientation Listener logging. See README.txt."
# Start adb.
print "Starting adb logcat.\n"