Merge lp:~terceiro/lava-dispatcher/nexus into lp:lava-dispatcher
- nexus
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 543 |
Proposed branch: | lp:~terceiro/lava-dispatcher/nexus |
Merge into: | lp:lava-dispatcher |
Diff against target: |
262 lines (+242/-0) 3 files modified
lava_dispatcher/config.py (+3/-0) lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf (+46/-0) lava_dispatcher/device/nexus.py (+193/-0) |
To merge this branch: | bzr merge lp:~terceiro/lava-dispatcher/nexus |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle (community) | Approve | ||
Review via email: mp+144204@code.launchpad.net |
This proposal supersedes a proposal from 2013-01-15.
Commit message
Description of the change
Hi guys,
I tried to address all your concerns and this is an improved version of the code.
The only issue I did not handle was Andy's point about exception handling in file_system. I didn't see the point in catching an eventual exception there just to raise a new exception. If anything in there blows up, the job will just be terminated.
Please let me know what you think.
Andy Doan (doanac) wrote : Posted in a previous version of this proposal | # |
Michael Hudson-Doyle (mwhudson) wrote : Posted in a previous version of this proposal | # |
Antonio Terceiro <email address hidden> writes:
> Antonio Terceiro has proposed merging lp:~terceiro/lava-dispatcher/nexus into lp:lava-dispatcher.
>
> Requested reviews:
> Linaro Validation Team (linaro-validation)
>
> For more details, see:
> https:/
>
> Hi,
>
> This time is for real. :-)
>
> I don't see any pending issues, so know you can have a hard review on the code.
> --
> https:/
> You are subscribed to branch lp:lava-dispatcher.
> === modified file 'lava_dispatche
> --- lava_dispatcher
> +++ lava_dispatcher
> @@ -91,6 +91,9 @@
> arm_probe_config = schema.
> arm_probe_channels = schema.
>
> + adb_command = schema.
> + fastboot_command = schema.
> + nexus_working_
It's super nitty, but "default=None" (i.e. without the spaces) would be
more conventional.
> class OptionDescripto
> def __init__(self, name):
>
> === added file 'lava_dispatche
> --- lava_dispatcher
> +++ lava_dispatcher
> @@ -0,0 +1,40 @@
> +client_type = nexus
> +
> +# The ADB command line.
> +#
> +# In the case where there are more than one android devices plugged into a
s/are/is/ or s/more than one/multiple/ (English grammar makes no sense)
> +# single host, this connection command must be overriden on each device to
> +# include the serial number of the device, e.g.
> +#
> +# serial_number = XXXXXXXXXXXXXXXX
> +# adb_command = adb -s %(serial_number)s
> +adb_command = adb
> +
> +# The fastboot command.
> +#
> +# The same as above: if you have more than one device, you will want to
> +# override this in your device config to add a serial number, e.g.
> +#
> +# serial_number = XXXXXXXXXXXXXXXX
> +# fastboot_command = sudo fastboot -s %(serial_number)s
> +#
> +# Of course, in the case you override both adb_command *and* fastboot_command,
> +# you don't need to specify `serial_number` twice.
> +fastboot_command = sudo fastboot
> +
> +# Working directory for temporary. By default, the usual place for LAVA images
> +# will be used.
> +#
> +# This is useful when the lava dispatcher is controlling Nexus phones that are
> +# physically plugged to other machines. In this case, you should set
> +# nexus_work_
> +# dispatcher and the machine where the phone is plugged.
I don't entirely understand when this would be useful.
> +# OBS: this shared directory must have the same path in both machines.
OBS?
> +nexus_
> +
> +connection_command = %(adb_command)s shell
> +
> +enable_
> +android_
> +android_adb_...
Antonio Terceiro (terceiro) wrote : Posted in a previous version of this proposal | # |
Hi,
On Tue, Jan 15, 2013 at 11:37:22PM -0000, Andy Doan wrote:
> On 01/15/2013 01:18 PM, Antonio Terceiro wrote:
> > + def power_on(self):
>
> Seems like you might want to add some check like:
>
> if not self.deployment
> raise CriticalError(
Done, thanks.
> > + self.reboot_os()
> > + self.reboot_
> > + self.boot_
> > +
> > + self._powered_on = True
> > + proc = self.adb('shell', spawn = True)
> > + proc.sendline("") # required to put the adb shell in a reasonable state
> > + proc.sendline(
> > + self._runner = self._get_
> > +
> > + return proc
>
> > + def power_off(self, proc):
> > + # there is no way to power off the Nexus while USB is plugged on; even
> > + # if you remove power, it will stay on.
> > + pass
> > +
> This might be correct, but I fear a bad image could put us in an
> irrecoverable state. One thing we might consider adding is some type of
> logic like I did in sdmux.sh:
>
>
> <http://
>
> This essentially powers down a USB port and might solve your problem.
> However, since you use ADB over USB be aware of this issue:
>
> https:/
Actually, I investigated a little more and found that:
- in android, just cutting the power works
- in fastboot, cutting the power sometimes works, sometimes not. But
if I tell fastboot to reboot after cutting the power, then it
actually goes off.
I will try that.
> Lastly, the filesystem function looks like it might need some exception
> handling logic. Not sure about this, but I found the need when
> implementing that logic for other targets.
ok - I will look at it.
--
Antonio Terceiro
Software Engineer - Linaro
http://
Antonio Terceiro (terceiro) wrote : Posted in a previous version of this proposal | # |
On Wed, Jan 16, 2013 at 12:16:20AM -0000, Michael Hudson-Doyle wrote:
> Antonio Terceiro <email address hidden> writes:
>
> > Antonio Terceiro has proposed merging lp:~terceiro/lava-dispatcher/nexus into lp:lava-dispatcher.
> >
> > Requested reviews:
> > Linaro Validation Team (linaro-validation)
> >
> > For more details, see:
> > https:/
> >
> > Hi,
> >
> > This time is for real. :-)
> >
> > I don't see any pending issues, so know you can have a hard review on the code.
> > --
> > https:/
> > You are subscribed to branch lp:lava-dispatcher.
> > === modified file 'lava_dispatche
> > --- lava_dispatcher
> > +++ lava_dispatcher
> > @@ -91,6 +91,9 @@
> > arm_probe_config = schema.
> > arm_probe_channels = schema.
> >
> > + adb_command = schema.
> > + fastboot_command = schema.
> > + nexus_working_
>
> It's super nitty, but "default=None" (i.e. without the spaces) would be
> more conventional.
Fixed.
> > class OptionDescripto
> > def __init__(self, name):
> >
> > === added file 'lava_dispatche
> > --- lava_dispatcher
> > +++ lava_dispatcher
> > @@ -0,0 +1,40 @@
> > +client_type = nexus
> > +
> > +# The ADB command line.
> > +#
> > +# In the case where there are more than one android devices plugged into a
>
> s/are/is/ or s/more than one/multiple/ (English grammar makes no sense)
Fixed
> > +# single host, this connection command must be overriden on each device to
> > +# include the serial number of the device, e.g.
> > +#
> > +# serial_number = XXXXXXXXXXXXXXXX
> > +# adb_command = adb -s %(serial_number)s
> > +adb_command = adb
> > +
> > +# The fastboot command.
> > +#
> > +# The same as above: if you have more than one device, you will want to
> > +# override this in your device config to add a serial number, e.g.
> > +#
> > +# serial_number = XXXXXXXXXXXXXXXX
> > +# fastboot_command = sudo fastboot -s %(serial_number)s
> > +#
> > +# Of course, in the case you override both adb_command *and* fastboot_command,
> > +# you don't need to specify `serial_number` twice.
> > +fastboot_command = sudo fastboot
> > +
> > +# Working directory for temporary. By default, the usual place for LAVA images
> > +# will be used.
> > +#
> > +# This is useful when the lava dispatcher is controlling Nexus phones that are
> > +# physically plugged to other machines. In this case, you should set
> > +# nexus_work_
> > +# dispatcher and the machine where the phone is plugged.
>
> I don't entirely understand when this would be useful.
I will try to explain it...
Michael Hudson-Doyle (mwhudson) wrote : Posted in a previous version of this proposal | # |
Antonio Terceiro <email address hidden> writes:
> On Wed, Jan 16, 2013 at 12:16:20AM -0000, Michael Hudson-Doyle wrote:
>> Antonio Terceiro <email address hidden> writes:
>> > +# Working directory for temporary. By default, the usual place for LAVA images
>> > +# will be used.
>> > +#
>> > +# This is useful when the lava dispatcher is controlling Nexus phones that are
>> > +# physically plugged to other machines. In this case, you should set
>> > +# nexus_work_
>> > +# dispatcher and the machine where the phone is plugged.
>>
>> I don't entirely understand when this would be useful.
>
> I will try to explain it better. :)
Did you do this? I'm guessing you use it when LAVA is running in a VM?
>> > +# OBS: this shared directory must have the same path in both machines.
>>
>> OBS?
>
> heh, that's the equivalent of "N.B." in Portuguese, I guess it doesn't
> exist in English. :) I will rewrite that doc, so for now I just removed
> the OBS.
Heh.
>> > +nexus_
>> > +
>> > +connection_command = %(adb_command)s shell
>> > +
>> > +enable_
>> > +android_
>> > +android_
>> >
>> > === added file 'lava_dispatche
>> > --- lava_dispatcher
>> > +++ lava_dispatcher
>> > @@ -0,0 +1,168 @@
>> > +# Copyright (C) 2012 Linaro Limited
>> > +#
>> > +# Author: Antonio Terceiro <email address hidden>
>> > +#
>> > +# This file is part of LAVA Dispatcher.
>> > +#
>> > +# LAVA Dispatcher is free software; you can redistribute it and/or modify
>> > +# it under the terms of the GNU General Public License as published by
>> > +# the Free Software Foundation; either version 2 of the License, or
>> > +# (at your option) any later version.
>> > +#
>> > +# LAVA Dispatcher is distributed in the hope that it will be useful,
>> > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
>> > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> > +# GNU General Public License for more details.
>> > +#
>> > +# You should have received a copy of the GNU General Public License
>> > +# along
>> > +# with this program; if not, see <http://
>> > +
>> > +import subprocess
>> > +import pexpect
>> > +from time import sleep
>> > +import logging
>> > +import contextlib
>> > +
>> > +from lava_dispatcher
>> > + Target
>> > +)
>> > +from lava_dispatcher
>> > + download_image
>> > +)
>> > +from lava_dispatcher
>> > + logging_spawn,
>> > + mkdtemp
>> > +)
>>
>> OK, so this is probably mostly the dispatcher's fault for being
>> confusing, but there are a few things I don't really understand here.
>> If I get you to explain them to me in excruciating detail, maybe we can
>> make the code clearer for the next person.
>>
>> > +class NexusTarget(
>> > +
>> > + def __init__(self, context, config):
>> > + super(NexusTarget, self)._
>> > + self._powered_on = Fal...
Antonio Terceiro (terceiro) wrote : Posted in a previous version of this proposal | # |
On Mon, Jan 21, 2013 at 11:19:21PM -0000, Michael Hudson-Doyle wrote:
> Antonio Terceiro <email address hidden> writes:
>
> > On Wed, Jan 16, 2013 at 12:16:20AM -0000, Michael Hudson-Doyle wrote:
> >> Antonio Terceiro <email address hidden> writes:
> >> > +# Working directory for temporary. By default, the usual place for LAVA images
> >> > +# will be used.
> >> > +#
> >> > +# This is useful when the lava dispatcher is controlling Nexus phones that are
> >> > +# physically plugged to other machines. In this case, you should set
> >> > +# nexus_work_
> >> > +# dispatcher and the machine where the phone is plugged.
> >>
> >> I don't entirely understand when this would be useful.
> >
> > I will try to explain it better. :)
>
> Did you do this? I'm guessing you use it when LAVA is running in a VM?
Yes! :-)
It works for lava-test-shell, but not for lava-android-test, because the
later syncs files from its installed code base directly into the device
with adb, without using the file_system API call.
--
Antonio Terceiro
Software Engineer - Linaro
http://
Michael Hudson-Doyle (mwhudson) : | # |
Preview Diff
1 | === modified file 'lava_dispatcher/config.py' |
2 | --- lava_dispatcher/config.py 2013-01-18 11:08:24 +0000 |
3 | +++ lava_dispatcher/config.py 2013-01-22 02:17:24 +0000 |
4 | @@ -92,6 +92,9 @@ |
5 | arm_probe_config = schema.StringOption(default='/usr/local/etc/arm-probe-config') |
6 | arm_probe_channels = schema.ListOption(default=['VDD_VCORE1']) |
7 | |
8 | + adb_command = schema.StringOption() |
9 | + fastboot_command = schema.StringOption() |
10 | + nexus_working_directory = schema.StringOption(default=None) |
11 | |
12 | class OptionDescriptor(object): |
13 | def __init__(self, name): |
14 | |
15 | === added file 'lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf' |
16 | --- lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf 1970-01-01 00:00:00 +0000 |
17 | +++ lava_dispatcher/default-config/lava-dispatcher/device-types/nexus.conf 2013-01-22 02:17:24 +0000 |
18 | @@ -0,0 +1,46 @@ |
19 | +client_type = nexus |
20 | + |
21 | +# The ADB command line. |
22 | +# |
23 | +# In the case where there are multiple android devices plugged into a |
24 | +# single host, this connection command must be overriden on each device to |
25 | +# include the serial number of the device, e.g. |
26 | +# |
27 | +# serial_number = XXXXXXXXXXXXXXXX |
28 | +# adb_command = adb -s %(serial_number)s |
29 | +adb_command = adb |
30 | + |
31 | +# The fastboot command. |
32 | +# |
33 | +# The same as above: if you have more than one device, you will want to |
34 | +# override this in your device config to add a serial number, e.g. |
35 | +# |
36 | +# serial_number = XXXXXXXXXXXXXXXX |
37 | +# fastboot_command = fastboot -s %(serial_number)s |
38 | +# |
39 | +# Of course, in the case you override both adb_command *and* fastboot_command, |
40 | +# you don't need to specify `serial_number` twice. |
41 | +fastboot_command = fastboot |
42 | + |
43 | +# Working directory for temporary files. By default, the usual place for LAVA |
44 | +# images will be used. |
45 | +# |
46 | +# This is useful when the lava dispatcher is controlling Nexus phones that are |
47 | +# physically plugged to other machines by setting adb_command to something like |
48 | +# "ssh <phone-host> adb" and fastboot_command to something like "ssh |
49 | +# <phone-host> fastboot". adb and fastboot always operate on local files, so |
50 | +# you need your local files to also be seen as local files on the host where |
51 | +# adb/fastboot are executed. |
52 | +# |
53 | +# In this case, you should set nexus_work_directory to a shared directory |
54 | +# between the machine running the dispatcher and the machine where the phone is |
55 | +# plugged. This shared directory must have the same path in both machines. |
56 | +# For example, you can have your /var/tmp/lava mounted at /var/tmp/lava at |
57 | +# <phone-host> (or the other way around). |
58 | +nexus_working_directory = |
59 | + |
60 | +connection_command = %(adb_command)s shell |
61 | + |
62 | +enable_network_after_boot_android = false |
63 | +android_adb_over_usb = true |
64 | +android_adb_over_tcp = false |
65 | |
66 | === added file 'lava_dispatcher/device/nexus.py' |
67 | --- lava_dispatcher/device/nexus.py 1970-01-01 00:00:00 +0000 |
68 | +++ lava_dispatcher/device/nexus.py 2013-01-22 02:17:24 +0000 |
69 | @@ -0,0 +1,193 @@ |
70 | +# Copyright (C) 2012 Linaro Limited |
71 | +# |
72 | +# Author: Antonio Terceiro <antonio.terceiro@linaro.org> |
73 | +# |
74 | +# This file is part of LAVA Dispatcher. |
75 | +# |
76 | +# LAVA Dispatcher is free software; you can redistribute it and/or modify |
77 | +# it under the terms of the GNU General Public License as published by |
78 | +# the Free Software Foundation; either version 2 of the License, or |
79 | +# (at your option) any later version. |
80 | +# |
81 | +# LAVA Dispatcher is distributed in the hope that it will be useful, |
82 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
83 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
84 | +# GNU General Public License for more details. |
85 | +# |
86 | +# You should have received a copy of the GNU General Public License |
87 | +# along |
88 | +# with this program; if not, see <http://www.gnu.org/licenses>. |
89 | + |
90 | +import subprocess |
91 | +import pexpect |
92 | +from time import sleep |
93 | +import logging |
94 | +import contextlib |
95 | + |
96 | +from lava_dispatcher.device.target import ( |
97 | + Target |
98 | +) |
99 | +from lava_dispatcher.downloader import ( |
100 | + download_image |
101 | +) |
102 | +from lava_dispatcher.utils import ( |
103 | + logging_system, |
104 | + logging_spawn, |
105 | + mkdtemp |
106 | +) |
107 | +from lava_dispatcher.errors import ( |
108 | + CriticalError |
109 | +) |
110 | + |
111 | +class NexusTarget(Target): |
112 | + |
113 | + def __init__(self, context, config): |
114 | + super(NexusTarget, self).__init__(context, config) |
115 | + |
116 | + if not config.hard_reset_command: |
117 | + logging.warn( |
118 | + "Setting the hard_reset_command config option " |
119 | + "is highly recommended!" |
120 | + ) |
121 | + |
122 | + self._booted = False |
123 | + self._working_dir = None |
124 | + |
125 | + def deploy_android(self, boot, system, userdata): |
126 | + |
127 | + boot = self._get_image(boot) |
128 | + system = self._get_image(system) |
129 | + userdata = self._get_image(userdata) |
130 | + |
131 | + self._enter_fastboot() |
132 | + self._fastboot('erase boot') |
133 | + self._fastboot('flash system %s' % system) |
134 | + self._fastboot('flash userdata %s' % userdata) |
135 | + |
136 | + self.deployment_data = Target.android_deployment_data |
137 | + self.deployment_data['boot_image'] = boot |
138 | + |
139 | + def power_on(self): |
140 | + if not self.deployment_data.get('boot_image', False): |
141 | + raise CriticalError('Deploy action must be run first') |
142 | + |
143 | + self._enter_fastboot() |
144 | + self._boot_test_image() |
145 | + |
146 | + self._booted = True |
147 | + proc = self._adb('shell', spawn = True) |
148 | + proc.sendline("") # required to put the adb shell in a reasonable state |
149 | + proc.sendline("export PS1='%s'" % self.deployment_data['TESTER_PS1']) |
150 | + self._runner = self._get_runner(proc) |
151 | + |
152 | + return proc |
153 | + |
154 | + def power_off(self, proc): |
155 | + # We always leave the device on |
156 | + pass |
157 | + |
158 | + @contextlib.contextmanager |
159 | + def file_system(self, partition, directory): |
160 | + |
161 | + if not self._booted: |
162 | + self.power_on() |
163 | + |
164 | + mount_point = self._get_partition_mount_point(partition) |
165 | + |
166 | + host_dir = '%s/mnt/%s' % (self.working_dir, directory) |
167 | + target_dir = '%s/%s' % (mount_point, directory) |
168 | + |
169 | + subprocess.check_call(['mkdir', '-p', host_dir]) |
170 | + self._adb('pull %s %s' % (target_dir, host_dir), ignore_failure = True) |
171 | + |
172 | + yield host_dir |
173 | + |
174 | + self._adb('push %s %s' % (host_dir, target_dir)) |
175 | + |
176 | + def get_device_version(self): |
177 | + # this is tricky, because fastboot does not have a visible version |
178 | + # number. For now let's use just the adb version number. |
179 | + return subprocess.check_output( |
180 | + "%s version | sed 's/.* version //'" % self.config.adb_command, |
181 | + shell = True |
182 | + ).strip() |
183 | + |
184 | + # start of private methods |
185 | + |
186 | + def _enter_fastboot(self): |
187 | + if self._already_on_fastboot(): |
188 | + logging.debug("Device is on fastboot - no need to hard reset") |
189 | + return |
190 | + try: |
191 | + # First we try a gentle reset |
192 | + self._adb('reboot') |
193 | + except subprocess.CalledProcessError: |
194 | + # Now a more brute force attempt. In this case the device is |
195 | + # probably hung. |
196 | + if self.config.hard_reset_command: |
197 | + logging.debug("Will hard reset the device") |
198 | + logging_system(self.config.hard_reset_command) |
199 | + else: |
200 | + logging.critical( |
201 | + "Hard reset command not configured. " |
202 | + "Please reset the device manually." |
203 | + ) |
204 | + |
205 | + def _already_on_fastboot(self): |
206 | + try: |
207 | + self._fastboot('getvar all', timeout = 2) |
208 | + return True |
209 | + except subprocess.CalledProcessError: |
210 | + return False |
211 | + |
212 | + def _boot_test_image(self): |
213 | + # We need an extra bootloader reboot before actually booting the image |
214 | + # to avoid the phone entering charging mode and getting stuck. |
215 | + self._fastboot('reboot') |
216 | + # specifically after `fastboot reset`, we have to wait a little |
217 | + sleep(10) |
218 | + self._fastboot('boot %s' % self.deployment_data['boot_image']) |
219 | + self._adb('wait-for-device') |
220 | + |
221 | + def _get_partition_mount_point(self, partition): |
222 | + lookup = { |
223 | + self.config.data_part_android_org: '/data', |
224 | + self.config.sys_part_android_org: '/system', |
225 | + } |
226 | + return lookup[partition] |
227 | + |
228 | + def _adb(self, args, ignore_failure = False, spawn = False, timeout = 600): |
229 | + cmd = self.config.adb_command + ' ' + args |
230 | + if spawn: |
231 | + return logging_spawn(cmd, timeout = 60) |
232 | + else: |
233 | + self._call(cmd, ignore_failure, timeout) |
234 | + |
235 | + def _fastboot(self, args, ignore_failure = False, timeout = 600): |
236 | + self._call(self.config.fastboot_command + ' ' + args, ignore_failure, timeout) |
237 | + |
238 | + def _call(self, cmd, ignore_failure, timeout): |
239 | + cmd = 'timeout ' + str(timeout) + 's ' + cmd |
240 | + logging.debug("Running on the host: %s" % cmd) |
241 | + if ignore_failure: |
242 | + subprocess.call(cmd, shell = True) |
243 | + else: |
244 | + subprocess.check_call(cmd, shell = True) |
245 | + |
246 | + def _get_image(self, url): |
247 | + sdir = self.working_dir |
248 | + image = download_image(url, self.context, sdir, decompress=False) |
249 | + return image |
250 | + |
251 | + @property |
252 | + def working_dir(self): |
253 | + if (self.config.nexus_working_directory is None or |
254 | + self.config.nexus_working_directory.strip() == ''): |
255 | + return self.scratch_dir |
256 | + |
257 | + if self._working_dir is None: |
258 | + self._working_dir = mkdtemp(self.config.nexus_working_directory) |
259 | + return self._working_dir |
260 | + |
261 | + |
262 | +target_class = NexusTarget |
On 01/15/2013 01:18 PM, Antonio Terceiro wrote:
> + def power_on(self):
Seems like you might want to add some check like:
if not self.deployment _data.get( 'boot_image' , False):
raise CriticalError( 'Deploy action must be run first')
> + self.reboot_os() bootloader( ) test_image( ) "export PS1='%s'" % self.deployment _data[' TESTER_ PS1']) runner( proc)
> + self.reboot_
> + self.boot_
> +
> + self._powered_on = True
> + proc = self.adb('shell', spawn = True)
> + proc.sendline("") # required to put the adb shell in a reasonable state
> + proc.sendline(
> + self._runner = self._get_
> +
> + return proc
> + def power_off(self, proc):
> + # there is no way to power off the Nexus while USB is plugged on; even
> + # if you remove power, it will stay on.
> + pass
> +
This might be correct, but I fear a bad image could put us in an
irrecoverable state. One thing we might consider adding is some type of
logic like I did in sdmux.sh:
<http:// bazaar. launchpad. net/~linaro- validation/ lava-dispatcher /trunk/ view/head: /lava_dispatche r/device/ sdmux.sh>
This essentially powers down a USB port and might solve your problem.
However, since you use ADB over USB be aware of this issue:
https:/ /groups. google. com/forum/ #!topic/ android- platform/ hLmD0DDAzj8
Lastly, the filesystem function looks like it might need some exception
handling logic. Not sure about this, but I found the need when
implementing that logic for other targets.