android_parser 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|