Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(38)

Side by Side Diff: build/android/pylib/device/device_utils.py

Issue 706203003: Update from https://crrev.com/303153 (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « build/android/pylib/constants.py ('k') | build/android/pylib/device/device_utils_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Provides a variety of device interactions based on adb. 5 """Provides a variety of device interactions based on adb.
6 6
7 Eventually, this will be based on adb_wrapper. 7 Eventually, this will be based on adb_wrapper.
8 """ 8 """
9 # pylint: disable=W0613 9 # pylint: disable=W0613
10 10
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
102 Args: 102 Args:
103 timeout: timeout in seconds 103 timeout: timeout in seconds
104 retries: number of retries 104 retries: number of retries
105 105
106 Returns: 106 Returns:
107 True if the device is online, False otherwise. 107 True if the device is online, False otherwise.
108 108
109 Raises: 109 Raises:
110 CommandTimeoutError on timeout. 110 CommandTimeoutError on timeout.
111 """ 111 """
112 return self.adb.GetState() == 'device' 112 try:
113 return self.adb.GetState() == 'device'
114 except device_errors.BaseError as exc:
115 logging.info('Failed to get state: %s', exc)
116 return False
113 117
114 @decorators.WithTimeoutAndRetriesFromInstance() 118 @decorators.WithTimeoutAndRetriesFromInstance()
115 def HasRoot(self, timeout=None, retries=None): 119 def HasRoot(self, timeout=None, retries=None):
116 """Checks whether or not adbd has root privileges. 120 """Checks whether or not adbd has root privileges.
117 121
118 Args: 122 Args:
119 timeout: timeout in seconds 123 timeout: timeout in seconds
120 retries: number of retries 124 retries: number of retries
121 125
122 Returns: 126 Returns:
123 True if adbd has root privileges, False otherwise. 127 True if adbd has root privileges, False otherwise.
124 128
125 Raises: 129 Raises:
126 CommandTimeoutError on timeout. 130 CommandTimeoutError on timeout.
127 DeviceUnreachableError on missing device. 131 DeviceUnreachableError on missing device.
128 """ 132 """
129 return self._HasRootImpl()
130
131 def _HasRootImpl(self):
132 try: 133 try:
133 self._RunShellCommandImpl('ls /root', check_return=True) 134 self.RunShellCommand('ls /root', check_return=True)
134 return True 135 return True
135 except device_errors.AdbShellCommandFailedError: 136 except device_errors.AdbShellCommandFailedError:
136 return False 137 return False
137 138
139 def NeedsSU(self, timeout=None, retries=None):
140 """Checks whether 'su' is needed to access protected resources.
141
142 Args:
143 timeout: timeout in seconds
144 retries: number of retries
145
146 Returns:
147 True if 'su' is available on the device and is needed to to access
148 protected resources; False otherwise if either 'su' is not available
149 (e.g. because the device has a user build), or not needed (because adbd
150 already has root privileges).
151
152 Raises:
153 CommandTimeoutError on timeout.
154 DeviceUnreachableError on missing device.
155 """
156 if 'needs_su' not in self._cache:
157 try:
158 self.RunShellCommand('su -c ls /root && ! ls /root', check_return=True,
159 timeout=timeout, retries=retries)
160 self._cache['needs_su'] = True
161 except device_errors.AdbShellCommandFailedError:
162 self._cache['needs_su'] = False
163 return self._cache['needs_su']
164
165
138 @decorators.WithTimeoutAndRetriesFromInstance() 166 @decorators.WithTimeoutAndRetriesFromInstance()
139 def EnableRoot(self, timeout=None, retries=None): 167 def EnableRoot(self, timeout=None, retries=None):
140 """Restarts adbd with root privileges. 168 """Restarts adbd with root privileges.
141 169
142 Args: 170 Args:
143 timeout: timeout in seconds 171 timeout: timeout in seconds
144 retries: number of retries 172 retries: number of retries
145 173
146 Raises: 174 Raises:
147 CommandFailedError if root could not be enabled. 175 CommandFailedError if root could not be enabled.
148 CommandTimeoutError on timeout. 176 CommandTimeoutError on timeout.
149 """ 177 """
178 if 'needs_su' in self._cache:
179 del self._cache['needs_su']
150 if not self.old_interface.EnableAdbRoot(): 180 if not self.old_interface.EnableAdbRoot():
151 raise device_errors.CommandFailedError( 181 raise device_errors.CommandFailedError(
152 'Could not enable root.', device=str(self)) 182 'Could not enable root.', device=str(self))
153 183
154 @decorators.WithTimeoutAndRetriesFromInstance() 184 @decorators.WithTimeoutAndRetriesFromInstance()
155 def IsUserBuild(self, timeout=None, retries=None): 185 def IsUserBuild(self, timeout=None, retries=None):
156 """Checks whether or not the device is running a user build. 186 """Checks whether or not the device is running a user build.
157 187
158 Args: 188 Args:
159 timeout: timeout in seconds 189 timeout: timeout in seconds
(...skipping 24 matching lines...) Expand all
184 CommandFailedError if the external storage path could not be determined. 214 CommandFailedError if the external storage path could not be determined.
185 CommandTimeoutError on timeout. 215 CommandTimeoutError on timeout.
186 DeviceUnreachableError on missing device. 216 DeviceUnreachableError on missing device.
187 """ 217 """
188 return self._GetExternalStoragePathImpl() 218 return self._GetExternalStoragePathImpl()
189 219
190 def _GetExternalStoragePathImpl(self): 220 def _GetExternalStoragePathImpl(self):
191 if 'external_storage' in self._cache: 221 if 'external_storage' in self._cache:
192 return self._cache['external_storage'] 222 return self._cache['external_storage']
193 223
194 value = self._RunShellCommandImpl('echo $EXTERNAL_STORAGE', 224 value = self.RunShellCommand('echo $EXTERNAL_STORAGE',
195 single_line=True, 225 single_line=True,
196 check_return=True) 226 check_return=True)
197 if not value: 227 if not value:
198 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', 228 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set',
199 str(self)) 229 str(self))
200 self._cache['external_storage'] = value 230 self._cache['external_storage'] = value
201 return value 231 return value
202 232
203 @decorators.WithTimeoutAndRetriesFromInstance() 233 @decorators.WithTimeoutAndRetriesFromInstance()
204 def GetApplicationPath(self, package, timeout=None, retries=None): 234 def GetApplicationPath(self, package, timeout=None, retries=None):
205 """Get the path of the installed apk on the device for the given package. 235 """Get the path of the installed apk on the device for the given package.
206 236
(...skipping 24 matching lines...) Expand all
231 wifi: A boolean indicating if we should wait for wifi to come up or not. 261 wifi: A boolean indicating if we should wait for wifi to come up or not.
232 timeout: timeout in seconds 262 timeout: timeout in seconds
233 retries: number of retries 263 retries: number of retries
234 264
235 Raises: 265 Raises:
236 CommandFailedError on failure. 266 CommandFailedError on failure.
237 CommandTimeoutError if one of the component waits times out. 267 CommandTimeoutError if one of the component waits times out.
238 DeviceUnreachableError if the device becomes unresponsive. 268 DeviceUnreachableError if the device becomes unresponsive.
239 """ 269 """
240 def sd_card_ready(): 270 def sd_card_ready():
241 return self.RunShellCommand(['ls', self.GetExternalStoragePath()], 271 try:
242 single_line=True, check_return=True) 272 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
273 check_return=True)
274 return True
275 except device_errors.AdbShellCommandFailedError:
276 return False
243 277
244 def pm_ready(): 278 def pm_ready():
245 try: 279 try:
246 return self.GetApplicationPath('android') 280 return self.GetApplicationPath('android')
247 except device_errors.CommandFailedError: 281 except device_errors.CommandFailedError:
248 return False 282 return False
249 283
250 def boot_completed(): 284 def boot_completed():
251 return self.GetProp('sys.boot_completed') == '1' 285 return self.GetProp('sys.boot_completed') == '1'
252 286
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after
382 (with the optional newline at the end stripped). 416 (with the optional newline at the end stripped).
383 417
384 Raises: 418 Raises:
385 AdbShellCommandFailedError if check_return is True and the exit code of 419 AdbShellCommandFailedError if check_return is True and the exit code of
386 the command run on the device is non-zero. 420 the command run on the device is non-zero.
387 CommandFailedError if single_line is True but the output contains two or 421 CommandFailedError if single_line is True but the output contains two or
388 more lines. 422 more lines.
389 CommandTimeoutError on timeout. 423 CommandTimeoutError on timeout.
390 DeviceUnreachableError on missing device. 424 DeviceUnreachableError on missing device.
391 """ 425 """
392 return self._RunShellCommandImpl(cmd, check_return=check_return, cwd=cwd,
393 env=env, as_root=as_root, single_line=single_line, timeout=timeout)
394
395 def _RunShellCommandImpl(self, cmd, check_return=False, cwd=None, env=None,
396 as_root=False, single_line=False, timeout=None):
397 def env_quote(key, value): 426 def env_quote(key, value):
398 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): 427 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key):
399 raise KeyError('Invalid shell variable name %r' % key) 428 raise KeyError('Invalid shell variable name %r' % key)
400 # using double quotes here to allow interpolation of shell variables 429 # using double quotes here to allow interpolation of shell variables
401 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) 430 return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
402 431
403 if not isinstance(cmd, basestring): 432 if not isinstance(cmd, basestring):
404 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) 433 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
405 if as_root and not self._HasRootImpl(): 434 if as_root and self.NeedsSU():
406 cmd = 'su -c %s' % cmd 435 cmd = 'su -c %s' % cmd
407 if env: 436 if env:
408 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) 437 env = ' '.join(env_quote(k, v) for k, v in env.iteritems())
409 cmd = '%s %s' % (env, cmd) 438 cmd = '%s %s' % (env, cmd)
410 if cwd: 439 if cwd:
411 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) 440 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd)
412 if timeout is None: 441 if timeout is None:
413 timeout = self._default_timeout 442 timeout = self._default_timeout
414 443
415 try: 444 try:
416 # TODO(perezju) still need to make sure that we call a version of 445 output = self.adb.Shell(cmd, expect_rc=0)
417 # adb.Shell without a timeout-and-retries wrapper.
418 output = self.adb.Shell(cmd, expect_rc=0, timeout=timeout, retries=0)
419 except device_errors.AdbShellCommandFailedError as e: 446 except device_errors.AdbShellCommandFailedError as e:
420 if check_return: 447 if check_return:
421 raise 448 raise
422 else: 449 else:
423 output = e.output 450 output = e.output
424 451
425 output = output.splitlines() 452 output = output.splitlines()
426 if single_line: 453 if single_line:
427 if not output: 454 if not output:
428 return '' 455 return ''
(...skipping 25 matching lines...) Expand all
454 CommandFailedError if no process was killed. 481 CommandFailedError if no process was killed.
455 CommandTimeoutError on timeout. 482 CommandTimeoutError on timeout.
456 DeviceUnreachableError on missing device. 483 DeviceUnreachableError on missing device.
457 """ 484 """
458 pids = self._GetPidsImpl(process_name) 485 pids = self._GetPidsImpl(process_name)
459 if not pids: 486 if not pids:
460 raise device_errors.CommandFailedError( 487 raise device_errors.CommandFailedError(
461 'No process "%s"' % process_name, device=str(self)) 488 'No process "%s"' % process_name, device=str(self))
462 489
463 cmd = ['kill', '-%d' % signum] + pids.values() 490 cmd = ['kill', '-%d' % signum] + pids.values()
464 self._RunShellCommandImpl(cmd, as_root=as_root, check_return=True) 491 self.RunShellCommand(cmd, as_root=as_root, check_return=True)
465 492
466 if blocking: 493 if blocking:
467 wait_period = 0.1 494 wait_period = 0.1
468 while self._GetPidsImpl(process_name): 495 while self._GetPidsImpl(process_name):
469 time.sleep(wait_period) 496 time.sleep(wait_period)
470 497
471 return len(pids) 498 return len(pids)
472 499
473 @decorators.WithTimeoutAndRetriesFromInstance() 500 @decorators.WithTimeoutAndRetriesFromInstance()
474 def StartActivity(self, intent, blocking=False, trace_file_name=None, 501 def StartActivity(self, intent, blocking=False, trace_file_name=None,
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
605 632
606 Raises: 633 Raises:
607 CommandFailedError on failure. 634 CommandFailedError on failure.
608 CommandTimeoutError on timeout. 635 CommandTimeoutError on timeout.
609 DeviceUnreachableError on missing device. 636 DeviceUnreachableError on missing device.
610 """ 637 """
611 638
612 files = [] 639 files = []
613 for h, d in host_device_tuples: 640 for h, d in host_device_tuples:
614 if os.path.isdir(h): 641 if os.path.isdir(h):
615 self._RunShellCommandImpl(['mkdir', '-p', d], check_return=True) 642 self.RunShellCommand(['mkdir', '-p', d], check_return=True)
616 files += self._GetChangedFilesImpl(h, d) 643 files += self._GetChangedFilesImpl(h, d)
617 644
618 if not files: 645 if not files:
619 return 646 return
620 647
621 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) 648 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
622 file_count = len(files) 649 file_count = len(files)
623 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) 650 dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
624 for h, _ in host_device_tuples) 651 for h, _ in host_device_tuples)
625 dir_file_count = 0 652 dir_file_count = 0
(...skipping 11 matching lines...) Expand all
637 664
638 self._InstallCommands() 665 self._InstallCommands()
639 666
640 if dir_push_duration < push_duration and ( 667 if dir_push_duration < push_duration and (
641 dir_push_duration < zip_duration or not self._commands_installed): 668 dir_push_duration < zip_duration or not self._commands_installed):
642 self._PushChangedFilesIndividually(host_device_tuples) 669 self._PushChangedFilesIndividually(host_device_tuples)
643 elif push_duration < zip_duration or not self._commands_installed: 670 elif push_duration < zip_duration or not self._commands_installed:
644 self._PushChangedFilesIndividually(files) 671 self._PushChangedFilesIndividually(files)
645 else: 672 else:
646 self._PushChangedFilesZipped(files) 673 self._PushChangedFilesZipped(files)
647 self._RunShellCommandImpl( 674 self.RunShellCommand(
648 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples], 675 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples],
649 as_root=True, check_return=True) 676 as_root=True, check_return=True)
650 677
651 def _GetChangedFilesImpl(self, host_path, device_path): 678 def _GetChangedFilesImpl(self, host_path, device_path):
652 real_host_path = os.path.realpath(host_path) 679 real_host_path = os.path.realpath(host_path)
653 try: 680 try:
654 real_device_path = self._RunShellCommandImpl( 681 real_device_path = self.RunShellCommand(
655 ['realpath', device_path], single_line=True, check_return=True) 682 ['realpath', device_path], single_line=True, check_return=True)
656 except device_errors.CommandFailedError: 683 except device_errors.CommandFailedError:
657 real_device_path = None 684 real_device_path = None
658 if not real_device_path: 685 if not real_device_path:
659 return [(host_path, device_path)] 686 return [(host_path, device_path)]
660 687
661 # TODO(jbudorick): Move the md5 logic up into DeviceUtils or base 688 # TODO(jbudorick): Move the md5 logic up into DeviceUtils or base
662 # this function on mtime. 689 # this function on mtime.
663 # pylint: disable=W0212 690 # pylint: disable=W0212
664 host_hash_tuples, device_hash_tuples = self.old_interface._RunMd5Sum( 691 host_hash_tuples, device_hash_tuples = self.old_interface._RunMd5Sum(
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
737 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: 764 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file:
738 zip_proc = multiprocessing.Process( 765 zip_proc = multiprocessing.Process(
739 target=DeviceUtils._CreateDeviceZip, 766 target=DeviceUtils._CreateDeviceZip,
740 args=(zip_file.name, files)) 767 args=(zip_file.name, files))
741 zip_proc.start() 768 zip_proc.start()
742 zip_proc.join() 769 zip_proc.join()
743 770
744 zip_on_device = '%s/tmp.zip' % self._GetExternalStoragePathImpl() 771 zip_on_device = '%s/tmp.zip' % self._GetExternalStoragePathImpl()
745 try: 772 try:
746 self.adb.Push(zip_file.name, zip_on_device) 773 self.adb.Push(zip_file.name, zip_on_device)
747 self._RunShellCommandImpl( 774 self.RunShellCommand(
748 ['unzip', zip_on_device], 775 ['unzip', zip_on_device],
749 as_root=True, 776 as_root=True,
750 env={'PATH': '$PATH:%s' % install_commands.BIN_DIR}, 777 env={'PATH': '$PATH:%s' % install_commands.BIN_DIR},
751 check_return=True) 778 check_return=True)
752 finally: 779 finally:
753 if zip_proc.is_alive(): 780 if zip_proc.is_alive():
754 zip_proc.terminate() 781 zip_proc.terminate()
755 if self.IsOnline(): 782 if self.IsOnline():
756 self._RunShellCommandImpl(['rm', zip_on_device], check_return=True) 783 self.RunShellCommand(['rm', zip_on_device], check_return=True)
757 784
758 @staticmethod 785 @staticmethod
759 def _CreateDeviceZip(zip_path, host_device_tuples): 786 def _CreateDeviceZip(zip_path, host_device_tuples):
760 with zipfile.ZipFile(zip_path, 'w') as zip_file: 787 with zipfile.ZipFile(zip_path, 'w') as zip_file:
761 for host_path, device_path in host_device_tuples: 788 for host_path, device_path in host_device_tuples:
762 if os.path.isfile(host_path): 789 if os.path.isfile(host_path):
763 zip_file.write(host_path, device_path, zipfile.ZIP_DEFLATED) 790 zip_file.write(host_path, device_path, zipfile.ZIP_DEFLATED)
764 else: 791 else:
765 for hd, _, files in os.walk(host_path): 792 for hd, _, files in os.walk(host_path):
766 dd = '%s/%s' % (device_path, os.path.relpath(host_path, hd)) 793 dd = '%s/%s' % (device_path, os.path.relpath(host_path, hd))
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
884 timeout: timeout in seconds 911 timeout: timeout in seconds
885 retries: number of retries 912 retries: number of retries
886 913
887 Raises: 914 Raises:
888 CommandFailedError if the file could not be written on the device. 915 CommandFailedError if the file could not be written on the device.
889 CommandTimeoutError on timeout. 916 CommandTimeoutError on timeout.
890 DeviceUnreachableError on missing device. 917 DeviceUnreachableError on missing device.
891 """ 918 """
892 cmd = 'echo %s > %s' % (cmd_helper.SingleQuote(text), 919 cmd = 'echo %s > %s' % (cmd_helper.SingleQuote(text),
893 cmd_helper.SingleQuote(device_path)) 920 cmd_helper.SingleQuote(device_path))
894 self._RunShellCommandImpl(cmd, as_root=as_root, check_return=True) 921 self.RunShellCommand(cmd, as_root=as_root, check_return=True)
895 922
896 @decorators.WithTimeoutAndRetriesFromInstance() 923 @decorators.WithTimeoutAndRetriesFromInstance()
897 def Ls(self, device_path, timeout=None, retries=None): 924 def Ls(self, device_path, timeout=None, retries=None):
898 """Lists the contents of a directory on the device. 925 """Lists the contents of a directory on the device.
899 926
900 Args: 927 Args:
901 device_path: A string containing the path of the directory on the device 928 device_path: A string containing the path of the directory on the device
902 to list. 929 to list.
903 timeout: timeout in seconds 930 timeout: timeout in seconds
904 retries: number of retries 931 retries: number of retries
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after
1027 provided |process_name|. 1054 provided |process_name|.
1028 1055
1029 Raises: 1056 Raises:
1030 CommandTimeoutError on timeout. 1057 CommandTimeoutError on timeout.
1031 DeviceUnreachableError on missing device. 1058 DeviceUnreachableError on missing device.
1032 """ 1059 """
1033 return self._GetPidsImpl(process_name) 1060 return self._GetPidsImpl(process_name)
1034 1061
1035 def _GetPidsImpl(self, process_name): 1062 def _GetPidsImpl(self, process_name):
1036 procs_pids = {} 1063 procs_pids = {}
1037 for line in self._RunShellCommandImpl('ps', check_return=True): 1064 for line in self.RunShellCommand('ps', check_return=True):
1038 try: 1065 try:
1039 ps_data = line.split() 1066 ps_data = line.split()
1040 if process_name in ps_data[-1]: 1067 if process_name in ps_data[-1]:
1041 procs_pids[ps_data[-1]] = ps_data[1] 1068 procs_pids[ps_data[-1]] = ps_data[1]
1042 except IndexError: 1069 except IndexError:
1043 pass 1070 pass
1044 return procs_pids 1071 return procs_pids
1045 1072
1046 @decorators.WithTimeoutAndRetriesFromInstance() 1073 @decorators.WithTimeoutAndRetriesFromInstance()
1047 def TakeScreenshot(self, host_path=None, timeout=None, retries=None): 1074 def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
1125 Returns: 1152 Returns:
1126 A Parallelizer operating over |devices|. 1153 A Parallelizer operating over |devices|.
1127 """ 1154 """
1128 if not devices or len(devices) == 0: 1155 if not devices or len(devices) == 0:
1129 devices = pylib.android_commands.GetAttachedDevices() 1156 devices = pylib.android_commands.GetAttachedDevices()
1130 parallelizer_type = (parallelizer.Parallelizer if async 1157 parallelizer_type = (parallelizer.Parallelizer if async
1131 else parallelizer.SyncParallelizer) 1158 else parallelizer.SyncParallelizer)
1132 return parallelizer_type([ 1159 return parallelizer_type([
1133 d if isinstance(d, DeviceUtils) else DeviceUtils(d) 1160 d if isinstance(d, DeviceUtils) else DeviceUtils(d)
1134 for d in devices]) 1161 for d in devices])
OLDNEW
« no previous file with comments | « build/android/pylib/constants.py ('k') | build/android/pylib/device/device_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698