ruby_android 0.0.2 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.idea/.name +1 -0
  3. data/.idea/.rakeTasks +7 -0
  4. data/.idea/encodings.xml +5 -0
  5. data/.idea/misc.xml +5 -0
  6. data/.idea/modules.xml +9 -0
  7. data/.idea/ruby_apk.iml +51 -0
  8. data/.idea/scopes/scope_settings.xml +5 -0
  9. data/.idea/vcs.xml +7 -0
  10. data/.idea/workspace.xml +508 -0
  11. data/.travis.yml +4 -0
  12. data/CHANGELOG.md +51 -0
  13. data/Gemfile +3 -3
  14. data/Gemfile.lock +73 -0
  15. data/LICENSE.txt +2 -2
  16. data/Rakefile +42 -1
  17. data/VERSION +1 -0
  18. data/lib/android/apk.rb +207 -0
  19. data/lib/android/axml_parser.rb +173 -0
  20. data/lib/android/dex/access_flag.rb +74 -0
  21. data/lib/android/dex/dex_object.rb +475 -0
  22. data/lib/android/dex/info.rb +151 -0
  23. data/lib/android/dex/utils.rb +45 -0
  24. data/lib/android/dex.rb +92 -0
  25. data/lib/android/layout.rb +44 -0
  26. data/lib/android/manifest.rb +249 -0
  27. data/lib/android/resource.rb +529 -0
  28. data/lib/android/utils.rb +55 -0
  29. data/lib/ruby_apk.rb +7 -0
  30. data/ruby_android.gemspec +103 -17
  31. data/spec/apk_spec.rb +301 -0
  32. data/spec/axml_parser_spec.rb +67 -0
  33. data/spec/data/sample.apk +0 -0
  34. data/spec/data/sample_AndroidManifest.xml +0 -0
  35. data/spec/data/sample_classes.dex +0 -0
  36. data/spec/data/sample_resources.arsc +0 -0
  37. data/spec/data/sample_resources_utf16.arsc +0 -0
  38. data/spec/data/str_resources.arsc +0 -0
  39. data/spec/dex/access_flag_spec.rb +42 -0
  40. data/spec/dex/dex_object_spec.rb +118 -0
  41. data/spec/dex/info_spec.rb +121 -0
  42. data/spec/dex/utils_spec.rb +56 -0
  43. data/spec/dex_spec.rb +59 -0
  44. data/spec/layout_spec.rb +27 -0
  45. data/spec/manifest_spec.rb +221 -0
  46. data/spec/resource_spec.rb +170 -0
  47. data/spec/ruby_apk_spec.rb +4 -0
  48. data/spec/spec_helper.rb +17 -0
  49. data/spec/utils_spec.rb +90 -0
  50. metadata +112 -27
  51. data/.gitignore +0 -14
  52. data/lib/ruby_android/version.rb +0 -3
  53. data/lib/ruby_android.rb +0 -7
@@ -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_android'
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,249 @@
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 = ['service', 'activity', 'receiver', 'provider']
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 [Array<Manifest::IntentFilter>]
28
+ attr_reader :intent_filters
29
+ # @return [Array<Manifest::Meta>]
30
+ attr_reader :metas
31
+ # @return [REXML::Element]
32
+ attr_reader :elem
33
+
34
+
35
+ # @param [REXML::Element] elem target element
36
+ # @raise [ArgumentError] when elem is invalid.
37
+ def initialize(elem)
38
+ raise ArgumentError unless Component.valid?(elem)
39
+ @elem = elem
40
+ @type = elem.name
41
+ @name = elem.attributes['name']
42
+ @intent_filters = []
43
+ unless elem.elements['intent-filter'].nil?
44
+ elem.elements['intent-filter'].each do |e|
45
+ next unless e.instance_of? REXML::Element
46
+ @intent_filters << IntentFilter.parse(e)
47
+ end
48
+ end
49
+ @metas = []
50
+ elem.each_element('meta-data') do |e|
51
+ @metas << Meta.new(e)
52
+ end
53
+ end
54
+ end
55
+
56
+ # intent-filter element in components
57
+ module IntentFilter
58
+ # parse inside of intent-filter element
59
+ # @param [REXML::Element] elem target element
60
+ # @return [IntentFilter::Action, IntentFilter::Category, IntentFilter::Data]
61
+ # intent-filter element
62
+ def self.parse(elem)
63
+ case elem.name
64
+ when 'action'
65
+ Action.new(elem)
66
+ when 'category'
67
+ Category.new(elem)
68
+ when 'data'
69
+ Data.new(elem)
70
+ else
71
+ nil
72
+ end
73
+ end
74
+
75
+ # intent-filter action class
76
+ class Action
77
+ # @return [String] action name of intent-filter
78
+ attr_reader :name
79
+ # @return [String] action type of intent-filter
80
+ attr_reader :type
81
+
82
+ def initialize(elem)
83
+ @type = 'action'
84
+ @name = elem.attributes['name']
85
+ end
86
+ end
87
+
88
+ # intent-filter category class
89
+ class Category
90
+ # @return [String] category name of intent-filter
91
+ attr_reader :name
92
+ # @return [String] category type of intent-filter
93
+ attr_reader :type
94
+
95
+ def initialize(elem)
96
+ @type = 'category'
97
+ @name = elem.attributes['name']
98
+ end
99
+ end
100
+
101
+ # intent-filter data class
102
+ class Data
103
+ # @return [String]
104
+ attr_reader :type
105
+ # @return [String]
106
+ attr_reader :host
107
+ # @return [String]
108
+ attr_reader :mime_type
109
+ # @return [String]
110
+ attr_reader :path
111
+ # @return [String]
112
+ attr_reader :path_pattern
113
+ # @return [String]
114
+ attr_reader :path_prefix
115
+ # @return [String]
116
+ attr_reader :port
117
+ # @return [String]
118
+ attr_reader :scheme
119
+
120
+ def initialize(elem)
121
+ @type = 'data'
122
+ @host = elem.attributes['host']
123
+ @mime_type = elem.attributes['mimeType']
124
+ @path = elem.attributes['path']
125
+ @path_pattern = elem.attributes['pathPattern']
126
+ @path_prefix = elem.attributes['pathPrefix']
127
+ @port = elem.attributes['port']
128
+ @scheme = elem.attributes['scheme']
129
+ end
130
+ end
131
+ end
132
+
133
+ # meta information class
134
+ class Meta
135
+ # @return [String]
136
+ attr_reader :name
137
+ # @return [String]
138
+ attr_reader :resource
139
+ # @return [String]
140
+ attr_reader :value
141
+ def initialize(elem)
142
+ @name = elem.attributes['name']
143
+ @resource = elem.attributes['resource']
144
+ @value = elem.attributes['value']
145
+ end
146
+ end
147
+
148
+ #################################
149
+ # Manifest class definitions
150
+ #################################
151
+ #
152
+ # @return [REXML::Document] manifest xml
153
+ attr_reader :doc
154
+
155
+ # @param [String] data binary data of AndroidManifest.xml
156
+ def initialize(data, rsc=nil)
157
+ parser = AXMLParser.new(data)
158
+ @doc = parser.parse
159
+ @rsc = rsc
160
+ end
161
+
162
+ # used permission array
163
+ # @return [Array<String>] permission names
164
+ # @note return empty array when the manifest includes no use-parmission element
165
+ def use_permissions
166
+ perms = []
167
+ @doc.each_element('/manifest/uses-permission') do |elem|
168
+ perms << elem.attributes['name']
169
+ end
170
+ perms.uniq
171
+ end
172
+
173
+ # @return [Array<Android::Manifest::Component>] all components in apk
174
+ # @note return empty array when the manifest include no components
175
+ def components
176
+ components = []
177
+ unless @doc.elements['/manifest/application'].nil?
178
+ @doc.elements['/manifest/application'].each do |elem|
179
+ components << Component.new(elem) if Component.valid?(elem)
180
+ end
181
+ end
182
+ components
183
+ end
184
+
185
+ # application package name
186
+ # @return [String]
187
+ def package_name
188
+ @doc.root.attributes['package']
189
+ end
190
+
191
+ # application version code
192
+ # @return [Integer]
193
+ def version_code
194
+ @doc.root.attributes['versionCode'].to_i
195
+ end
196
+
197
+ # application version name
198
+ # @return [String]
199
+ def version_name(lang=nil)
200
+ vername = @doc.root.attributes['versionName']
201
+ unless @rsc.nil?
202
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ vername
203
+ opts = {}
204
+ opts[:lang] = lang unless lang.nil?
205
+ vername = @rsc.find(vername, opts)
206
+ end
207
+ end
208
+ vername
209
+ end
210
+
211
+ # @return [Integer] minSdkVersion in uses element
212
+ def min_sdk_ver
213
+ @doc.elements['/manifest/uses-sdk'].attributes['minSdkVersion'].to_i
214
+ end
215
+
216
+ # application label
217
+ # @param [String] lang language code like 'ja', 'cn', ...
218
+ # @return [String] application label string(if resouce is provided), or label resource id
219
+ # @return [nil] when label is not found
220
+ # @since 0.5.1
221
+ def label(lang=nil)
222
+ label = @doc.elements['/manifest/application'].attributes['label']
223
+ if label.nil?
224
+ # application element has no label attributes.
225
+ # so looking for activites that has label attribute.
226
+ activities = @doc.elements['/manifest/application'].find{|e| e.name == 'activity' && !e.attributes['label'].nil? }
227
+ label = activities.nil? ? nil : activities.first.attributes['label']
228
+ end
229
+ unless @rsc.nil?
230
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ label
231
+ opts = {}
232
+ opts[:lang] = lang unless lang.nil?
233
+ label = @rsc.find(label, opts)
234
+ end
235
+ end
236
+ label
237
+ end
238
+
239
+ # return xml as string format
240
+ # @param [Integer] indent size(bytes)
241
+ # @return [String] raw xml string
242
+ def to_xml(indent=4)
243
+ xml =''
244
+ formatter = REXML::Formatters::Pretty.new(indent)
245
+ formatter.write(@doc.root, xml)
246
+ xml
247
+ end
248
+ end
249
+ end