PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/support/android/builder.py

http://github.com/appcelerator/titanium_mobile
Python | 2648 lines | 2435 code | 132 blank | 81 comment | 233 complexity | cac4effa378a19aae6a2aed15fafadbf MD5 | raw file
Possible License(s): MIT, JSON, Apache-2.0, 0BSD, CC-BY-SA-3.0, BSD-2-Clause, MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-3.0, Unlicense

Large files files are truncated, but you can click here to view the full file

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Appcelerator Titanium Mobile
  5. # Copyright (c) 2011-2012 by Appcelerator, Inc. All Rights Reserved.
  6. # Licensed under the terms of the Apache Public License
  7. # Please see the LICENSE included with this distribution for details.
  8. #
  9. # General builder script for staging, packaging, deploying,
  10. # and debugging Titanium Mobile applications on Android
  11. #
  12. import os, sys, subprocess, shutil, time, signal, string, platform, re, glob, hashlib, imp, inspect
  13. import run, avd, prereq, zipfile, tempfile, fnmatch, codecs, traceback, sgmllib
  14. from os.path import splitext
  15. from compiler import Compiler
  16. from os.path import join, splitext, split, exists
  17. from shutil import copyfile
  18. from xml.dom.minidom import parseString
  19. from tilogger import *
  20. from datetime import datetime, timedelta
  21. reload(sys) # this is required to prevent the following error: "AttributeError: 'module' object has no attribute 'setdefaultencoding'"
  22. sys.setdefaultencoding("utf_8") # Fix umlaut issues
  23. template_dir = os.path.abspath(os.path.dirname(sys._getframe(0).f_code.co_filename))
  24. top_support_dir = os.path.dirname(template_dir)
  25. sys.path.append(top_support_dir)
  26. sys.path.append(os.path.join(top_support_dir, 'common'))
  27. sys.path.append(os.path.join(top_support_dir, 'module'))
  28. import simplejson, java
  29. from mako.template import Template
  30. from tiapp import *
  31. from android import Android
  32. from androidsdk import AndroidSDK
  33. from deltafy import Deltafy, Delta
  34. from css import csscompiler
  35. from module import ModuleDetector
  36. import localecompiler
  37. import fastdev
  38. import requireIndex
  39. resourceFiles = ['strings.xml', 'attrs.xml', 'styles.xml', 'bools.xml', 'colors.xml',
  40. 'dimens.xml', 'ids.xml', 'integers.xml', 'arrays.xml']
  41. ignoreFiles = ['.gitignore', '.cvsignore', '.DS_Store'];
  42. ignoreDirs = ['.git','.svn','_svn', 'CVS'];
  43. android_avd_hw = {'hw.camera': 'yes', 'hw.gps':'yes'}
  44. res_skips = ['style']
  45. log = None
  46. # Copied from frameworks/base/tools/aapt/Package.cpp
  47. uncompressed_types = [
  48. ".jpg", ".jpeg", ".png", ".gif",
  49. ".wav", ".mp2", ".mp3", ".ogg", ".aac",
  50. ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
  51. ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
  52. ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
  53. ".amr", ".awb", ".wma", ".wmv"
  54. ]
  55. # Java keywords to reference in case app id contains java keyword
  56. java_keywords = [
  57. "abstract", "continue", "for", "new", "switch",
  58. "assert", "default", "goto", "package", "synchronized",
  59. "boolean", "do", "if", "private", "this",
  60. "break", "double", "implements", "protected", "throw",
  61. "byte", "else", "import", "public", "throws",
  62. "case", "enum", "instanceof", "return", "transient",
  63. "catch", "extends", "int", "short", "try",
  64. "char", "final", "interface", "static", "void",
  65. "class", "finally", "long", "strictfp", "volatile",
  66. "const", "float", "native", "super", "while",
  67. "true", "false", "null"
  68. ]
  69. MIN_API_LEVEL = 10
  70. HONEYCOMB_MR2_LEVEL = 13
  71. KNOWN_ABIS = ("armeabi-v7a", "x86")
  72. # Used only to find <script> tags in HTML files
  73. # so we can be sure to package referenced JS files
  74. # even when compiling for production. (See
  75. # Builder.package_and_deploy later in this file.)
  76. class HTMLParser(sgmllib.SGMLParser):
  77. def parse(self, html_source):
  78. self.feed(html_source)
  79. self.close()
  80. def __init__(self, verbose=0):
  81. sgmllib.SGMLParser.__init__(self, verbose)
  82. self.referenced_js_files = []
  83. def start_script(self, attributes):
  84. for name, value in attributes:
  85. if value and name.lower() == "src":
  86. self.referenced_js_files.append(value.lower())
  87. def get_referenced_js_files(self):
  88. return self.referenced_js_files
  89. def launch_logcat():
  90. valid_device_switches = ('-e', '-d', '-s')
  91. device_id = None
  92. android_sdk_location = None
  93. adb_location = None
  94. logcat_process = None
  95. device_switch = None # e.g., -e or -d or -s
  96. def show_usage():
  97. print >> sys.stderr, ""
  98. print >> sys.stderr, "%s devicelog <sdk_dir> <device_switch> [device_serial_number]" % os.path.basename(sys.argv[0])
  99. print >> sys.stderr, ""
  100. print >> sys.stderr, "The <device_switch> can be -e, -d -s. If -s, also pass serial number."
  101. sys.exit(1)
  102. if len(sys.argv) < 3:
  103. print >> sys.stderr, "Missing Android SDK location."
  104. show_usage()
  105. else:
  106. android_sdk_location = os.path.abspath(os.path.expanduser(sys.argv[2]))
  107. adb_location = AndroidSDK(android_sdk_location).get_adb()
  108. if len(sys.argv) < 4:
  109. print >> sys.stderr, "Missing device/emulator switch (e.g., -e, -d, -s)."
  110. show_usage()
  111. device_switch = sys.argv[3]
  112. if device_switch not in valid_device_switches:
  113. print >> sys.stderr, "Unknown device type switch: %s" % device_switch
  114. show_usage()
  115. if device_switch == "-s":
  116. if len(sys.argv) < 5:
  117. print >> sys.stderr, "Must specify serial number when using -s."
  118. show_usage()
  119. else:
  120. device_id = sys.argv[4]
  121. # For killing the logcat process if our process gets killed.
  122. def signal_handler(signum, frame):
  123. print "[DEBUG] Signal %s received. Terminating the logcat process." % signum
  124. if logcat_process is not None:
  125. if platform.system() == "Windows":
  126. os.system("taskkill /F /T /PID %i" % logcat_process.pid)
  127. else:
  128. os.kill(logcat_process.pid, signal.SIGTERM)
  129. # make sure adb is running on windows, else XP can lockup the python
  130. # process when adb runs first time
  131. if platform.system() == "Windows":
  132. run.run([adb_location, "start-server"], True, ignore_output=True)
  133. logcat_cmd = [adb_location, device_switch]
  134. if device_id:
  135. logcat_cmd.append(device_id)
  136. logcat_cmd.extend(["logcat", "-s", "*:d,*,TiAPI:V"])
  137. logcat_process = subprocess.Popen(logcat_cmd)
  138. if platform.system() != "Windows":
  139. signal.signal(signal.SIGHUP, signal_handler)
  140. signal.signal(signal.SIGQUIT, signal_handler)
  141. signal.signal(signal.SIGINT, signal_handler)
  142. signal.signal(signal.SIGABRT, signal_handler)
  143. signal.signal(signal.SIGTERM, signal_handler)
  144. # In case it's gonna exit early (like if the command line
  145. # was wrong or something) give it a chance to do so before we start
  146. # waiting on it.
  147. time.sleep(1)
  148. return_code = logcat_process.poll()
  149. if return_code:
  150. signal_handler(signal.SIGQUIT, None)
  151. sys.exit(return_code)
  152. # Now wait for it.
  153. try:
  154. return_code = logcat_process.wait()
  155. except OSError:
  156. signal_handler(signal.SIGQUIT, None)
  157. sys.exit(return_code)
  158. sys.exit(return_code)
  159. def render_template_with_tiapp(template_text, tiapp_obj):
  160. t = Template(template_text)
  161. return t.render(tiapp=tiapp_obj)
  162. def remove_ignored_dirs(dirs):
  163. for d in dirs:
  164. if d in ignoreDirs:
  165. dirs.remove(d)
  166. # ZipFile.extractall introduced in Python 2.6, so this is workaround for earlier
  167. # versions
  168. def zip_extractall(zfile, target_dir):
  169. file_infos = zfile.infolist()
  170. for info in file_infos:
  171. if info.file_size > 0:
  172. file_path = os.path.join(target_dir, os.path.normpath(info.filename))
  173. parent_path = os.path.dirname(file_path)
  174. if not os.path.exists(parent_path):
  175. os.makedirs(parent_path)
  176. out_file = open(file_path, "wb")
  177. out_file.write(zfile.read(info.filename))
  178. out_file.close()
  179. def dequote(s):
  180. if s[0:1] == '"':
  181. return s[1:-1]
  182. return s
  183. def pipe(args1,args2):
  184. p1 = subprocess.Popen(args1, stdout=subprocess.PIPE)
  185. p2 = subprocess.Popen(args2, stdin=p1.stdout, stdout=subprocess.PIPE)
  186. return p2.communicate()[0]
  187. def read_properties(propFile, separator=":= "):
  188. propDict = dict()
  189. for propLine in propFile:
  190. propDef = propLine.strip()
  191. if len(propDef) == 0:
  192. continue
  193. if propDef[0] in ( '!', '#' ):
  194. continue
  195. punctuation= [ propDef.find(c) for c in separator ] + [ len(propDef) ]
  196. found= min( [ pos for pos in punctuation if pos != -1 ] )
  197. name= propDef[:found].rstrip()
  198. value= propDef[found:].lstrip(separator).rstrip()
  199. propDict[name]= value
  200. propFile.close()
  201. return propDict
  202. def info(msg):
  203. log.info(msg)
  204. def debug(msg):
  205. log.debug(msg)
  206. def warn(msg):
  207. log.warn(msg)
  208. def trace(msg):
  209. log.trace(msg)
  210. def error(msg):
  211. log.error(msg)
  212. def copy_all(source_folder, dest_folder, mergeXMLResources=False, ignore_dirs=[], ignore_files=[], ignore_exts=[], one_time_msg=""):
  213. msg_shown = False
  214. for root, dirs, files in os.walk(source_folder, True, None, True):
  215. for d in dirs:
  216. if d in ignore_dirs:
  217. dirs.remove(d)
  218. for f in files:
  219. if f in ignore_files:
  220. continue
  221. ext = os.path.splitext(f)[1]
  222. if ext in ignore_exts:
  223. continue
  224. if one_time_msg and not msg_shown:
  225. info(one_time_msg)
  226. msg_shown = True
  227. from_ = os.path.join(root, f)
  228. to_ = from_.replace(source_folder, dest_folder, 1)
  229. to_directory = os.path.split(to_)[0]
  230. if not os.path.exists(to_directory):
  231. os.makedirs(to_directory)
  232. shutil.copyfile(from_, to_)
  233. #
  234. # Merge the xml resource files in res/values/ if there are multiple files with the same name.
  235. # (TIMOB-12663)
  236. #
  237. elif mergeXMLResources and os.path.isfile(to_) and f in resourceFiles:
  238. sfile = open(from_, 'r')
  239. dfile = open(to_, 'r')
  240. scontent = sfile.read()
  241. dcontent = dfile.read()
  242. sfile.close()
  243. dfile.close()
  244. sindex = scontent.find('</resources>')
  245. dindex = dcontent.find('>', dcontent.find('<resources')) + 1
  246. content_to_write = scontent[:sindex] + dcontent[dindex:]
  247. wfile = open(to_, 'w')
  248. wfile.write(content_to_write)
  249. wfile.close()
  250. else:
  251. shutil.copyfile(from_, to_)
  252. def remove_orphaned_files(source_folder, target_folder, ignore=[]):
  253. is_res = source_folder.endswith('Resources') or source_folder.endswith('Resources' + os.sep)
  254. for root, dirs, files in os.walk(target_folder):
  255. for f in files:
  256. if f in ignore:
  257. continue
  258. full = os.path.join(root, f)
  259. rel = full.replace(target_folder, '')
  260. if rel[0] == os.sep:
  261. rel = rel[1:]
  262. is_orphan = False
  263. if not os.path.exists(os.path.join(source_folder, rel)):
  264. is_orphan = True
  265. # But it could be under android/... too (platform-specific)
  266. if is_orphan and is_res:
  267. if os.path.exists(os.path.join(source_folder, 'android', rel)):
  268. is_orphan = False
  269. if is_orphan:
  270. os.remove(full)
  271. def is_resource_drawable(path):
  272. if re.search("android/images/(high|medium|low|res-[^/]+)/", path.replace(os.sep, "/")):
  273. return True
  274. else:
  275. return False
  276. def resource_drawable_folder(path):
  277. if not is_resource_drawable(path):
  278. return None
  279. else:
  280. pattern = r'/android/images/(high|medium|low|res-[^/]+)/'
  281. match = re.search(pattern, path.replace(os.sep, "/"))
  282. if not match.groups():
  283. return None
  284. folder = match.groups()[0]
  285. if re.match('high|medium|low', folder):
  286. return 'drawable-%sdpi' % folder[0]
  287. else:
  288. return 'drawable-%s' % folder.replace('res-', '')
  289. def remove_duplicate_nodes_in_res_file(full_path, node_names_to_check):
  290. f = open(full_path, 'r')
  291. contents = f.read()
  292. f.close()
  293. doc = parseString(contents)
  294. resources_node = doc.getElementsByTagName('resources')[0]
  295. made_change = False
  296. for node_name in node_names_to_check:
  297. nodes = doc.getElementsByTagName(node_name)
  298. if len(nodes) == 0:
  299. continue
  300. name_list = [] #keeps track of the name attribute for the node we are checking
  301. for node in nodes:
  302. # Only check for the children of the "resources" node
  303. if node.parentNode != resources_node:
  304. continue
  305. name = node.getAttribute('name')
  306. # Remove the node with the duplicate names
  307. if name in name_list:
  308. resources_node.removeChild(node)
  309. made_change = True
  310. debug('Removed duplicate node [%s] from %s' %(name, full_path))
  311. else:
  312. name_list.append(name)
  313. if made_change:
  314. new_contents = doc.toxml()
  315. f = codecs.open(full_path, 'w')
  316. f.write(new_contents)
  317. f.close()
  318. class Builder(object):
  319. def __init__(self, name, sdk, project_dir, support_dir, app_id, is_emulator):
  320. self.top_dir = project_dir
  321. self.project_tiappxml = os.path.join(self.top_dir,'tiapp.xml')
  322. self.project_dir = os.path.join(project_dir,'build','android')
  323. self.res_dir = os.path.join(self.project_dir,'res')
  324. self.platform_dir = os.path.join(project_dir, 'platform', 'android')
  325. self.project_src_dir = os.path.join(self.project_dir, 'src')
  326. self.project_gen_dir = os.path.join(self.project_dir, 'gen')
  327. self.name = name
  328. self.app_id = app_id
  329. self.support_dir = support_dir
  330. self.compiled_files = []
  331. self.force_rebuild = False
  332. self.debugger_host = None
  333. self.debugger_port = -1
  334. self.profiler_host = None
  335. self.profiler_port = -1
  336. self.fastdev_port = -1
  337. self.fastdev = False
  338. self.compile_js = False
  339. self.tool_api_level = MIN_API_LEVEL
  340. self.abis = list(KNOWN_ABIS)
  341. # don't build if a java keyword in the app id would cause the build to fail
  342. tok = self.app_id.split('.')
  343. for token in tok:
  344. if token in java_keywords:
  345. error("Do not use java keywords for project app id, such as " + token)
  346. sys.exit(1)
  347. tool_api_level_explicit = False
  348. temp_tiapp = TiAppXML(self.project_tiappxml)
  349. if temp_tiapp and temp_tiapp.android:
  350. if 'tool-api-level' in temp_tiapp.android:
  351. self.tool_api_level = int(temp_tiapp.android['tool-api-level'])
  352. tool_api_level_explicit = True
  353. if 'abi' in temp_tiapp.android and temp_tiapp.android['abi'] != 'all':
  354. tiapp_abis = [abi.strip() for abi in temp_tiapp.android['abi'].split(",")]
  355. to_remove = [bad_abi for bad_abi in tiapp_abis if bad_abi not in KNOWN_ABIS]
  356. if to_remove:
  357. warn("The following ABIs listed in the Android <abi> section of tiapp.xml are unknown and will be ignored: %s." % ", ".join(to_remove))
  358. tiapp_abis = [abi for abi in tiapp_abis if abi not in to_remove]
  359. self.abis = tiapp_abis
  360. if not self.abis:
  361. warn("Android <abi> tiapp.xml section does not specify any valid ABIs. Defaulting to '%s'." %
  362. ",".join(KNOWN_ABIS))
  363. self.abis = list(KNOWN_ABIS)
  364. self.sdk = AndroidSDK(sdk, self.tool_api_level)
  365. # If the tool-api-level was not explicitly set in the tiapp.xml, but
  366. # <uses-sdk android:targetSdkVersion> *is* set, try to match the target version.
  367. if (not tool_api_level_explicit and temp_tiapp and temp_tiapp.android_manifest
  368. and "manifest" in temp_tiapp.android_manifest):
  369. self.check_target_api_version(temp_tiapp.android_manifest["manifest"])
  370. self.tiappxml = temp_tiapp
  371. json_contents = open(os.path.join(template_dir,'dependency.json')).read()
  372. self.depends_map = simplejson.loads(json_contents)
  373. # favor the ANDROID_SDK_HOME environment variable if used
  374. if os.environ.has_key('ANDROID_SDK_HOME') and os.path.exists(os.environ['ANDROID_SDK_HOME']):
  375. self.home_dir = os.path.join(os.environ['ANDROID_SDK_HOME'], '.titanium')
  376. self.android_home_dir = os.path.join(os.environ['ANDROID_SDK_HOME'], '.android')
  377. # we place some files in the users home
  378. elif platform.system() == "Windows":
  379. self.home_dir = os.path.join(os.environ['USERPROFILE'], '.titanium')
  380. self.android_home_dir = os.path.join(os.environ['USERPROFILE'], '.android')
  381. else:
  382. self.home_dir = os.path.join(os.path.expanduser('~'), '.titanium')
  383. self.android_home_dir = os.path.join(os.path.expanduser('~'), '.android')
  384. if not os.path.exists(self.home_dir):
  385. os.makedirs(self.home_dir)
  386. self.sdcard = os.path.join(self.home_dir,'android2.sdcard')
  387. self.classname = Android.strip_classname(self.name)
  388. if not is_emulator:
  389. self.set_java_commands()
  390. # start in 1.4, you no longer need the build/android directory
  391. # if missing, we'll create it on the fly
  392. if not os.path.exists(self.project_dir) or not os.path.exists(os.path.join(self.project_dir,'AndroidManifest.xml')):
  393. android_creator = Android(name, app_id, self.sdk, None, self.java)
  394. parent_dir = os.path.dirname(self.top_dir)
  395. if os.path.exists(self.top_dir):
  396. android_creator.create(parent_dir, project_dir=self.top_dir, build_time=True)
  397. else:
  398. android_creator.create(parent_dir)
  399. self.force_rebuild = True
  400. sys.stdout.flush()
  401. def check_target_api_version(self, manifest_elements):
  402. pattern = r'android:targetSdkVersion=\"(\d+)\"'
  403. for el in manifest_elements:
  404. if el.nodeName == "uses-sdk":
  405. xml = el.toxml()
  406. matches = re.findall(pattern, xml)
  407. if matches:
  408. new_level = self.sdk.try_best_match_api_level(int(matches[0]))
  409. if new_level != self.tool_api_level:
  410. self.tool_api_level = new_level
  411. break
  412. def set_java_commands(self):
  413. commands = java.find_java_commands()
  414. to_check = ("java", "javac", "keytool", "jarsigner")
  415. found = True
  416. for check in to_check:
  417. if not commands[check]:
  418. found = False
  419. error("Required Java tool '%s' not located." % check)
  420. if not found:
  421. error("One or more required files not found - please check your JAVA_HOME environment variable")
  422. sys.exit(1)
  423. self.jarsigner = commands["jarsigner"]
  424. self.keytool = commands["keytool"]
  425. self.javac = commands["javac"]
  426. self.java = commands["java"]
  427. if not commands["environ_java_home"] and commands["java_home"]:
  428. os.environ["JAVA_HOME"] = commands["java_home"]
  429. def wait_for_home(self, type):
  430. max_wait = 20
  431. attempts = 0
  432. while True:
  433. processes = self.sdk.list_processes(['-%s' % type])
  434. found_home = False
  435. for process in processes:
  436. if process["name"] == "android.process.acore":
  437. found_home = True
  438. break
  439. if found_home:
  440. break
  441. attempts += 1
  442. if attempts == max_wait:
  443. error("Timed out waiting for android.process.acore")
  444. return False
  445. time.sleep(1)
  446. return True
  447. def wait_for_device(self, type):
  448. debug("Waiting for device to be ready ...")
  449. t = time.time()
  450. max_wait = 30
  451. max_zero = 10
  452. attempts = 0
  453. zero_attempts = 0
  454. timed_out = True
  455. no_devices = False
  456. while True:
  457. devices = self.sdk.list_devices()
  458. trace("adb devices returned %s devices/emulators" % len(devices))
  459. if len(devices) > 0:
  460. found = False
  461. for device in devices:
  462. if type == "e" and device.is_emulator() and not device.is_offline(): found = True
  463. elif type == "d" and device.is_device(): found = True
  464. if found:
  465. timed_out = False
  466. break
  467. else: zero_attempts += 1
  468. try: time.sleep(5) # for some reason KeyboardInterrupts get caught here from time to time
  469. except KeyboardInterrupt: pass
  470. attempts += 1
  471. if attempts == max_wait:
  472. break
  473. elif zero_attempts == max_zero:
  474. no_devices = True
  475. break
  476. if timed_out:
  477. if type == "e":
  478. device = "emulator"
  479. extra_message = "you may need to close the emulator and try again"
  480. else:
  481. device = "device"
  482. extra_message = "you may try reconnecting the USB cable"
  483. error("Timed out waiting for %s to be ready, %s" % (device, extra_message))
  484. if no_devices:
  485. sys.exit(1)
  486. return False
  487. debug("Device connected... (waited %d seconds)" % (attempts*5))
  488. duration = time.time() - t
  489. debug("waited %f seconds on emulator to get ready" % duration)
  490. if duration > 1.0:
  491. info("Waiting for the Android Emulator to become available")
  492. return self.wait_for_home(type)
  493. #time.sleep(20) # give it a little more time to get installed
  494. return True
  495. def create_avd(self, avd_id, avd_skin, avd_abi):
  496. # Sanity check the AVD to see if the ABI is available, or
  497. # necessary.
  498. available_avds = avd.get_avds(self.sdk)
  499. multiple_abis = False
  500. for device in available_avds:
  501. if device['id'] == avd_id:
  502. default_abi = device['abis'][0]
  503. multiple_abis = ( len(device['abis']) != 1 )
  504. if avd_abi is None:
  505. avd_abi = default_abi
  506. elif avd_abi not in device['abis']:
  507. warn("ABI %s not supported for AVD ID %s: Using default ABI %s" % (avd_abi, avd_id, default_abi))
  508. avd_abi = default_abi
  509. break
  510. if multiple_abis:
  511. name = "titanium_%s_%s_%s" % (avd_id, avd_skin, avd_abi)
  512. else:
  513. name = "titanium_%s_%s" % (avd_id, avd_skin)
  514. name = name.replace(' ', '_')
  515. if not os.path.exists(self.home_dir):
  516. os.makedirs(self.home_dir)
  517. avd_path = os.path.join(self.android_home_dir, 'avd')
  518. my_avd = os.path.join(avd_path,"%s.avd" % name)
  519. own_sdcard = os.path.join(self.home_dir, '%s.sdcard' % name)
  520. if not os.path.exists(my_avd) or os.path.exists(own_sdcard):
  521. # starting with 1.7.2, when we create a new avd, give it its own
  522. # SDCard as well.
  523. self.sdcard = own_sdcard
  524. if not os.path.exists(self.sdcard):
  525. info("Creating 64M SD card for use in Android emulator")
  526. run.run([self.sdk.get_mksdcard(), '64M', self.sdcard])
  527. if not os.path.exists(my_avd):
  528. if multiple_abis:
  529. info("Creating new Android Virtual Device (%s %s %s)" % (avd_id,avd_skin,avd_abi))
  530. else:
  531. info("Creating new Android Virtual Device (%s %s)" % (avd_id,avd_skin))
  532. inputgen = os.path.join(template_dir,'input.py')
  533. abi_args = []
  534. if multiple_abis:
  535. abi_args = ['-b', avd_abi]
  536. pipe([sys.executable, inputgen], [self.sdk.get_android(), '--verbose', 'create', 'avd', '--name', name, '--target', avd_id, '-s', avd_skin, '--force', '--sdcard', self.sdcard] + abi_args)
  537. inifile = os.path.join(my_avd,'config.ini')
  538. inifilec = open(inifile,'r').read()
  539. inifiledata = open(inifile,'w')
  540. inifiledata.write(inifilec)
  541. # TODO - Document options
  542. for hw_option in android_avd_hw.keys():
  543. inifiledata.write("%s=%s\n" % (hw_option, android_avd_hw[hw_option]))
  544. inifiledata.close()
  545. return name
  546. def run_emulator(self, avd_id, avd_skin, avd_name, avd_abi, add_args):
  547. info("Launching Android emulator...one moment")
  548. debug("From: " + self.sdk.get_emulator())
  549. debug("SDCard: " + self.sdcard)
  550. if avd_name is None:
  551. debug("AVD ID: " + avd_id)
  552. debug("AVD Skin: " + avd_skin)
  553. else:
  554. debug("AVD Name: " + avd_name)
  555. if avd_abi is not None:
  556. debug("AVD ABI: " + avd_abi)
  557. debug("SDK: " + sdk_dir)
  558. # make sure adb is running on windows, else XP can lockup the python
  559. # process when adb runs first time
  560. if platform.system() == "Windows":
  561. run.run([self.sdk.get_adb(), "start-server"], True, ignore_output=True)
  562. devices = self.sdk.list_devices()
  563. for device in devices:
  564. if device.is_emulator() and device.get_port() == 5560:
  565. info("Emulator is running.")
  566. sys.exit()
  567. # this will create an AVD on demand or re-use existing one if already created
  568. if avd_name == None:
  569. avd_name = self.create_avd(avd_id, avd_skin, avd_abi)
  570. # start the emulator
  571. emulator_cmd = [
  572. self.sdk.get_emulator(),
  573. '-avd',
  574. avd_name,
  575. '-port',
  576. '5560',
  577. '-sdcard',
  578. self.get_sdcard_path(),
  579. '-logcat',
  580. '*:d,*,TiAPI:V',
  581. '-no-boot-anim',
  582. '-partition-size',
  583. '128' # in between nexusone and droid
  584. ]
  585. if add_args:
  586. emulator_cmd.extend([arg.strip() for arg in add_args if len(arg.strip()) > 0])
  587. debug(' '.join(emulator_cmd))
  588. p = subprocess.Popen(emulator_cmd)
  589. def handler(signum, frame):
  590. debug("signal caught: %d" % signum)
  591. if not p == None:
  592. debug("calling emulator kill on %d" % p.pid)
  593. if platform.system() == "Windows":
  594. os.system("taskkill /F /T /PID %i" % p.pid)
  595. else:
  596. os.kill(p.pid, signal.SIGTERM)
  597. if platform.system() != "Windows":
  598. signal.signal(signal.SIGHUP, handler)
  599. signal.signal(signal.SIGQUIT, handler)
  600. signal.signal(signal.SIGINT, handler)
  601. signal.signal(signal.SIGABRT, handler)
  602. signal.signal(signal.SIGTERM, handler)
  603. # give it some time to exit prematurely
  604. time.sleep(1)
  605. rc = p.poll()
  606. if rc != None:
  607. handler(3,None)
  608. sys.exit(rc)
  609. # wait for the emulator to finish
  610. try:
  611. rc = p.wait()
  612. except OSError:
  613. handler(3,None)
  614. info("Android Emulator has exited")
  615. sys.exit(rc)
  616. def check_file_exists(self, path):
  617. output = self.run_adb('shell', 'ls', path)
  618. if output != None:
  619. if output.find("No such file or directory") == -1 \
  620. and output.find("error: device offline") == -1:
  621. return True
  622. return False
  623. def is_app_installed(self):
  624. return self.check_file_exists('/data/app/%s*.apk' % self.app_id)
  625. def get_sdcard_path(self):
  626. # We need to surround the sd card path in quotes for windows to account for spaces in path
  627. if platform.system() == "Windows":
  628. return '"' + self.sdcard + '"'
  629. return self.sdcard
  630. def are_resources_installed(self):
  631. return self.check_file_exists(self.sdcard_resources+'/app.js')
  632. def include_path(self, path, isfile):
  633. if not isfile and os.path.basename(path) in ignoreDirs: return False
  634. elif isfile and os.path.basename(path) in ignoreFiles: return False
  635. return True
  636. def warn_dupe_drawable_folders(self):
  637. tocheck = ('high', 'medium', 'low')
  638. image_parent = os.path.join(self.top_dir, 'Resources', 'android', 'images')
  639. for check in tocheck:
  640. if os.path.exists(os.path.join(image_parent, check)) and os.path.exists(os.path.join(image_parent, 'res-%sdpi' % check[0])):
  641. warn('You have both an android/images/%s folder and an android/images/res-%sdpi folder. Files from both of these folders will end up in res/drawable-%sdpi. If two files are named the same, there is no guarantee which one will be copied last and therefore be the one the application uses. You should use just one of these folders to avoid conflicts.' % (check, check[0], check[0]))
  642. def copy_module_platform_folders(self):
  643. for module in self.modules:
  644. platform_folder = os.path.join(module.path, 'platform', 'android')
  645. if os.path.exists(platform_folder):
  646. copy_all(platform_folder, self.project_dir, True, one_time_msg="Copying platform-specific files for '%s' module" % module.manifest.name)
  647. def copy_commonjs_modules(self):
  648. info('Copying CommonJS modules...')
  649. for module in self.modules:
  650. if module.js is None:
  651. continue
  652. module_name = os.path.basename(module.js)
  653. self.non_orphans.append(module_name)
  654. shutil.copy(module.js, self.assets_resources_dir)
  655. def copy_project_platform_folder(self, ignore_dirs=[], ignore_files=[]):
  656. if not os.path.exists(self.platform_dir):
  657. return
  658. copy_all(self.platform_dir, self.project_dir, True, ignore_dirs, ignore_files, one_time_msg="Copying platform-specific files ...")
  659. def copy_resource_drawables(self):
  660. debug('Processing Android resource drawables')
  661. def make_resource_drawable_filename(orig):
  662. normalized = orig.replace(os.sep, "/")
  663. matches = re.search("/android/images/(high|medium|low|res-[^/]+)/(?P<chopped>.*$)", normalized)
  664. if matches and matches.groupdict() and 'chopped' in matches.groupdict():
  665. chopped = matches.groupdict()['chopped'].lower()
  666. for_hash = chopped
  667. if for_hash.endswith('.9.png'):
  668. for_hash = for_hash[:-6] + '.png'
  669. extension = ""
  670. without_extension = chopped
  671. if re.search("\\..*$", chopped):
  672. if chopped.endswith('.9.png'):
  673. extension = '9.png'
  674. without_extension = chopped[:-6]
  675. else:
  676. extension = chopped.split(".")[-1]
  677. without_extension = chopped[:-(len(extension)+1)]
  678. cleaned_without_extension = re.sub(r'[^a-z0-9_]', '_', without_extension)
  679. cleaned_extension = re.sub(r'[^a-z0-9\._]', '_', extension)
  680. result = cleaned_without_extension[:80] + "_" + hashlib.md5(for_hash).hexdigest()[:10]
  681. if extension:
  682. result += "." + extension
  683. return result
  684. else:
  685. trace("Regexp for resource drawable file %s failed" % orig)
  686. return None
  687. def delete_resource_drawable(orig):
  688. folder = resource_drawable_folder(orig)
  689. res_file = os.path.join(self.res_dir, folder, make_resource_drawable_filename(orig))
  690. if os.path.exists(res_file):
  691. try:
  692. trace("DELETING FILE: %s" % res_file)
  693. os.remove(res_file)
  694. except:
  695. warn('Unable to delete %s: %s. Execution will continue.' % (res_file, sys.exc_info()[0]))
  696. def copy_resource_drawable(orig):
  697. partial_folder = resource_drawable_folder(orig)
  698. if not partial_folder:
  699. trace("Could not copy %s; resource folder not determined" % orig)
  700. return
  701. dest_folder = os.path.join(self.res_dir, partial_folder)
  702. dest_filename = make_resource_drawable_filename(orig)
  703. if dest_filename is None:
  704. return
  705. dest = os.path.join(dest_folder, dest_filename)
  706. if not os.path.exists(dest_folder):
  707. os.makedirs(dest_folder)
  708. trace("COPYING FILE: %s => %s" % (orig, dest))
  709. shutil.copy(orig, dest)
  710. fileset = []
  711. if self.force_rebuild or self.deploy_type == 'production' or \
  712. (self.js_changed and not self.fastdev):
  713. for root, dirs, files in os.walk(os.path.join(self.top_dir, "Resources")):
  714. remove_ignored_dirs(dirs)
  715. for f in files:
  716. if f in ignoreFiles:
  717. continue
  718. path = os.path.join(root, f)
  719. if is_resource_drawable(path) and f != 'default.png':
  720. fileset.append(path)
  721. else:
  722. if self.project_deltas:
  723. for delta in self.project_deltas:
  724. path = delta.get_path()
  725. if is_resource_drawable(path):
  726. if delta.get_status() == Delta.DELETED:
  727. delete_resource_drawable(path)
  728. else:
  729. fileset.append(path)
  730. if len(fileset) == 0:
  731. return False
  732. for f in fileset:
  733. copy_resource_drawable(f)
  734. return True
  735. def copy_project_resources(self):
  736. info("Copying project resources..")
  737. def validate_filenames(topdir):
  738. for root, dirs, files in os.walk(topdir):
  739. remove_ignored_dirs(dirs)
  740. for d in dirs:
  741. if d == "iphone" or d == "mobileweb":
  742. dirs.remove(d)
  743. for filename in files:
  744. if filename.startswith("_"):
  745. error("%s is an invalid filename. Android will not package assets whose filenames start with underscores. Fix and rebuild." % os.path.join(root, filename))
  746. sys.exit(1)
  747. resources_dir = os.path.join(self.top_dir, 'Resources')
  748. validate_filenames(resources_dir)
  749. android_resources_dir = os.path.join(resources_dir, 'android')
  750. self.project_deltafy = Deltafy(resources_dir, include_callback=self.include_path)
  751. self.project_deltas = self.project_deltafy.scan()
  752. self.js_changed = False
  753. tiapp_delta = self.project_deltafy.scan_single_file(self.project_tiappxml)
  754. self.tiapp_changed = tiapp_delta is not None
  755. full_copy = not os.path.exists(self.assets_resources_dir)
  756. if self.tiapp_changed or self.force_rebuild or full_copy:
  757. info("Detected change in tiapp.xml, or assets deleted. Forcing full re-build...")
  758. # force a clean scan/copy when the tiapp.xml has changed
  759. self.project_deltafy.clear_state()
  760. self.project_deltas = self.project_deltafy.scan()
  761. # rescan tiapp.xml so it doesn't show up as created next time around
  762. self.project_deltafy.scan_single_file(self.project_tiappxml)
  763. if self.tiapp_changed:
  764. for root, dirs, files in os.walk(self.project_gen_dir, topdown=False):
  765. for name in files:
  766. os.remove(os.path.join(root, name))
  767. for name in dirs:
  768. os.rmdir(os.path.join(root, name))
  769. def strip_slash(s):
  770. if s[0:1]=='/' or s[0:1]=='\\': return s[1:]
  771. return s
  772. def make_relative(path, relative_to, prefix=None):
  773. relative_path = strip_slash(path[len(relative_to):])
  774. if prefix is not None:
  775. return os.path.join(prefix, relative_path)
  776. return relative_path
  777. for delta in self.project_deltas:
  778. path = delta.get_path()
  779. if re.search("android/images/(high|medium|low|res-[^/]+)/", path.replace(os.sep, "/")):
  780. continue # density images are handled later
  781. if delta.get_status() == Delta.DELETED and path.startswith(android_resources_dir):
  782. shared_path = path.replace(android_resources_dir, resources_dir, 1)
  783. if os.path.exists(shared_path):
  784. dest = make_relative(shared_path, resources_dir, self.assets_resources_dir)
  785. trace("COPYING FILE: %s => %s (platform-specific file was removed)" % (shared_path, dest))
  786. shutil.copy(shared_path, dest)
  787. if delta.get_status() != Delta.DELETED:
  788. if path.startswith(android_resources_dir):
  789. dest = make_relative(path, android_resources_dir, self.assets_resources_dir)
  790. else:
  791. # don't copy it if there is an android-specific file
  792. if os.path.exists(path.replace(resources_dir, android_resources_dir, 1)):
  793. continue
  794. dest = make_relative(path, resources_dir, self.assets_resources_dir)
  795. if path.startswith(os.path.join(resources_dir, "iphone")) or path.startswith(os.path.join(resources_dir, "mobileweb")) or path.startswith(os.path.join(resources_dir, "blackberry")):
  796. continue
  797. parent = os.path.dirname(dest)
  798. if not os.path.exists(parent):
  799. os.makedirs(parent)
  800. trace("COPYING %s FILE: %s => %s" % (delta.get_status_str(), path, dest))
  801. shutil.copy(path, dest)
  802. if (path.startswith(resources_dir) or path.startswith(android_resources_dir)) and path.endswith(".js"):
  803. self.js_changed = True
  804. # copy to the sdcard in development mode
  805. if self.sdcard_copy and self.app_installed and (self.deploy_type == 'development' or self.deploy_type == 'test'):
  806. if path.startswith(android_resources_dir):
  807. relative_path = make_relative(delta.get_path(), android_resources_dir)
  808. else:
  809. relative_path = make_relative(delta.get_path(), resources_dir)
  810. relative_path = relative_path.replace("\\", "/")
  811. self.run_adb('push', delta.get_path(), "%s/%s" % (self.sdcard_resources, relative_path))
  812. if os.environ.has_key('LIVEVIEW'):
  813. debug("LiveView enabled")
  814. appjs = os.path.join(self.assets_resources_dir, 'app.js')
  815. _appjs = os.path.join(self.assets_resources_dir, '_app.js')
  816. liveviewjs = os.path.join(tempfile.gettempdir(), 'liveview.js')
  817. self.non_orphans.append('_app.js')
  818. if not os.path.exists(appjs):
  819. debug('app.js not found: %s' % appjs)
  820. if not os.path.exists(liveviewjs):
  821. debug('liveviewjs.js not found: %s' % liveviewjs)
  822. if os.path.exists(appjs) and os.path.exists(liveviewjs):
  823. trace("COPYING %s => %s" % (appjs, _appjs))
  824. shutil.copy(appjs, _appjs)
  825. trace("COPYING %s => %s" % (liveviewjs, appjs))
  826. shutil.copy(liveviewjs, appjs)
  827. else:
  828. debug('LiveView not enabled')
  829. index_json_path = os.path.join(self.assets_dir, "index.json")
  830. if len(self.project_deltas) > 0 or not os.path.exists(index_json_path):
  831. requireIndex.generateJSON(self.assets_dir, index_json_path)
  832. def check_permissions_mapping(self, key, permissions_mapping, permissions_list):
  833. try:
  834. perms = permissions_mapping[key]
  835. if perms:
  836. for perm in perms:
  837. try:
  838. permissions_list.index(perm)
  839. except:
  840. permissions_list.append(perm)
  841. except:
  842. pass
  843. def generate_android_manifest(self,compiler):
  844. self.generate_localizations()
  845. self.remove_duplicate_res()
  846. # NOTE: these are built-in permissions we need -- we probably need to refine when these are needed too
  847. permissions_required = ['INTERNET','ACCESS_WIFI_STATE','ACCESS_NETWORK_STATE', 'WRITE_EXTERNAL_STORAGE']
  848. GEO_PERMISSION = [ 'ACCESS_COARSE_LOCATION', 'ACCESS_FINE_LOCATION']
  849. CONTACTS_READ_PERMISSION = ['READ_CONTACTS']
  850. CONTACTS_PERMISSION = ['READ_CONTACTS', 'WRITE_CONTACTS']
  851. CALENDAR_PERMISSION = ['READ_CALENDAR', 'WRITE_CALENDAR']
  852. VIBRATE_PERMISSION = ['VIBRATE']
  853. CAMERA_PERMISSION = ['CAMERA']
  854. WALLPAPER_PERMISSION = ['SET_WALLPAPER']
  855. # Enable mock location if in development or test mode.
  856. if self.deploy_type == 'development' or self.deploy_type == 'test':
  857. GEO_PERMISSION.append('ACCESS_MOCK_LOCATION')
  858. # this is our module to permission(s) trigger - for each module on the left, require the permission(s) on the right
  859. permissions_module_mapping = {
  860. # GEO
  861. 'geolocation' : GEO_PERMISSION
  862. }
  863. # this is our module method to permission(s) trigger - for each method on the left, require the permission(s) on the right
  864. permissions_method_mapping = {
  865. # MAP
  866. 'Map.createView' : GEO_PERMISSION,
  867. # MEDIA
  868. 'Media.vibrate' : VIBRATE_PERMISSION,
  869. 'Media.showCamera' : CAMERA_PERMISSION,
  870. # CONTACTS
  871. 'Contacts.createPerson' : CONTACTS_PERMISSION,
  872. 'Contacts.removePerson' : CONTACTS_PERMISSION,
  873. 'Contacts.getAllContacts' : CONTACTS_READ_PERMISSION,
  874. 'Contacts.showContactPicker' : CONTACTS_READ_PERMISSION,
  875. 'Contacts.showContacts' : CONTACTS_READ_PERMISSION,
  876. 'Contacts.getPersonByID' : CONTACTS_READ_PERMISSION,
  877. 'Contacts.getPeopleWithName' : CONTACTS_READ_PERMISSION,
  878. 'Contacts.getAllPeople' : CONTACTS_READ_PERMISSION,
  879. 'Contacts.getAllGroups' : CONTACTS_READ_PERMISSION,
  880. 'Contacts.getGroupByID' : CONTACTS_READ_PERMISSION,
  881. # Old CALENDAR
  882. 'Android.Calendar.getAllAlerts' : CALENDAR_PERMISSION,
  883. 'Android.Calendar.getAllCalendars' : CALENDAR_PERMISSION,
  884. 'Android.Calendar.getCalendarById' : CALENDAR_PERMISSION,
  885. 'Android.Calendar.getSelectableCalendars' : CALENDAR_PERMISSION,
  886. # CALENDAR
  887. 'Calendar.getAllAlerts' : CALENDAR_PERMISSION,
  888. 'Calendar.getAllCalendars' : CALENDAR_PERMISSION,
  889. 'Calendar.getCalendarById' : CALENDAR_PERMISSION,
  890. 'Calendar.getSelectableCalendars' : CALENDAR_PERMISSION,
  891. # WALLPAPER
  892. 'Media.Android.setSystemWallpaper' : WALLPAPER_PERMISSION,
  893. }
  894. VIDEO_ACTIVITY = """<activity
  895. android:name="ti.modules.titanium.media.TiVideoActivity"
  896. android:configChanges="keyboardHidden|orientation"
  897. android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
  898. android:launchMode="singleTask"
  899. />"""
  900. MAP_ACTIVITY = """<activity
  901. android:name="ti.modules.titanium.map.TiMapActivity"
  902. android:configChanges="keyboardHidden|orientation"
  903. android:launchMode="singleTask"
  904. />
  905. <uses-library android:name="com.google.android.maps" />"""
  906. CAMERA_ACTIVITY = """<activity
  907. android:name="ti.modules.titanium.media.TiCameraActivity"
  908. android:configChanges="keyboardHidden|orientation"
  909. android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
  910. />"""
  911. activity_mapping = {
  912. # MEDIA
  913. 'Media.createVideoPlayer' : VIDEO_ACTIVITY,
  914. 'Media.showCamera' : CAMERA_ACTIVITY,
  915. # MAPS
  916. 'Map.createView' : MAP_ACTIVITY,
  917. }
  918. # this is a map of our APIs to ones that require Google APIs to be available on the device
  919. google_apis = {
  920. "Map.createView" : True
  921. }
  922. activities = []
  923. # figure out which permissions we need based on the used module
  924. for mod in compiler.modules:
  925. self.check_permissions_mapping(mod, permissions_module_mapping, permissions_required)
  926. # figure out which permissions we need based on the used module methods
  927. for mn in compiler.module_methods:
  928. self.check_permissions_mapping(mn, permissions_method_mapping, permissions_required)
  929. try:
  930. mappings = activity_mapping[mn]
  931. try:
  932. if google_apis[mn] and not self.google_apis_supported:
  933. warn("Google APIs detected but a device has been selected that doesn't support them. The API call to Titanium.%s will fail using '%s'" % (mn,my_avd['name']))
  934. continue
  935. except:
  936. pass
  937. try:
  938. activities.index(mappings)
  939. except:
  940. activities.append(mappings)
  941. except:
  942. pass
  943. # Javascript-based activities defined in tiapp.xml
  944. if self.tiapp and self.tiapp.android and 'activities' in self.tiapp.android:
  945. tiapp_activities = self.tiapp.android['activities']
  946. for key in tiapp_activities:
  947. activity = tiapp_activities[key]
  948. if not 'url' in activity:
  949. continue
  950. activity_name = self.app_id + '.' + activity['classname']
  951. activity_str = '<activity \n\t\t\tandroid:name="%s"' % activity_name
  952. for subkey in activity:
  953. if subkey not in ('nodes', 'name', 'url', 'options', 'classname', 'android:name'):
  954. activity_str += '\n\t\t\t%s="%s"' % (subkey, activity[subkey])
  955. if 'android:config' not in activity:
  956. activity_str += '\n\t\t\tandroid:configChanges="keyboardHidden|orientation"'
  957. if 'nodes' in activity:
  958. activity_str += '>'
  959. for node in activity['nodes']:
  960. activity_str += '\n\t\t\t\t' + node.toxml()
  961. activities.append(activity_str + '\n\t\t</activity>\n')
  962. else:
  963. activities.append(activity_str + '\n\t\t/>\n')
  964. activities = set(activities)
  965. services = []
  966. # Javascript-based services defined in tiapp.xml
  967. if self.tiapp and self.tiapp.android and 'services' in self.tiapp.android:
  968. tiapp_services = self.tiapp.android['services']
  969. for key in tiapp_services:
  970. service = tiapp_services[key]
  971. if not 'url' in service:
  972. continue
  973. service_name = self.app_id + '.' + service['classname']
  974. service_str = '<service \n\t\t\tandroid:name="%s"' % service_name
  975. for subkey in service:
  976. if subkey not in ('nodes', 'service_type', 'type', 'name', 'url', 'options', 'classname', 'android:name'):
  977. service_str += '\n\t\t\t%s="%s"' % (subkey, service[subkey])
  978. if 'nodes' in service:
  979. service_str += '>'
  980. for node in service['nodes']:
  981. service_str += '\n\t\t\t\t' + node.toxml()
  982. services.append(service_str + '\n\t\t</service>\n')
  983. else:
  984. services.append(service_str + '\n\t\t/>\n')
  985. self.use_maps = False
  986. self.res_changed = False
  987. icon_name = self.tiapp.properties['icon']
  988. icon_path = os.path.join(self.assets_resources_dir, icon_name)
  989. icon_ext = os.path.splitext(icon_path)[1]
  990. res_drawable_dest = os.path.join(self.project_dir, 'res', 'drawable')
  991. if not os.path.exists(res_drawable_dest):
  992. os.makedirs(res_drawable_dest)
  993. default_icon = os.path.join(self.support_resources_dir, 'default.png')
  994. dest_icon = os.path.join(res_drawable_dest, 'appicon%s' % icon_ext)
  995. if Deltafy.needs_update(icon_path, dest_icon):
  996. self.res_changed = True
  997. debug("copying app icon: %s" % icon_path)
  998. shutil.copy(icon_path, dest_icon)
  999. elif Deltafy.needs_update(default_icon, dest_icon):
  1000. self.res_changed = True
  1001. debug("copying default app icon")
  1002. shutil.copy(default_icon, dest_icon)
  1003. # make our Titanium theme for our icon
  1004. res_values_dir = os.path.join(self.project_dir, 'res','values')
  1005. if not os.path.exists(res_values_dir):
  1006. os.makedirs(res_values_dir)
  1007. theme_xml = os.path.join(res_values_dir,'theme.xml')
  1008. if not os.path.exists(theme_xml):
  1009. self.res_changed = True
  1010. debug('generating theme.xml')
  1011. theme_file = open(theme_xml, 'w')
  1012. theme_flags = "Theme"
  1013. # We need to treat the default values for fulscreen and
  1014. # navbar-hidden the same as android.py does -- false for both.
  1015. theme_fullscreen = False
  1016. theme_navbarhidden = False
  1017. if (self.tiapp.properties.get("fullscreen") == "true" or
  1018. self.tiapp.properties.get("statusbar-hidden") == "true"):
  1019. theme_fullscreen = True
  1020. elif self.tiapp.properties.get("navbar-hidden") == "true":
  1021. theme_navbarhidden = True
  1022. if theme_fullscreen:
  1023. theme_flags += ".NoTitleBar.Fullscreen"
  1024. elif theme_navbarhidden:
  1025. theme_flags += ".NoTitleBar"
  1026. # Wait, one exception. If you want the notification area (very
  1027. # top of screen) hidden, but want the title bar in the app,
  1028. # there's no theme for that. So we have to use the default theme (no flags)
  1029. # and when the application code starts running, the adjustments are then made.
  1030. # Only do this when the properties are explicitly set, so as to avoid changing
  1031. # old default behavior.
  1032. if theme_flags.endswith('.Fullscreen') and \
  1033. self.tiapp.properties.get("navbar-hidden") == 'false' and \
  1034. ('fullscreen' in self.tiapp.explicit_properties or \
  1035. 'statusbar-hidden' in self.tiapp.explicit_properties) and \
  1036. 'navbar-hidden' in self.tiapp.explicit_properties:
  1037. theme_flags = 'Theme'
  1038. TITANIUM_THEME="""<?xml version="1.0" encoding="utf-8"?>
  1039. <resources>
  1040. <style name="Theme.Titanium" parent="android:%s">
  1041. <item name="android:windowBackground">@drawable/background</item>
  1042. </style>
  1043. </resources>
  1044. """ % theme_flags
  1045. theme_file.write(TITANIUM_THEME)
  1046. theme_file.close()
  1047. # create our background image which acts as splash screen during load
  1048. resources_dir = os.path.join(self.top_dir, 'Resources')
  1049. android_images_dir = os.path.join(resources_dir, 'android', 'images')
  1050. # look for density-specific default.png's first
  1051. if os.path.exists(android_images_dir):
  1052. pattern = r'/android/images/(high|medium|low|res-[^/]+)/default.png'
  1053. for root, dirs, files in os.walk(android_images_dir):
  1054. remove_ignored_dirs(dirs)
  1055. for f in files:
  1056. if f in ignoreFiles:
  1057. continue
  1058. path = os.path.join(root, f)
  1059. if re.search(pattern, path.replace(os.sep, "/")):
  1060. res_folder = resource_drawable_folder(path)
  1061. debug('found %s splash screen at %s' % (res_folder, path))
  1062. dest_path = os.path.join(self.res_dir, res_folder)
  1063. dest_file = os.path.join(dest_path, 'background.png')
  1064. if not os.path.exists(dest_path):
  1065. os.makedirs(dest_path)
  1066. if Deltafy.needs_update(path, dest_file):
  1067. self.res_changed = True
  1068. debug('copying %s splash screen to %s' % (path, dest_file))
  1069. shutil.copy(path, dest_file)
  1070. default_png = os.path.join(self.assets_resources_dir, 'default.png')
  1071. support_default_png = os.path.join(self.support_resources_dir, 'default.png')
  1072. background_png = os.path.join(self.project_dir, 'res','drawable','background.png')
  1073. if os.path.exists(default_png) and Deltafy.needs_update(default_png, background_png):
  1074. self.res_changed = True
  1075. debug("found splash screen at %s" % os.path.abspath(default_png))
  1076. shutil.copy(default_png, background_png)
  1077. elif Deltafy.needs_update(support_default_png, background_png):
  1078. self.res_changed = True
  1079. debug("copying default splash screen")
  1080. shutil.copy(support_default_png, background_png)
  1081. android_manifest = os.path.join(self.project_dir, 'AndroidManifest.xml')
  1082. android_manifest_to_read = android_manifest
  1083. # NOTE: allow the user to use their own custom AndroidManifest if they put a file named
  1084. # AndroidManifest.xml in platform/android, in which case all bets are off
  1085. is_custom = False
  1086. # Catch people who may have it in project root (un-released 1.4.x android_native_refactor branch users)
  1087. if os.path.exists(os.path.join(self.top_dir, 'AndroidManifest.xml')):
  1088. warn('AndroidManifest.xml file in the project root is ignored. Move it to platform/android if you want it to be your custom manifest.')
  1089. android_custom_manifest = os.path.join(self.project_dir, 'AndroidManifest.custom.xml')
  1090. if not os.path.exists(android_custom_manifest):
  1091. android_custom_manifest = os.path.join(self.platform_dir, 'AndroidManifest.xml')
  1092. else:
  1093. warn('Use of AndroidManifest.custom.xml is deprecated. Please put your custom manifest as "AndroidManifest.xml" in the "platform/android" directory if you do not need to compile for versions < 1.5')
  1094. if os.path.exists(android_custom_manifest):
  1095. android_manifest_to_read = android_custom_manifest
  1096. is_custom = True
  1097. info("Detected custom ApplicationManifest.xml -- no Titanium version migration supported")
  1098. default_manifest_contents = self.android.render_android_manifest()
  1099. if self.sdk.api_level >= HONEYCOMB_MR2_LEVEL:
  1100. # Need to add "screenSize" in our default "configChanges" attribute on
  1101. # <activity> elements, else changes in orientation will cause the app
  1102. # to restart. cf. TIMOB-10863.
  1103. default_manifest_contents = default_manifest_contents.replace('|orientation"', '|orientation|screenSize"')
  1104. debug("Added 'screenSize' to <activity android:configChanges> because targeted api level %s is >= %s" % (self.sdk.api_level, HONEYCOMB_MR2_LEVEL))
  1105. custom_manifest_contents = None
  1106. if is_custom:
  1107. custom_manifest_contents = open(android_manifest_to_read,'r').read()
  1108. manifest_xml = ''
  1109. def get_manifest_xml(tiapp, template_obj=None):
  1110. xml = ''
  1111. if 'manifest' in tiapp.android_manifest:
  1112. for manifest_el in tiapp.android_manifest['manifest']:
  1113. # since we already track permissions in another way, go ahead and us e that
  1114. if manifest_el.nodeName == 'uses-permission' and manifest_el.hasAttribute('android:name'):
  1115. if manifest_el.getAttribute('android:name').split('.')[-1] not in permissions_required:
  1116. perm_val = manifest_el.getAttribute('android:name')
  1117. if template_obj is not None and "${" in perm_val:
  1118. perm_val = render_template_with_tiapp(perm_val, template_obj)
  1119. permissions_required.append(perm_val)
  1120. elif manifest_el.nodeName not in ('supports-screens', 'uses-sdk'):
  1121. this_xml = manifest_el.toprettyxml()
  1122. if template_obj is not None and "${" in this_xml:
  1123. this_xml = render_template_with_tiapp(this_xml, template_obj)
  1124. xml += this_xml
  1125. return xml
  1126. application_xml = ''
  1127. def get_application_xml(tiapp, template_obj=None):
  1128. xml = ''
  1129. if 'application' in tiapp.android_manifest:
  1130. for app_el in tiapp.android_manifest['application']:
  1131. this_xml = app_el.toxml()
  1132. if template_obj is not None and "${" in this_xml:
  1133. this_xml = render_template_with_tiapp(this_xml, template_obj)
  1134. xml += this_xml
  1135. return xml
  1136. # add manifest / application entries from tiapp.xml
  1137. manifest_xml += get_manifest_xml(self.tiapp)
  1138. application_xml += get_application_xml(self.tiapp)
  1139. # add manifest / application entries from modules
  1140. for module in self.modules:
  1141. if module.xml == None: continue
  1142. manifest_xml += get_manifest_xml(module.xml, self.tiapp)
  1143. application_xml += get_application_xml(module.xml, self.tiapp)
  1144. # build the permissions XML based on the permissions detected
  1145. permissions_required = set(permissions_required)
  1146. permissions_required_xml = ""
  1147. for p in permissions_required:
  1148. if '.' not in p:
  1149. permissions_required_xml+="<uses-permission android:name=\"android.permission.%s\"/>\n\t" % p
  1150. else:
  1151. permissions_required_xml+="<uses-permission android:name=\"%s\"/>\n\t" % p
  1152. def fill_manifest(manifest_source):
  1153. ti_activities = '<!-- TI_ACTIVITIES -->'
  1154. ti_permissions = '<!-- TI_PERMISSIONS -->'
  1155. ti_manifest = '<!-- TI_MANIFEST -->'
  1156. ti_application = '<!-- TI_APPLICATION -->'
  1157. ti_services = '<!-- TI_SERVICES -->'
  1158. manifest_source = manifest_source.replace(ti_activities,"\n\n\t\t".join(activities))
  1159. manifest_source = manifest_source.replace(ti_services,"\n\n\t\t".join(services))
  1160. manifest_source = manifest_source.replace(ti_permissions,permissions_required_xml)
  1161. if len(manifest_xml) > 0:
  1162. manifest_source = manifest_source.replace(ti_manifest, manifest_xml)
  1163. if len(application_xml) > 0:
  1164. manifest_source = manifest_source.replace(ti_application, application_xml)
  1165. return manifest_source
  1166. default_manifest_contents = fill_manifest(default_manifest_contents)
  1167. # if a custom uses-sdk or supports-screens has been specified via tiapp.xml
  1168. # <android><manifest>..., we need to replace the ones in the generated
  1169. # default manifest
  1170. supports_screens_node = None
  1171. uses_sdk_node = None
  1172. if 'manifest' in self.tiapp.android_manifest:
  1173. for node in self.tiapp.android_manifest['manifest']:
  1174. if node.nodeName == 'uses-sdk':
  1175. uses_sdk_node = node
  1176. elif node.nodeName == 'supports-screens':
  1177. supports_screens_node = node
  1178. if supports_screens_node or uses_sdk_node or ('manifest-attributes' in self.tiapp.android_manifest and se

Large files files are truncated, but you can click here to view the full file