def build_and_run(self, install, avd_id, keystore=None, keystore_pass='******', keystore_alias='tidev', dist_dir=None): deploy_type = 'development' if install: if keystore == None: deploy_type = 'test' else: deploy_type = 'production' aapt = os.path.join(self.tools_dir,'aapt') jar = os.path.join(self.platform_dir,'android.jar') dx = os.path.join(self.tools_dir,'dx') apkbuilder = os.path.join(self.sdk,'tools','apkbuilder') if platform.system() == "Windows": aapt += ".exe" dx += ".bat" apkbuilder += ".bat" if keystore==None: keystore = os.path.join(self.support_dir,'dev_keystore') curdir = os.getcwd() tijar = os.path.join(self.support_dir,'titanium.jar') timapjar = os.path.join(self.support_dir,'titanium-map.jar') try: os.chdir(self.project_dir) if os.path.exists('bin'): shutil.rmtree('bin') os.makedirs('bin') if os.path.exists('lib'): shutil.copy(tijar,'lib') resources_dir = os.path.join(self.top_dir,'Resources') assets_dir = os.path.join('bin','assets') asset_resource_dir = os.path.join(assets_dir,'Resources') # we re-run the create each time through in case any of our key files # have changed android = Android(self.name,self.app_id,self.sdk) android.create(os.path.abspath(os.path.join(self.top_dir,'..')),True) # transform resources def strip_slash(s): if s[0:1]=='/' or s[0:1]=='\\': return s[1:] return s def recursive_cp(dir,dest): for root, dirs, files in os.walk(dir): # Remove file from the list of files copied # that shouldn't appear in the binaries for name in ignoreFiles: if name in files: files.remove(name); for name in ignoreDirs: if name in dirs: dirs.remove(name) # Copy remaining files relative = strip_slash(root.replace(dir,'')) relative_dest = os.path.join(dest,relative) if not os.path.exists(relative_dest): os.makedirs(relative_dest) for f in files: fullpath = os.path.join(root,f) relativedest = os.path.join(dest,relative,f) print "[TRACE] COPYING: %s => %s" %(fullpath,relativedest) shutil.copy(fullpath,relativedest) if os.path.exists(asset_resource_dir): shutil.rmtree(asset_resource_dir) os.makedirs(asset_resource_dir) recursive_cp(resources_dir,asset_resource_dir) if os.path.exists(os.path.join(asset_resource_dir,'iphone')): shutil.rmtree(os.path.join(asset_resource_dir,'iphone')) if os.path.exists(os.path.join(resources_dir,'android')): recursive_cp(os.path.join(resources_dir,'android'),asset_resource_dir) shutil.rmtree(os.path.join(asset_resource_dir,'android')) sys.stdout.flush() if not os.path.exists(assets_dir): os.makedirs(assets_dir) my_avd = None google_apis_supported = False # find the AVD we've selected and determine if we support Google APIs for avd_props in avd.get_avds(self.sdk): if avd_props['id'] == avd_id: my_avd = avd_props google_apis_supported = (my_avd['name'].find('Google')!=-1) break # compile resources full_resource_dir = os.path.join(self.project_dir,asset_resource_dir) compiler = Compiler(self.app_id,full_resource_dir,False) compiler.compile() # Android SDK version --- FIXME: this is hardcoded until i hook in Nolan's code from Developer android_sdk_version = '3' # NOTE: these are built-in permissions we need -- we probably need to refine when these are needed too permissions_required = ['INTERNET','ACCESS_WIFI_STATE','ACCESS_NETWORK_STATE'] GEO_PERMISSION = [ 'ACCESS_COARSE_LOCATION', 'ACCESS_FINE_LOCATION', 'ACCESS_MOCK_LOCATION'] CONTACTS_PERMISSION = ['READ_CONTACTS'] VIBRATE_PERMISSION = ['VIBRATE'] CAMERA_PERMISSION = ['CAMERA'] # this is our module method to permission(s) trigger - for each method on the left, require the permission(s) on the right permission_mapping = { # GEO 'Geolocation.watchPosition' : GEO_PERMISSION, 'Geolocation.getCurrentPosition' : GEO_PERMISSION, 'Geolocation.watchHeading' : GEO_PERMISSION, 'Geolocation.getCurrentHeading' : GEO_PERMISSION, # MEDIA 'Media.vibrate' : VIBRATE_PERMISSION, 'Media.createVideoPlayer' : CAMERA_PERMISSION, 'Media.showCamera' : CAMERA_PERMISSION, # CONTACTS 'Contacts.createContact' : CONTACTS_PERMISSION, 'Contacts.saveContact' : CONTACTS_PERMISSION, 'Contacts.removeContact' : CONTACTS_PERMISSION, 'Contacts.addContact' : CONTACTS_PERMISSION, 'Contacts.getAllContacts' : CONTACTS_PERMISSION, 'Contacts.showContactPicker' : CONTACTS_PERMISSION, } VIDEO_ACTIVITY = """<activity android:name="org.appcelerator.titanium.TitaniumVideoActivity" android:configChanges="keyboardHidden|orientation" android:launchMode="singleTask" />""" MAP_ACTIVITY = """<activity android:name="org.appcelerator.titanium.module.map.TitaniumMapActivity" android:configChanges="keyboardHidden|orientation" android:launchMode="singleTask" /> <uses-library android:name="com.google.android.maps" />""" FACEBOOK_ACTIVITY = """<activity android:name="org.appcelerator.titanium.module.facebook.FBActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar" />""" activity_mapping = { # MEDIA 'Media.createVideoPlayer' : VIDEO_ACTIVITY, # MAPS 'Map.createView' : MAP_ACTIVITY, # FACEBOOK 'Facebook.setup' : FACEBOOK_ACTIVITY, 'Facebook.login' : FACEBOOK_ACTIVITY, 'Facebook.createLoginButton' : FACEBOOK_ACTIVITY, } # this is a map of our APIs to ones that require Google APIs to be available on the device google_apis = { "Map.createView" : True } activities = [] # figure out which permissions we need based on the used module methods for mn in compiler.module_methods: try: perms = permission_mapping[mn] if perms: for perm in perms: try: permissions_required.index(perm) except: permissions_required.append(perm) except: pass try: mappings = activity_mapping[mn] try: if google_apis[mn] and not google_apis_supported: print "[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']) sys.stdout.flush() continue except: pass try: activities.index(mappings) except: activities.append(mappings) except: pass # build the permissions XML based on the permissions detected permissions_required_xml = "" for p in permissions_required: permissions_required_xml+="<uses-permission android:name=\"android.permission.%s\"/>\n\t" % p # copy any module image directories for module in compiler.modules: if module.lower() == 'map' and google_apis_supported: tijar = timapjar print "[INFO] Detected Google Maps dependency. Using Titanium + Maps" img_dir = os.path.abspath(os.path.join(template_dir,'modules',module.lower(),'images')) if os.path.exists(img_dir): dest_img_dir = os.path.join(full_resource_dir,'modules',module.lower(),'images') if os.path.exists(dest_img_dir): shutil.rmtree(dest_img_dir) os.makedirs(dest_img_dir) copy_resources(img_dir,dest_img_dir) shutil.copy(os.path.join(self.top_dir,'tiapp.xml'), assets_dir) tiapp = open(os.path.join(assets_dir, 'tiapp.xml')).read() finalxml = os.path.join(assets_dir,'tiapp.xml') tiapp = TiAppXML(finalxml) tiapp.setDeployType(deploy_type) iconname = tiapp.properties['icon'] iconpath = os.path.join(asset_resource_dir,iconname) iconext = os.path.splitext(iconpath)[1] if not os.path.exists(os.path.join('res','drawable')): os.makedirs(os.path.join('res','drawable')) existingicon = os.path.join('res','drawable','appicon%s' % iconext) if os.path.exists(existingicon): os.remove(existingicon) if os.path.exists(iconpath): shutil.copy(iconpath,existingicon) # make our Titanium theme for our icon resfiledir = os.path.join('res','values') if not os.path.exists(resfiledir): os.makedirs(resfiledir) resfilepath = os.path.join(resfiledir,'theme.xml') if not os.path.exists(resfilepath): resfile = open(resfilepath,'w') TITANIUM_THEME="""<?xml version="1.0" encoding="utf-8"?> <resources> <style name="Theme.Titanium" parent="android:Theme"> <item name="android:windowBackground">@drawable/background</item> </style> </resources> """ resfile.write(TITANIUM_THEME) resfile.close() # create our background image which acts as splash screen during load splashimage = os.path.join(asset_resource_dir,'default.png') if os.path.exists(splashimage): print "[DEBUG] found splash screen at %s" % os.path.abspath(splashimage) shutil.copy(splashimage,os.path.join('res','drawable','background.png')) src_dir = os.path.join(self.project_dir, 'src') android_manifest = os.path.join(self.project_dir, 'AndroidManifest.xml') android_manifest_to_read = android_manifest # NOTE: allow the user to use their own custom AndroidManifest if they put a file named # AndroidManifest.custom.xml in their android project directory in which case all bets are # off android_custom_manifest = os.path.join(self.project_dir, 'AndroidManifest.custom.xml') if os.path.exists(android_custom_manifest): android_manifest_to_read = android_custom_manifest print "[INFO] Detected custom ApplicationManifest.xml -- no Titanium version migration supported" # we need to write out the new manifest manifest_contents = open(android_manifest_to_read,'r').read() manifest_contents = manifest_contents.replace('<!-- TI_ACTIVITIES -->',"\n\n\t\t".join(activities)) manifest_contents = manifest_contents.replace('<!-- TI_PERMISSIONS -->',permissions_required_xml) manifest_contents = manifest_contents.replace('<uses-sdk android:minSdkVersion="3" />', '<uses-sdk android:minSdkVersion="%s" />' % android_sdk_version) # write out the new manifest amf = open(android_manifest,'w') amf.write(manifest_contents) amf.close() res_dir = os.path.join(self.project_dir, 'res') output = run.run([aapt, 'package', '-m', '-J', src_dir, '-M', android_manifest, '-S', res_dir, '-I', jar]) if output == None: sys.exit(1) success = re.findall(r'ERROR (.*)',output) if len(success) > 0: print "[ERROR] %s" % success[0] sys.exit(1) srclist = [] jarlist = [] for root, dirs, files in os.walk(os.path.join(self.project_dir,'src')): # Strip out directories we shouldn't traverse for name in ignoreDirs: if name in dirs: dirs.remove(name) if len(files) > 0: for f in files: if f in ignoreFiles : continue path = root + os.sep + f srclist.append(path) project_module_dir = os.path.join(self.top_dir,'modules','android') if os.path.exists(project_module_dir): for root, dirs, files in os.walk(project_module_dir): # Strip out directories we shouldn't traverse for name in ignoreDirs: if name in dirs: dirs.remove(name) if len(files) > 0: for f in files: path = root + os.sep + f ext = splitext(f)[-1] if ext in ('.java'): srclist.append(path) elif ext in ('.jar'): jarlist.append(path) classes_dir = os.path.join(self.project_dir, 'bin', 'classes') if not os.path.exists(classes_dir): os.makedirs(classes_dir) jarsigner = "jarsigner" javac = "javac" if platform.system() == "Windows": if os.environ.has_key("JAVA_HOME"): home_jarsigner = os.path.join(os.environ["JAVA_HOME"], "bin", "jarsigner.exe") home_javac = os.path.join(os.environ["JAVA_HOME"], "bin", "javac.exe") if os.path.exists(home_jarsigner): jarsigner = home_jarsigner if os.path.exists(home_javac): javac = home_javac else: found = False for path in os.environ['PATH'].split(os.pathsep): if os.path.exists(os.path.join(path, 'jarsigner.exe')) and os.path.exists(os.path.join(path, 'javac.exe')): jarsigner = os.path.join(path, 'jarsigner.exe') javac = os.path.join(path, 'javac.exe') found = True break if not found: print "[ERROR] Error locating JDK: set $JAVA_HOME or put javac and jarsigner on your $PATH" sys.exit(1) # see if the user has app data and if so, compile in the user data # such that it can be accessed automatically using Titanium.App.Properties.getString app_data_cfg = os.path.join(self.top_dir,"appdata.cfg") if os.path.exists(app_data_cfg): props = read_properties(open(app_data_cfg,"r")) module_data = '' for key in props.keys(): data = props[key] module_data+="properties.setString(\"%s\",\"%s\");\n " % (key,data) print("[DEBUG] detected user application data at = %s"% app_data_cfg) sys.stdout.flush() dtf = os.path.join(src_dir,"AppUserData.java") if os.path.exists(dtf): os.remove(dtf) ctf = open(dtf,"w") cf_template = open(os.path.join(template_dir,'templates','AppUserData.java'),'r').read() cf_template = cf_template.replace('__MODULE_BODY__',module_data) ctf.write(cf_template) ctf.close() srclist.append(dtf) classpath = jar + os.pathsep + tijar + os.pathsep.join(jarlist) javac_command = [javac, '-classpath', classpath, '-d', classes_dir, '-sourcepath', src_dir] javac_command += srclist print "[DEBUG] %s" % javac_command sys.stdout.flush() out = run.run(javac_command) classes_dex = os.path.join(self.project_dir, 'bin', 'classes.dex') if platform.system() == "Windows": run.run([dx, '--dex', '--output='+classes_dex, classes_dir, tijar]) else: run.run([dx, '-JXmx512M', '--dex', '--output='+classes_dex, classes_dir, tijar]) ap_ = os.path.join(self.project_dir, 'bin', 'app.ap_') run.run([aapt, 'package', '-f', '-M', 'AndroidManifest.xml', '-A', assets_dir, '-S', 'res', '-I', jar, '-I', tijar, '-F', ap_]) unsigned_apk = os.path.join(self.project_dir, 'bin', 'app-unsigned.apk') run.run([apkbuilder, unsigned_apk, '-u', '-z', ap_, '-f', classes_dex, '-rf', src_dir, '-rj', tijar]) if dist_dir: app_apk = os.path.join(dist_dir, project_name + '.apk') else: app_apk = os.path.join(self.project_dir, 'bin', 'app.apk') output = run.run([jarsigner, '-storepass', keystore_pass, '-keystore', keystore, '-signedjar', app_apk, unsigned_apk, keystore_alias]) success = re.findall(r'RuntimeException: (.*)',output) if len(success) > 0: print "[ERROR] %s " %success[0] sys.exit(1) # NOTE: we can't zipalign until we officially support 1.6+ # # attempt to zipalign -- this only exists in 1.6 and above so be nice # zipalign = os.path.join(self.tools_dir,'zipalign') # if platform.system() == "Windows": # zipalign+=".exe" # # if os.path.exists(zipalign): # #zipalign -v 4 source.apk destination.apk # run.run([zipalign, '-v', '4', app_apk, app_apk+'z']) # os.rename(app_apk+'z',app_apk) if dist_dir: sys.exit(0) out = subprocess.Popen([self.adb,'get-state'], stderr=subprocess.PIPE, stdout=subprocess.PIPE).communicate()[0] out = str(out).strip() # try a few times as sometimes it fails waiting on boot attempts = 0 launched = False launch_failed = False while attempts < 5: try: cmd = [self.adb] if install: self.wait_for_device('d') print "[INFO] Installing application on emulator" cmd += ['-d', 'install', '-r', app_apk] else: self.wait_for_device('e') print "[INFO] Installing application on device" cmd += ['-e', 'install', '-r', app_apk] if run.run(cmd)==None: launch_failed = True elif not install: launched = True break except: time.sleep(3) attempts+=1 if launched: print "[INFO] Launching application ... %s" % self.name sys.stdout.flush() run.run([self.adb, '-e' , 'shell', 'am', 'start', '-a', 'android.intent.action.MAIN', '-c','android.intent.category.LAUNCHER', '-n', '%s/.%sActivity' % (self.app_id , self.classname)]) print "[INFO] Deployed %s ... Application should be running." % self.name elif launch_failed==False: print "[INFO] Application installed. Launch from drawer on Home Screen" finally: os.chdir(curdir) sys.stdout.flush()