run_emulator.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. #!/usr/bin/env python3
  2. """
  3. ZenTap Android Emulator Launcher
  4. Starts the ZenTap app in an Android emulator with selectable resolution and options.
  5. """
  6. import os
  7. import sys
  8. import subprocess
  9. import time
  10. import argparse
  11. from pathlib import Path
  12. # Common resolutions and their names
  13. RESOLUTIONS = {
  14. '1': {'name': 'Phone Small (360x640)', 'width': 360, 'height': 640, 'density': 160},
  15. '2': {'name': 'Phone Medium (411x731)', 'width': 411, 'height': 731, 'density': 160},
  16. '3': {'name': 'Phone Large (414x896)', 'width': 414, 'height': 896, 'density': 160},
  17. '4': {'name': 'Tablet 7" (600x960)', 'width': 600, 'height': 960, 'density': 160},
  18. '5': {'name': 'Tablet 10" (800x1280)', 'width': 800, 'height': 1280, 'density': 160},
  19. '6': {'name': 'Foldable (673x841)', 'width': 673, 'height': 841, 'density': 160},
  20. '7': {'name': 'Desktop (1920x1080)', 'width': 1920, 'height': 1080, 'density': 160},
  21. '8': {'name': 'Custom (specify)', 'width': None, 'height': None, 'density': 160}
  22. }
  23. class ZenTapEmulatorLauncher:
  24. def __init__(self):
  25. self.project_root = Path(__file__).parent.parent
  26. self.android_sdk_root = self.find_android_sdk()
  27. self.emulator_path = self.find_emulator_path()
  28. def find_android_sdk(self):
  29. """Find Android SDK root directory"""
  30. possible_paths = [
  31. os.environ.get('ANDROID_SDK_ROOT'),
  32. os.environ.get('ANDROID_HOME'),
  33. Path.home() / 'Android' / 'Sdk',
  34. Path.home() / 'Library' / 'Android' / 'sdk', # macOS
  35. Path('/opt/android-sdk'),
  36. Path('/usr/local/android-sdk')
  37. ]
  38. for path in possible_paths:
  39. if path and Path(path).exists():
  40. return Path(path)
  41. return None
  42. def find_emulator_path(self):
  43. """Find emulator executable"""
  44. if self.android_sdk_root:
  45. emulator_path = self.android_sdk_root / 'emulator' / 'emulator'
  46. if emulator_path.exists():
  47. return emulator_path
  48. # Try to find in PATH
  49. try:
  50. result = subprocess.run(['which', 'emulator'], capture_output=True, text=True)
  51. if result.returncode == 0:
  52. return Path(result.stdout.strip())
  53. except:
  54. pass
  55. return None
  56. def get_available_avds(self):
  57. """Get list of available Android Virtual Devices"""
  58. if not self.emulator_path:
  59. return []
  60. try:
  61. result = subprocess.run([str(self.emulator_path), '-list-avds'],
  62. capture_output=True, text=True)
  63. if result.returncode == 0:
  64. return [line.strip() for line in result.stdout.strip().split('\n') if line.strip()]
  65. except:
  66. pass
  67. return []
  68. def create_avd_if_needed(self, avd_name, resolution_info):
  69. """Create a new AVD if it doesn't exist"""
  70. avds = self.get_available_avds()
  71. if avd_name in avds:
  72. return True
  73. print(f"Creating new AVD: {avd_name}")
  74. # Use avdmanager to create AVD
  75. if self.android_sdk_root:
  76. avdmanager_path = self.android_sdk_root / 'cmdline-tools' / 'latest' / 'bin' / 'avdmanager'
  77. if not avdmanager_path.exists():
  78. avdmanager_path = self.android_sdk_root / 'tools' / 'bin' / 'avdmanager'
  79. if avdmanager_path.exists():
  80. try:
  81. # Create AVD with specified resolution
  82. cmd = [
  83. str(avdmanager_path), 'create', 'avd',
  84. '-n', avd_name,
  85. '-k', 'system-images;android-34;google_apis;x86_64',
  86. '-d', 'pixel',
  87. '--force'
  88. ]
  89. result = subprocess.run(cmd, input='no\n', text=True, capture_output=True)
  90. return result.returncode == 0
  91. except Exception as e:
  92. print(f"Failed to create AVD: {e}")
  93. return False
  94. def wait_for_emulator(self, max_wait=120):
  95. """Wait for emulator to boot completely"""
  96. print("Waiting for emulator to boot...")
  97. start_time = time.time()
  98. while time.time() - start_time < max_wait:
  99. try:
  100. result = subprocess.run(['adb', 'shell', 'getprop', 'sys.boot_completed'],
  101. capture_output=True, text=True, timeout=5)
  102. if result.returncode == 0 and result.stdout.strip() == '1':
  103. print("Emulator is ready!")
  104. return True
  105. except:
  106. pass
  107. print(".", end="", flush=True)
  108. time.sleep(2)
  109. print("\nTimeout waiting for emulator to boot")
  110. return False
  111. def launch_emulator(self, avd_name, resolution_info, no_window=False, cold_boot=False):
  112. """Launch the emulator with specified options"""
  113. if not self.emulator_path:
  114. print("Error: Emulator not found!")
  115. return False
  116. cmd = [str(self.emulator_path), '-avd', avd_name]
  117. # Add resolution parameters if specified
  118. if resolution_info['width'] and resolution_info['height']:
  119. cmd.extend(['-skin', f"{resolution_info['width']}x{resolution_info['height']}"])
  120. # Add optional parameters
  121. if no_window:
  122. cmd.append('-no-window')
  123. if cold_boot:
  124. cmd.append('-wipe-data')
  125. # Performance optimizations
  126. cmd.extend([
  127. '-gpu', 'host',
  128. '-no-boot-anim',
  129. '-memory', '2048'
  130. ])
  131. print(f"Starting emulator with command: {' '.join(cmd)}")
  132. try:
  133. # Start emulator in background
  134. process = subprocess.Popen(cmd)
  135. time.sleep(5) # Give emulator time to start
  136. if process.poll() is None: # Process is still running
  137. return True
  138. else:
  139. print("Emulator failed to start")
  140. return False
  141. except Exception as e:
  142. print(f"Failed to start emulator: {e}")
  143. return False
  144. def build_and_install_app(self, build_mode='debug'):
  145. """Build and install the ZenTap app"""
  146. print(f"Building app in {build_mode} mode...")
  147. # Change to project directory
  148. os.chdir(self.project_root)
  149. # Build the app
  150. build_cmd = ['flutter', 'build', 'apk']
  151. if build_mode == 'debug':
  152. build_cmd.append('--debug')
  153. elif build_mode == 'release':
  154. build_cmd.append('--release')
  155. try:
  156. result = subprocess.run(build_cmd, check=True)
  157. print("Build successful!")
  158. except subprocess.CalledProcessError:
  159. print("Build failed!")
  160. return False
  161. # Install the app
  162. print("Installing app on emulator...")
  163. try:
  164. if build_mode == 'debug':
  165. apk_path = 'build/app/outputs/flutter-apk/app-debug.apk'
  166. else:
  167. apk_path = 'build/app/outputs/flutter-apk/app-release.apk'
  168. subprocess.run(['adb', 'install', '-r', apk_path], check=True)
  169. print("App installed successfully!")
  170. return True
  171. except subprocess.CalledProcessError:
  172. print("Installation failed!")
  173. return False
  174. def launch_app(self):
  175. """Launch the ZenTap app"""
  176. print("Launching ZenTap...")
  177. try:
  178. subprocess.run([
  179. 'adb', 'shell', 'am', 'start',
  180. '-n', 'hu.fsociety.zentap/hu.fsociety.zentap.MainActivity'
  181. ], check=True)
  182. print("App launched successfully!")
  183. return True
  184. except subprocess.CalledProcessError:
  185. print("Failed to launch app!")
  186. return False
  187. def interactive_mode(self):
  188. """Run in interactive mode with menu selection"""
  189. print("=== ZenTap Android Emulator Launcher ===\n")
  190. # Check prerequisites
  191. if not self.emulator_path:
  192. print("Error: Android emulator not found!")
  193. print("Please install Android SDK and set ANDROID_SDK_ROOT environment variable.")
  194. return False
  195. # Show available AVDs
  196. avds = self.get_available_avds()
  197. print("Available Android Virtual Devices:")
  198. if avds:
  199. for i, avd in enumerate(avds, 1):
  200. print(f" {i}. {avd}")
  201. else:
  202. print(" No AVDs found. Will create a new one.")
  203. print("\nSelect resolution:")
  204. for key, info in RESOLUTIONS.items():
  205. print(f" {key}. {info['name']}")
  206. # Get user input
  207. resolution_choice = input(f"\nChoose resolution (1-{len(RESOLUTIONS)}): ").strip()
  208. if resolution_choice not in RESOLUTIONS:
  209. print("Invalid choice!")
  210. return False
  211. resolution_info = RESOLUTIONS[resolution_choice]
  212. # Handle custom resolution
  213. if resolution_choice == '8':
  214. try:
  215. width = int(input("Enter width: "))
  216. height = int(input("Enter height: "))
  217. resolution_info['width'] = width
  218. resolution_info['height'] = height
  219. except ValueError:
  220. print("Invalid resolution values!")
  221. return False
  222. # Select or create AVD
  223. avd_name = f"ZenTap_{resolution_info['width']}x{resolution_info['height']}"
  224. # Additional options
  225. print("\nAdditional options:")
  226. cold_boot = input("Cold boot (wipe data)? (y/N): ").lower().startswith('y')
  227. build_mode = input("Build mode (debug/release) [debug]: ").strip() or 'debug'
  228. return self.run_full_cycle(avd_name, resolution_info, cold_boot, build_mode)
  229. def run_full_cycle(self, avd_name, resolution_info, cold_boot=False, build_mode='debug'):
  230. """Run the complete cycle: create AVD, launch emulator, build, install, and run app"""
  231. # Create AVD if needed
  232. if avd_name not in self.get_available_avds():
  233. if not self.create_avd_if_needed(avd_name, resolution_info):
  234. print("Failed to create AVD. Using existing AVD if available.")
  235. avds = self.get_available_avds()
  236. if avds:
  237. avd_name = avds[0]
  238. print(f"Using existing AVD: {avd_name}")
  239. else:
  240. print("No AVDs available!")
  241. return False
  242. # Launch emulator
  243. if not self.launch_emulator(avd_name, resolution_info, cold_boot=cold_boot):
  244. return False
  245. # Wait for emulator to boot
  246. if not self.wait_for_emulator():
  247. return False
  248. # Build and install app
  249. if not self.build_and_install_app(build_mode):
  250. return False
  251. # Launch app
  252. if not self.launch_app():
  253. return False
  254. print("\n=== ZenTap is now running in the emulator! ===")
  255. print("Press Ctrl+C to stop the emulator when done.")
  256. try:
  257. # Keep script running
  258. while True:
  259. time.sleep(1)
  260. except KeyboardInterrupt:
  261. print("\nStopping emulator...")
  262. subprocess.run(['adb', 'emu', 'kill'], capture_output=True)
  263. print("Emulator stopped.")
  264. return True
  265. def main():
  266. parser = argparse.ArgumentParser(description='Launch ZenTap in Android emulator')
  267. parser.add_argument('--resolution', '-r', choices=list(RESOLUTIONS.keys()),
  268. help='Resolution preset')
  269. parser.add_argument('--width', '-w', type=int, help='Custom width')
  270. parser.add_argument('--height', type=int, help='Custom height')
  271. parser.add_argument('--avd', '-a', help='Specific AVD name to use')
  272. parser.add_argument('--cold-boot', '-c', action='store_true', help='Cold boot (wipe data)')
  273. parser.add_argument('--build-mode', '-b', choices=['debug', 'release'], default='debug',
  274. help='Build mode')
  275. parser.add_argument('--list-avds', '-l', action='store_true', help='List available AVDs')
  276. args = parser.parse_args()
  277. launcher = ZenTapEmulatorLauncher()
  278. if args.list_avds:
  279. avds = launcher.get_available_avds()
  280. print("Available AVDs:")
  281. for avd in avds:
  282. print(f" - {avd}")
  283. return
  284. if args.resolution or (args.width and args.height):
  285. # Command line mode
  286. if args.resolution:
  287. resolution_info = RESOLUTIONS[args.resolution].copy()
  288. else:
  289. resolution_info = {'width': args.width, 'height': args.height, 'density': 160}
  290. avd_name = args.avd or f"ZenTap_{resolution_info['width']}x{resolution_info['height']}"
  291. launcher.run_full_cycle(avd_name, resolution_info, args.cold_boot, args.build_mode)
  292. else:
  293. # Interactive mode
  294. launcher.interactive_mode()
  295. if __name__ == '__main__':
  296. main()