1# Copyright (C) 2010 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15# A nawk/gawk script used to extract the list of launchable activities
16# from an application's manifest (i.e. AndroidManifest.xml). Usage:
17#
18#   awk -f <this-script> AndroidManifest.xml
19#
20
21#
22# Explanation:
23#
24# A given application can have several activities, and each activity
25# can have several intent filters. We want to only list, in the final
26# output, the activities which have a intent-filter that contains the
27# following elements:
28#
29#   <action android:name="android.intent.action.MAIN" />
30#   <category android:name="android.intent.category.LAUNCHER" />
31#
32# To do this, we need hooks called when entering and exiting <activity>
33# and <intent-filter> elements.
34#
35
36BEGIN {
37    while ( xml_event() ) {
38        # concat xml event type and tag for simpler comparisons
39        event = XML_TYPE "-" XML_TAG;
40        # When entering a new <activity>, extract its name and set
41        # the 'launchable' flag to false.
42        if ( event == "BEGIN-ACTIVITY" && 
43             XML_RPATH == "ACTIVITY/APPLICATION/MANIFEST/" ) {
44            name = XML_ATTR["android:name"];
45            launchable = 0;
46        }
47        # When exiting an <activity>, check that it has a name and
48        # is launchable. If so, print its name to the output
49        else if ( event == "END-ACTIVITY" &&
50                  XML_RPATH == "APPLICATION/MANIFEST/" ) {
51            if ( name && launchable ) {
52                # If the name doesn't contain any dot, we consider
53                # that it is just missing the initial one.
54                if (index(name, ".") == 0) {
55                    name = "." name
56                }
57                print name;
58            }
59        }
60        # When entering an <intent-filter> inside an <activity>, clear
61        # the 'action' and 'category' variables. They are updated when
62        # we enter the corresponding elements within the intent-filter.
63        else if ( event == "BEGIN-INTENT-FILTER" &&
64                 XML_RPATH == "INTENT-FILTER/ACTIVITY/APPLICATION/MANIFEST/" ) {
65            action_main = 0;
66            category_launcher = 0;
67        }
68        # When exiting an <intent-filter>, set the 'launchable' flag to true
69        # for the current activity if both 'action' and 'category' have the
70        # correct name.
71        else if ( event == "END-INTENT-FILTER" &&
72                  XML_RPATH == "ACTIVITY/APPLICATION/MANIFEST/" ) {
73            if ( category_launcher ) {
74                launchable = 1;
75            }
76        }
77        # When entering an <action> element inside an <intent-filter>, record
78        # its name.
79        else if ( event == "BEGIN-ACTION" &&
80                  XML_RPATH == "ACTION/INTENT-FILTER/ACTIVITY/APPLICATION/MANIFEST/" ) {
81            action_main = 0;
82            if ( XML_ATTR["android:name"] == "android.intent.action.MAIN" ) {
83                action_main = 1;
84            }
85        }
86        # When entering a <category> element inside an <intent-filter>, record
87        # its name.
88        else if ( event == "BEGIN-CATEGORY" &&
89                  XML_RPATH == "CATEGORY/INTENT-FILTER/ACTIVITY/APPLICATION/MANIFEST/" ) {
90            if ( action_main && XML_ATTR["android:name"] == "android.intent.category.LAUNCHER" ) {
91                category_launcher = 1;
92            }
93        }
94    }
95}
96
97
98#
99# the following is copied directly from xml.awk - see this file for
100# usage and implementation details.
101#
102function xml_event () {
103    RS=">";
104    XML_TAG=XML_TYPE="";
105    split("", XML_ATTR);
106    while ( 1 ) {
107        if (_xml_closing) { # delayed direct tag closure
108            XML_TAG = _xml_closing;
109            XML_TYPE = "END";
110            _xml_closing = "";
111            _xml_exit(XML_TAG);
112            return 1;
113        }
114        if (getline <= 0) return 0; # read new input line
115        _xml_p = index($0, "<"); # get start marker
116        if (_xml_p == 0) return 0; # end of file (or malformed input)
117        $0 = substr($0, _xml_p) # remove anything before '<'
118        # ignore CData / Comments / Processing instructions / Declarations
119        if (_xml_in_section("<!\\[[Cc][Dd][Aa][Tt][Aa]\\[", "]]") ||
120            _xml_in_section("<!--", "--") ||
121            _xml_in_section("<\\?", "\\?") ||
122            _xml_in_section("<!", "")) {
123            continue;
124        }
125        if (substr($0, 1, 2) == "</") { # is it a closing tag ?
126            XML_TYPE = "END";
127            $0 = substr($0, 3);
128        } else { # nope, it's an opening one
129            XML_TYPE = "BEGIN";
130            $0 = substr($0, 2);
131        }
132        XML_TAG = $0
133        sub("[ \n\t/].*$", "", XML_TAG);  # extract tag name
134        XML_TAG = toupper(XML_TAG);       # uppercase it
135        if ( XML_TAG !~ /^[A-Z][-+_.:0-9A-Z]*$/ )  # validate it
136            _xml_panic("Invalid tag name: " XML_TAG);
137        if (XML_TYPE == "BEGIN") {  # update reverse path
138            _xml_enter(XML_TAG);
139        } else {
140            _xml_exit(XML_TAG);
141        }
142        sub("[^ \n\t]*[ \n\t]*", "", $0); # get rid of tag and spaces
143        while ($0) { # process attributes
144            if ($0 == "/") {  # deal with direct closing tag, e.g. </foo>
145                _xml_closing = XML_TAG; # record delayed tag closure.
146                break
147            }
148            _xml_attrib = $0;
149            sub(/=.*$/,"",_xml_attrib);  # extract attribute name
150            sub(/^[^=]*/,"",$0);         # remove it from record
151            _xml_attrib = tolower(_xml_attrib);
152            if ( _xml_attrib !~ /^[a-z][-+_0-9a-z:]*$/ ) # validate it
153                _xml_panic("Invalid attribute name: " _xml_attrib);
154            if (substr($0,1,2) == "=\"") { # value is ="something"
155                _xml_value = substr($0,3);
156                sub(/".*$/,"",_xml_value);
157                sub(/^="[^"]*"/,"",$0);
158            } else if (substr($0,1,2) == "='") { # value is ='something'
159                _xml_value = substr($0,3);
160                sub(/'.*$/,"",_xml_value);
161                sub(/^='[^']*'/,"",$0);
162            } else {
163                _xml_panic("Invalid attribute value syntax for " _xml_attrib ": " $0);
164            }
165            XML_ATTR[_xml_attrib] = _xml_value;  # store attribute name/value
166            sub(/^[ \t\n]*/,"",$0); # get rid of remaining leading spaces
167        }
168        return 1; # now return, XML_TYPE/TAG/ATTR/RPATH are set
169    }
170}
171
172function _xml_panic (msg) {
173    print msg > "/dev/stderr"
174    exit(1)
175}
176
177function _xml_in_section (sec_begin, sec_end) {
178    if (!match( $0, "^" sec_begin )) return 0;
179    while (!match($0, sec_end "$")) {
180        if (getline <= 0) _xml_panic("Unexpected EOF: " ERRNO);
181    }
182    return 1;
183}
184
185function _xml_enter (tag) {
186    XML_RPATH = tag "/" XML_RPATH;
187}
188
189function _xml_exit (tag) {
190    _xml_p = index(XML_RPATH, "/");
191    _xml_expected = substr(XML_RPATH, 1, _xml_p-1);
192    if (_xml_expected != XML_TAG)
193        _xml_panic("Unexpected close tag: " XML_TAG ", expecting " _xml_expected);
194    XML_RPATH = substr(XML_RPATH, _xml_p+1);
195}
196