android_parser 2.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +54 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +92 -0
- data/LICENSE.txt +22 -0
- data/README.md +158 -0
- data/Rakefile +44 -0
- data/android_parser.gemspec +64 -0
- data/lib/android/apk.rb +220 -0
- data/lib/android/axml_parser.rb +239 -0
- data/lib/android/axml_writer.rb +49 -0
- data/lib/android/dex/access_flag.rb +74 -0
- data/lib/android/dex/dex_object.rb +475 -0
- data/lib/android/dex/info.rb +151 -0
- data/lib/android/dex/utils.rb +45 -0
- data/lib/android/dex.rb +92 -0
- data/lib/android/layout.rb +44 -0
- data/lib/android/manifest.rb +350 -0
- data/lib/android/resource.rb +621 -0
- data/lib/android/utils.rb +55 -0
- data/lib/ruby_apk.rb +8 -0
- metadata +193 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
require_relative 'dex_object'
|
2
|
+
|
3
|
+
module Android
|
4
|
+
class Dex
|
5
|
+
# class information in dex
|
6
|
+
# @!attribute [r] name
|
7
|
+
# @return [String] class name
|
8
|
+
# @!attribute [r] super_class
|
9
|
+
# @return [String] super class name
|
10
|
+
class ClassInfo
|
11
|
+
# no index flag
|
12
|
+
NO_INDEX = 0xffffffff
|
13
|
+
|
14
|
+
# @return [ClassAccessFlag]
|
15
|
+
attr_reader :access_flags
|
16
|
+
# @return [Array<FieldInfo>] static fields
|
17
|
+
attr_reader :static_fields
|
18
|
+
# @return [Array<FieldInfo>] instance fields
|
19
|
+
attr_reader :instance_fields
|
20
|
+
# @return [Array<MethodInfo>] direct methods
|
21
|
+
attr_reader :direct_methods
|
22
|
+
# @return [Array<MethodInfo>] virtual methods
|
23
|
+
attr_reader :virtual_methods
|
24
|
+
|
25
|
+
# @return [DexObject::ClassDataItem]
|
26
|
+
attr_reader :class_data
|
27
|
+
# @return [DexObject::ClassDefItem]
|
28
|
+
attr_reader :class_def
|
29
|
+
|
30
|
+
def name
|
31
|
+
@dex.type_resolve(@class_def[:class_idx])
|
32
|
+
end
|
33
|
+
def super_class
|
34
|
+
if @class_def[:superclass_idx] != NO_INDEX
|
35
|
+
@super_class = @dex.type_resolve(@class_def[:superclass_idx])
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# @param [Dex::ClassDefItem] class_def
|
41
|
+
# @param [Dex] dex dex class instance
|
42
|
+
def initialize(class_def, dex)
|
43
|
+
@class_def = class_def
|
44
|
+
@dex = dex
|
45
|
+
@access_flags = ClassAccessFlag.new(@class_def[:access_flags])
|
46
|
+
@class_data = @class_def.class_data_item
|
47
|
+
@static_fields = @instance_fields = @direct_methods = @virtual_methods = []
|
48
|
+
unless @class_data.nil?
|
49
|
+
@static_fields = cls2info(@class_data[:static_fields], FieldInfo, :field_idx_diff)
|
50
|
+
@instance_fields = cls2info(@class_data[:instance_fields], FieldInfo, :field_idx_diff)
|
51
|
+
@direct_methods = cls2info(@class_data[:direct_methods], MethodInfo, :method_idx_diff)
|
52
|
+
@virtual_methods = cls2info(@class_data[:virtual_methods], MethodInfo, :method_idx_diff)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String] class difinition
|
57
|
+
def definition
|
58
|
+
ret = "#{access_flags} class #{name}"
|
59
|
+
super_class.nil? ? ret : ret + " extends #{super_class}"
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def cls2info(arr, cls, idx_key)
|
64
|
+
idx = 0
|
65
|
+
ret = []
|
66
|
+
arr.each do |item|
|
67
|
+
idx += item[idx_key]
|
68
|
+
ret << cls.new(item, idx, @dex)
|
69
|
+
end
|
70
|
+
ret
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# field info object
|
75
|
+
# @!attribute [r] name
|
76
|
+
# @return [String] field name
|
77
|
+
# @!attribute [r] type
|
78
|
+
# @return [String] field type
|
79
|
+
class FieldInfo
|
80
|
+
# @return [ClassAccessFlag]
|
81
|
+
attr_reader :access_flags
|
82
|
+
|
83
|
+
def name
|
84
|
+
@dex.strings[@dex.field_ids[@field_id][:name_idx]]
|
85
|
+
end
|
86
|
+
def type
|
87
|
+
@dex.type_resolve(@dex.field_ids[@field_id][:type_idx])
|
88
|
+
end
|
89
|
+
def initialize(encoded_field, field_id, dex)
|
90
|
+
@dex = dex
|
91
|
+
@encoded_field = encoded_field
|
92
|
+
@field_id = field_id
|
93
|
+
@access_flags = ClassAccessFlag.new(encoded_field[:access_flags])
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [String] field definition
|
97
|
+
def definition
|
98
|
+
"#{@access_flags.to_s} #{type} #{name}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# method info object
|
103
|
+
# @!attribute [r] name
|
104
|
+
# @return [String] method name
|
105
|
+
# @!attribute [r] ret_type
|
106
|
+
# @return [String] return type of the method
|
107
|
+
# @!attribute [r] parameters
|
108
|
+
# @return [Array<String>] method parameters
|
109
|
+
class MethodInfo
|
110
|
+
# @return [MethodAccessFlag]
|
111
|
+
attr_reader :access_flags
|
112
|
+
|
113
|
+
def initialize(encoded_method, method_id, dex)
|
114
|
+
@encoded_method = encoded_method
|
115
|
+
@method_id = method_id
|
116
|
+
@dex = dex
|
117
|
+
@access_flags = MethodAccessFlag.new(encoded_method[:access_flags])
|
118
|
+
end
|
119
|
+
def name
|
120
|
+
@dex.strings[@dex.method_ids[@method_id][:name_idx]]
|
121
|
+
end
|
122
|
+
def ret_type
|
123
|
+
@dex.type_resolve(proto[:return_type_idx])
|
124
|
+
end
|
125
|
+
def parameters
|
126
|
+
unless proto[:parameters_off] == 0
|
127
|
+
list = DexObject::TypeList.new(@dex.data, proto[:parameters_off])
|
128
|
+
list[:list].map { |item| @dex.type_resolve(item) }
|
129
|
+
else
|
130
|
+
[]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [String] method definition string
|
135
|
+
def definition
|
136
|
+
"#{access_flags.to_s} #{ret_type} #{name}(#{parameters.join(', ')});"
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [DexObject::CodeItem]
|
140
|
+
def code_item
|
141
|
+
@encoded_method.code_item
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
def proto
|
146
|
+
@dex.proto_ids[@dex.method_ids[@method_id][:proto_idx]]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Android
|
2
|
+
class Dex
|
3
|
+
class << self
|
4
|
+
# parse uleb128(unsigned integer) data
|
5
|
+
# @param [String] data target byte data
|
6
|
+
# @param [Integer] offset
|
7
|
+
# @return [Integer, Integer] parsed value and parsed byte length
|
8
|
+
# @see http://en.wikipedia.org/wiki/LEB128
|
9
|
+
def uleb128(data, offset=0)
|
10
|
+
result = 0
|
11
|
+
shift = 0
|
12
|
+
d = data[offset...data.size]
|
13
|
+
(0..4).each do |i|
|
14
|
+
byte = d.getbyte(i)
|
15
|
+
result |= ((byte & 0x7f) << shift)
|
16
|
+
return result, i+1 if ((byte & 0x80) == 0)
|
17
|
+
shift += 7
|
18
|
+
end
|
19
|
+
end
|
20
|
+
# parse uleb128 + 1 data
|
21
|
+
# @param [String] data target byte data
|
22
|
+
# @param [Integer] offset
|
23
|
+
# @return [Integer, Integer] parsed value and parsed byte length
|
24
|
+
def uleb128p1(data, offset=0)
|
25
|
+
ret, len = self.uleb128(data, offset)
|
26
|
+
return (ret - 1), len
|
27
|
+
end
|
28
|
+
# parse sleb128(signed integer) data
|
29
|
+
# @param [String] data target byte data
|
30
|
+
# @param [Integer] offset
|
31
|
+
# @return [Integer, Integer] parsed value and parsed byte length
|
32
|
+
def sleb128(data, offset=0)
|
33
|
+
result = 0
|
34
|
+
shift = 0
|
35
|
+
d = data[offset...data.size]
|
36
|
+
(0..4).each do |i|
|
37
|
+
byte = d.getbyte(i)
|
38
|
+
result |=((byte & 0x7F) << shift)
|
39
|
+
return (0 == (byte & 0x40) ? result : result - (1 << (shift+7))), i+1 if ((byte & 0x80) == 0)
|
40
|
+
shift += 7
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/android/dex.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative 'dex/dex_object'
|
2
|
+
require_relative 'dex/info'
|
3
|
+
require_relative 'dex/access_flag'
|
4
|
+
require_relative 'dex/utils'
|
5
|
+
|
6
|
+
module Android
|
7
|
+
# parsed dex object
|
8
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
9
|
+
# @attr_reader strings [Array<String>] strings in dex file.
|
10
|
+
class Dex
|
11
|
+
# @return [Dex::Header] dex header information
|
12
|
+
attr_reader :header
|
13
|
+
alias :h :header
|
14
|
+
|
15
|
+
# @return [String] dex binary data
|
16
|
+
attr_reader :data
|
17
|
+
# @return [Array<Dex::ClassInfo>] array of class information
|
18
|
+
attr_reader :classes
|
19
|
+
|
20
|
+
attr_reader :field_ids, :method_ids, :proto_ids
|
21
|
+
# @param [String] data dex binary data
|
22
|
+
def initialize(data)
|
23
|
+
@data = data
|
24
|
+
@data.force_encoding(Encoding::ASCII_8BIT)
|
25
|
+
@classes = []
|
26
|
+
parse()
|
27
|
+
end
|
28
|
+
|
29
|
+
def strings
|
30
|
+
@strings ||= @string_data_items.map{|item| item.to_s }
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"<Android::Dex @classes => #{@classes.size}, datasize => #{@data.size}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# @private
|
39
|
+
TYPE_DESCRIPTOR = {
|
40
|
+
'V' => 'void',
|
41
|
+
'Z' => 'boolean',
|
42
|
+
'B' => 'byte',
|
43
|
+
'S' => 'short',
|
44
|
+
'C' => 'short',
|
45
|
+
'I' => 'int',
|
46
|
+
'J' => 'long',
|
47
|
+
'F' => 'float',
|
48
|
+
'D' => 'double'
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
def type_resolve(typeid)
|
53
|
+
type = strings[@type_ids[typeid]]
|
54
|
+
if type.start_with? '['
|
55
|
+
type = type[1..type.size]
|
56
|
+
return TYPE_DESCRIPTOR.fetch(type, type) + "[]" # TODO: recursive
|
57
|
+
else
|
58
|
+
return TYPE_DESCRIPTOR.fetch(type, type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
private
|
64
|
+
def parse
|
65
|
+
@header = DexObject::Header.new(@data)
|
66
|
+
@map_list = DexObject::MapList.new(@data, h[:map_off])
|
67
|
+
|
68
|
+
# parse strings
|
69
|
+
@string_ids = DexObject::StringIdItem.new(@data, h[:string_ids_off], h[:string_ids_size])
|
70
|
+
@string_data_items = []
|
71
|
+
@string_ids[:string_data_off].each { |off| @string_data_items << DexObject::StringDataItem.new(@data, off) }
|
72
|
+
|
73
|
+
@type_ids = DexObject::TypeIdItem.new(@data, h[:type_ids_off], h[:type_ids_size])
|
74
|
+
@proto_ids = ids_list_array(DexObject::ProtoIdItem, h[:proto_ids_off], h[:proto_ids_size])
|
75
|
+
@field_ids = ids_list_array(DexObject::FieldIdItem, h[:field_ids_off], h[:field_ids_size])
|
76
|
+
@method_ids = ids_list_array(DexObject::MethodIdItem, h[:method_ids_off], h[:method_ids_size])
|
77
|
+
@class_defs = ids_list_array(DexObject::ClassDefItem, h[:class_defs_off], h[:class_defs_size])
|
78
|
+
|
79
|
+
@classes = []
|
80
|
+
@class_defs.each do |cls_def|
|
81
|
+
@classes << ClassInfo.new(cls_def, self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def ids_list_array(cls, offset, size)
|
86
|
+
ret_array = []
|
87
|
+
size.times { |i| ret_array << cls.new(@data, offset + cls.size * i) }
|
88
|
+
ret_array
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'ruby_apk'
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
module Android
|
6
|
+
class Layout
|
7
|
+
# @return [Hash] { path => Layout }
|
8
|
+
def self.collect_layouts(apk)
|
9
|
+
targets = apk.find {|name, data| name =~ /^res\/layout\/*/ }
|
10
|
+
ret = {}
|
11
|
+
targets.each do |path|
|
12
|
+
data = apk.file(path)
|
13
|
+
data.force_encoding(Encoding::ASCII_8BIT)
|
14
|
+
ret[path] = nil
|
15
|
+
begin
|
16
|
+
ret[path] = Layout.new(data, path) if AXMLParser.axml?(data)
|
17
|
+
rescue => e
|
18
|
+
$stderr.puts e
|
19
|
+
end
|
20
|
+
end
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] layout file path
|
25
|
+
attr_reader :path
|
26
|
+
# @return [REXML::Document] xml document object
|
27
|
+
attr_reader :doc
|
28
|
+
|
29
|
+
def initialize(data, path=nil)
|
30
|
+
@data = data
|
31
|
+
@path = path
|
32
|
+
@doc = AXMLParser.new(data).parse
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String] xml string
|
36
|
+
def to_xml(indent=4)
|
37
|
+
xml = ''
|
38
|
+
formatter = REXML::Formatters::Pretty.new(indent)
|
39
|
+
formatter.write(@doc.root, xml)
|
40
|
+
xml
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,350 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module Android
|
4
|
+
# parsed AndroidManifest.xml class
|
5
|
+
# @see http://developer.android.com/guide/topics/manifest/manifest-intro.html
|
6
|
+
class Manifest
|
7
|
+
APPLICATION_TAG = '/manifest/application'
|
8
|
+
|
9
|
+
# <activity>, <service>, <receiver> or <provider> element in <application> element of the manifest file.
|
10
|
+
class Component
|
11
|
+
# component types
|
12
|
+
TYPES = ['activity', 'activity-alias', 'service', 'receiver', 'provider', 'application']
|
13
|
+
|
14
|
+
# the element is valid Component element or not
|
15
|
+
# @param [REXML::Element] elem xml element
|
16
|
+
# @return [Boolean]
|
17
|
+
def self.valid?(elem)
|
18
|
+
TYPES.include?(elem.name.downcase)
|
19
|
+
rescue => e
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] type string in TYPES
|
24
|
+
attr_reader :type
|
25
|
+
# @return [String] component name
|
26
|
+
attr_reader :name
|
27
|
+
# @return [String] icon id - use apk.icon_by_id(icon_id) to retrieve it's corresponding data.
|
28
|
+
attr_reader :icon_id
|
29
|
+
# @return [Array<Manifest::IntentFilters<Manifest::IntentFilter>>]
|
30
|
+
attr_reader :intent_filters
|
31
|
+
# @return [Array<Manifest::Meta>]
|
32
|
+
attr_reader :metas
|
33
|
+
# @return [REXML::Element]
|
34
|
+
attr_reader :elem
|
35
|
+
|
36
|
+
|
37
|
+
# @param [REXML::Element] elem target element
|
38
|
+
# @raise [ArgumentError] when elem is invalid.
|
39
|
+
def initialize(elem)
|
40
|
+
raise ArgumentError unless Component.valid?(elem)
|
41
|
+
@elem = elem
|
42
|
+
@type = elem.name
|
43
|
+
@name = elem.attributes['name']
|
44
|
+
@icon_id = elem.attributes['icon']
|
45
|
+
|
46
|
+
@intent_filters = []
|
47
|
+
unless elem.elements['intent-filter'].nil?
|
48
|
+
elem.each_element('intent-filter') do |filters|
|
49
|
+
intent_filters = []
|
50
|
+
filters.elements.each do |filter|
|
51
|
+
next unless IntentFilter.valid?(filter)
|
52
|
+
|
53
|
+
intent_filters << IntentFilter.parse(filter)
|
54
|
+
end
|
55
|
+
@intent_filters << intent_filters
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@metas = []
|
60
|
+
elem.each_element('meta-data') do |e|
|
61
|
+
@metas << Meta.new(e)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Activity < Component
|
67
|
+
# the element is valid Activity element or not
|
68
|
+
# @param [REXML::Element] elem xml element
|
69
|
+
# @return [Boolean]
|
70
|
+
def self.valid?(elem)
|
71
|
+
['activity', 'activity-alias'].include?(elem.name.downcase)
|
72
|
+
rescue => e
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
def launcher_activity?
|
77
|
+
intent_filters.flatten.any? do |filter|
|
78
|
+
filter.type == 'category' && filter.name == 'android.intent.category.LAUNCHER'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return whether this instance is the default main launcher activity.
|
83
|
+
def default_launcher_activity?
|
84
|
+
intent_filters.any? do |intent_filter|
|
85
|
+
intent_filter.any? { |f| f.type == 'category' && f.name == 'android.intent.category.LAUNCHER' } &&
|
86
|
+
intent_filter.any? { |f| f.type == 'category' && f.name == 'android.intent.category.DEFAULT' }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class ActivityAlias < Activity
|
92
|
+
# @return [String] target activity name
|
93
|
+
attr_reader :target_activity
|
94
|
+
|
95
|
+
# @param [REXML::Element] elem target element
|
96
|
+
# @raise [ArgumentError] when elem is invalid.
|
97
|
+
def initialize(elem)
|
98
|
+
super
|
99
|
+
@target_activity = elem.attributes['targetActivity']
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Service < Component
|
104
|
+
end
|
105
|
+
|
106
|
+
class Receiver < Component
|
107
|
+
end
|
108
|
+
|
109
|
+
class Provider < Component
|
110
|
+
end
|
111
|
+
|
112
|
+
class Application < Component
|
113
|
+
def self.valid?(elem)
|
114
|
+
elem&.name == 'application'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# intent-filter element in components
|
119
|
+
module IntentFilter
|
120
|
+
# filter types
|
121
|
+
TYPES = ['action', 'category', 'data']
|
122
|
+
|
123
|
+
# the element is valid IntentFilter element or not
|
124
|
+
# @param [REXML::Element] elem xml element
|
125
|
+
# @return [Boolean]
|
126
|
+
def self.valid?(elem)
|
127
|
+
TYPES.include?(elem.name.downcase)
|
128
|
+
rescue => e
|
129
|
+
false
|
130
|
+
end
|
131
|
+
|
132
|
+
# parse inside of intent-filter element
|
133
|
+
# @param [REXML::Element] elem target element
|
134
|
+
# @return [IntentFilter::Action, IntentFilter::Category, IntentFilter::Data]
|
135
|
+
# intent-filter element
|
136
|
+
def self.parse(elem)
|
137
|
+
case elem.name
|
138
|
+
when 'action'
|
139
|
+
Action.new(elem)
|
140
|
+
when 'category'
|
141
|
+
Category.new(elem)
|
142
|
+
when 'data'
|
143
|
+
Data.new(elem)
|
144
|
+
else
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# intent-filter action class
|
150
|
+
class Action
|
151
|
+
# @return [String] action name of intent-filter
|
152
|
+
attr_reader :name
|
153
|
+
# @return [String] action type of intent-filter
|
154
|
+
attr_reader :type
|
155
|
+
|
156
|
+
def initialize(elem)
|
157
|
+
@type = 'action'
|
158
|
+
@name = elem.attributes['name']
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# intent-filter category class
|
163
|
+
class Category
|
164
|
+
# @return [String] category name of intent-filter
|
165
|
+
attr_reader :name
|
166
|
+
# @return [String] category type of intent-filter
|
167
|
+
attr_reader :type
|
168
|
+
|
169
|
+
def initialize(elem)
|
170
|
+
@type = 'category'
|
171
|
+
@name = elem.attributes['name']
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# intent-filter data class
|
176
|
+
class Data
|
177
|
+
# @return [String]
|
178
|
+
attr_reader :type
|
179
|
+
# @return [String]
|
180
|
+
attr_reader :host
|
181
|
+
# @return [String]
|
182
|
+
attr_reader :mime_type
|
183
|
+
# @return [String]
|
184
|
+
attr_reader :path
|
185
|
+
# @return [String]
|
186
|
+
attr_reader :path_pattern
|
187
|
+
# @return [String]
|
188
|
+
attr_reader :path_prefix
|
189
|
+
# @return [String]
|
190
|
+
attr_reader :port
|
191
|
+
# @return [String]
|
192
|
+
attr_reader :scheme
|
193
|
+
|
194
|
+
def initialize(elem)
|
195
|
+
@type = 'data'
|
196
|
+
@host = elem.attributes['host']
|
197
|
+
@mime_type = elem.attributes['mimeType']
|
198
|
+
@path = elem.attributes['path']
|
199
|
+
@path_pattern = elem.attributes['pathPattern']
|
200
|
+
@path_prefix = elem.attributes['pathPrefix']
|
201
|
+
@port = elem.attributes['port']
|
202
|
+
@scheme = elem.attributes['scheme']
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# meta information class
|
208
|
+
class Meta
|
209
|
+
# @return [String]
|
210
|
+
attr_reader :name
|
211
|
+
# @return [String]
|
212
|
+
attr_reader :resource
|
213
|
+
# @return [String]
|
214
|
+
attr_reader :value
|
215
|
+
def initialize(elem)
|
216
|
+
@name = elem.attributes['name']
|
217
|
+
@resource = elem.attributes['resource']
|
218
|
+
@value = elem.attributes['value']
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
#################################
|
223
|
+
# Manifest class definitions
|
224
|
+
#################################
|
225
|
+
#
|
226
|
+
# @return [REXML::Document] manifest xml
|
227
|
+
attr_reader :doc
|
228
|
+
|
229
|
+
# @param [String] data binary data of AndroidManifest.xml
|
230
|
+
def initialize(data, rsc=nil)
|
231
|
+
parser = AXMLParser.new(data)
|
232
|
+
@doc = parser.parse
|
233
|
+
@rsc = rsc
|
234
|
+
end
|
235
|
+
|
236
|
+
# used permission array
|
237
|
+
# @return [Array<String>] permission names
|
238
|
+
# @note return empty array when the manifest includes no use-parmission element
|
239
|
+
def use_permissions
|
240
|
+
perms = []
|
241
|
+
@doc.each_element('/manifest/uses-permission') do |elem|
|
242
|
+
perms << elem.attributes['name']
|
243
|
+
end
|
244
|
+
perms.uniq
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns the manifest's application element or nil, if there isn't any.
|
248
|
+
# @return [Android::Manifest::Application] the manifest's application element
|
249
|
+
def application
|
250
|
+
element = @doc.elements['//application']
|
251
|
+
Application.new(element) if Application.valid?(element)
|
252
|
+
end
|
253
|
+
|
254
|
+
# @return [Array<Android::Manifest::Component>] all components in apk
|
255
|
+
# @note return empty array when the manifest include no components
|
256
|
+
def components
|
257
|
+
components = []
|
258
|
+
unless @doc.elements['/manifest/application'].nil?
|
259
|
+
@doc.elements['/manifest/application'].each do |elem|
|
260
|
+
components << Component.new(elem) if Component.valid?(elem)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
components
|
264
|
+
end
|
265
|
+
|
266
|
+
# @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities in the apk
|
267
|
+
# @note return empty array when the manifest include no activities
|
268
|
+
def activities
|
269
|
+
activities = []
|
270
|
+
unless @doc.elements['/manifest/application'].nil?
|
271
|
+
@doc.elements['/manifest/application'].each do |elem|
|
272
|
+
next unless Activity.valid?(elem)
|
273
|
+
|
274
|
+
activities << (elem.name == 'activity-alias' ? ActivityAlias.new(elem) : Activity.new(elem))
|
275
|
+
end
|
276
|
+
end
|
277
|
+
activities
|
278
|
+
end
|
279
|
+
|
280
|
+
# @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities that are launchers in the apk
|
281
|
+
# @note return empty array when the manifest include no activities
|
282
|
+
def launcher_activities
|
283
|
+
activities.select(&:launcher_activity?)
|
284
|
+
end
|
285
|
+
|
286
|
+
# application package name
|
287
|
+
# @return [String]
|
288
|
+
def package_name
|
289
|
+
@doc.root.attributes['package']
|
290
|
+
end
|
291
|
+
|
292
|
+
# application version code
|
293
|
+
# @return [Integer]
|
294
|
+
def version_code
|
295
|
+
@doc.root.attributes['versionCode'].to_i
|
296
|
+
end
|
297
|
+
|
298
|
+
# application version name
|
299
|
+
# @return [String]
|
300
|
+
def version_name(lang=nil)
|
301
|
+
vername = @doc.root.attributes['versionName']
|
302
|
+
unless @rsc.nil?
|
303
|
+
if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ vername
|
304
|
+
opts = {}
|
305
|
+
opts[:lang] = lang unless lang.nil?
|
306
|
+
vername = @rsc.find(vername, opts)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
vername
|
310
|
+
end
|
311
|
+
|
312
|
+
# @return [Integer] minSdkVersion in uses element
|
313
|
+
def min_sdk_ver
|
314
|
+
@doc.elements['/manifest/uses-sdk'].attributes['minSdkVersion'].to_i
|
315
|
+
end
|
316
|
+
|
317
|
+
# application label
|
318
|
+
# @param [String] lang language code like 'ja', 'cn', ...
|
319
|
+
# @return [String] application label string(if resouce is provided), or label resource id
|
320
|
+
# @return [nil] when label is not found
|
321
|
+
# @since 0.5.1
|
322
|
+
def label(lang=nil)
|
323
|
+
label = @doc.elements['/manifest/application'].attributes['label']
|
324
|
+
if label.nil?
|
325
|
+
# application element has no label attributes.
|
326
|
+
# so looking for activites that has label attribute.
|
327
|
+
activities = @doc.elements['/manifest/application'].find{|e| e.name == 'activity' && !e.attributes['label'].nil? }
|
328
|
+
label = activities.nil? ? nil : activities.first.attributes['label']
|
329
|
+
end
|
330
|
+
unless @rsc.nil?
|
331
|
+
if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ label
|
332
|
+
opts = {}
|
333
|
+
opts[:lang] = lang unless lang.nil?
|
334
|
+
label = @rsc.find(label, opts)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
label
|
338
|
+
end
|
339
|
+
|
340
|
+
# return xml as string format
|
341
|
+
# @param [Integer] indent size(bytes)
|
342
|
+
# @return [String] raw xml string
|
343
|
+
def to_xml(indent=4)
|
344
|
+
xml =''
|
345
|
+
formatter = REXML::Formatters::Pretty.new(indent)
|
346
|
+
formatter.write(@doc.root, xml)
|
347
|
+
xml
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|