blob: cee0cd52a546c72915b663a1a71cbc03fbada237 [file] [log] [blame]
/*
* Copyright (C) 2016 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 "aapt.h"
#include "command.h"
#include "print.h"
#include "util.h"
#include <regex>
const regex NS_REGEX("( *)N: ([^=]+)=(.*)");
const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)");
const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*");
const string ANDROID_NS("http://schemas.android.com/apk/res/android");
bool
Apk::HasActivity(const string& className)
{
string fullClassName = full_class_name(package, className);
const size_t N = activities.size();
for (size_t i=0; i<N; i++) {
if (activities[i] == fullClassName) {
return true;
}
}
return false;
}
struct Attribute {
string ns;
string name;
string value;
};
struct Element {
Element* parent;
string ns;
string name;
int lineno;
vector<Attribute> attributes;
vector<Element*> children;
/**
* Indentation in the xmltree dump. Might not be equal to the distance
* from the root because namespace rows (scopes) have their own indentation.
*/
int depth;
Element();
~Element();
string GetAttr(const string& ns, const string& name) const;
void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse);
};
Element::Element()
{
}
Element::~Element()
{
const size_t N = children.size();
for (size_t i=0; i<N; i++) {
delete children[i];
}
}
string
Element::GetAttr(const string& ns, const string& name) const
{
const size_t N = attributes.size();
for (size_t i=0; i<N; i++) {
const Attribute& attr = attributes[i];
if (attr.ns == ns && attr.name == name) {
return attr.value;
}
}
return string();
}
void
Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse)
{
const size_t N = children.size();
for (size_t i=0; i<N; i++) {
Element* child = children[i];
if (child->ns == ns && child->name == name) {
result->push_back(child);
}
if (recurse) {
child->FindElements(ns, name, result, recurse);
}
}
}
struct Scope {
Scope* parent;
int depth;
map<string,string> namespaces;
Scope(Scope* parent, int depth);
};
Scope::Scope(Scope* p, int d)
:parent(p),
depth(d)
{
if (p != NULL) {
namespaces = p->namespaces;
}
}
string
full_class_name(const string& packageName, const string& className)
{
if (className.length() == 0) {
return "";
}
if (className[0] == '.') {
return packageName + className;
}
if (className.find('.') == string::npos) {
return packageName + "." + className;
}
return className;
}
string
pretty_component_name(const string& packageName, const string& className)
{
if (starts_with(packageName, className)) {
size_t pn = packageName.length();
size_t cn = className.length();
if (cn > pn && className[pn] == '.') {
return packageName + "/" + string(className, pn, string::npos);
}
}
return packageName + "/" + className;
}
int
inspect_apk(Apk* apk, const string& filename)
{
// Load the manifest xml
Command cmd("aapt2");
cmd.AddArg("dump");
cmd.AddArg("xmltree");
cmd.AddArg(filename);
cmd.AddArg("--file");
cmd.AddArg("AndroidManifest.xml");
int err;
string output = get_command_output(cmd, &err, false);
check_error(err);
// Parse the manifest xml
Scope* scope = new Scope(NULL, -1);
Element* root = NULL;
Element* current = NULL;
vector<string> lines;
split_lines(&lines, output);
for (size_t i=0; i<lines.size(); i++) {
const string& line = lines[i];
smatch match;
if (regex_match(line, match, NS_REGEX)) {
int depth = match[1].length() / 2;
while (depth < scope->depth) {
Scope* tmp = scope;
scope = scope->parent;
delete tmp;
}
scope = new Scope(scope, depth);
scope->namespaces[match[2]] = match[3];
} else if (regex_match(line, match, ELEMENT_REGEX)) {
Element* element = new Element();
string str = match[2];
size_t colon = str.find(':');
if (colon == string::npos) {
element->name = str;
} else {
element->ns = scope->namespaces[string(str, 0, colon)];
element->name.assign(str, colon+1, string::npos);
}
element->lineno = atoi(match[3].str().c_str());
element->depth = match[1].length() / 2;
if (root == NULL) {
current = element;
root = element;
} else {
while (element->depth <= current->depth && current->parent != NULL) {
current = current->parent;
}
element->parent = current;
current->children.push_back(element);
current = element;
}
} else if (regex_match(line, match, ATTR_REGEX)) {
if (current != NULL) {
Attribute attr;
string str = match[2];
size_t colon = str.rfind(':');
if (colon == string::npos) {
attr.name = str;
} else {
attr.ns.assign(str, 0, colon);
attr.name.assign(str, colon+1, string::npos);
}
attr.value = match[3];
current->attributes.push_back(attr);
}
}
}
while (scope != NULL) {
Scope* tmp = scope;
scope = scope->parent;
delete tmp;
}
// Package name
apk->package = root->GetAttr("", "package");
if (apk->package.size() == 0) {
print_error("%s:%d: Manifest root element doesn't contain a package attribute",
filename.c_str(), root->lineno);
delete root;
return 1;
}
// Instrumentation runner
vector<Element*> instrumentation;
root->FindElements("", "instrumentation", &instrumentation, true);
if (instrumentation.size() > 0) {
// TODO: How could we deal with multiple instrumentation tags?
// We'll just pick the first one.
apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name");
}
// Activities
vector<Element*> activities;
root->FindElements("", "activity", &activities, true);
for (size_t i=0; i<activities.size(); i++) {
string name = activities[i]->GetAttr(ANDROID_NS, "name");
if (name.size() == 0) {
continue;
}
apk->activities.push_back(full_class_name(apk->package, name));
}
delete root;
return 0;
}