ruby_android 0.0.2 → 0.7.2

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.
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