#!/usr/bin/env python3 """ ZenTap Android Emulator Launcher Starts the ZenTap app in an Android emulator with selectable resolution and options. """ import os import sys import subprocess import time import argparse from pathlib import Path # Common resolutions and their names RESOLUTIONS = { '1': {'name': 'Phone Small (360x640)', 'width': 360, 'height': 640, 'density': 160}, '2': {'name': 'Phone Medium (411x731)', 'width': 411, 'height': 731, 'density': 160}, '3': {'name': 'Phone Large (414x896)', 'width': 414, 'height': 896, 'density': 160}, '4': {'name': 'Tablet 7" (600x960)', 'width': 600, 'height': 960, 'density': 160}, '5': {'name': 'Tablet 10" (800x1280)', 'width': 800, 'height': 1280, 'density': 160}, '6': {'name': 'Foldable (673x841)', 'width': 673, 'height': 841, 'density': 160}, '7': {'name': 'Desktop (1920x1080)', 'width': 1920, 'height': 1080, 'density': 160}, '8': {'name': 'Custom (specify)', 'width': None, 'height': None, 'density': 160} } class ZenTapEmulatorLauncher: def __init__(self): self.project_root = Path(__file__).parent.parent self.android_sdk_root = self.find_android_sdk() self.emulator_path = self.find_emulator_path() def find_android_sdk(self): """Find Android SDK root directory""" possible_paths = [ os.environ.get('ANDROID_SDK_ROOT'), os.environ.get('ANDROID_HOME'), Path.home() / 'Android' / 'Sdk', Path.home() / 'Library' / 'Android' / 'sdk', # macOS Path('/opt/android-sdk'), Path('/usr/local/android-sdk') ] for path in possible_paths: if path and Path(path).exists(): return Path(path) return None def find_emulator_path(self): """Find emulator executable""" if self.android_sdk_root: emulator_path = self.android_sdk_root / 'emulator' / 'emulator' if emulator_path.exists(): return emulator_path # Try to find in PATH try: result = subprocess.run(['which', 'emulator'], capture_output=True, text=True) if result.returncode == 0: return Path(result.stdout.strip()) except: pass return None def get_available_avds(self): """Get list of available Android Virtual Devices""" if not self.emulator_path: return [] try: result = subprocess.run([str(self.emulator_path), '-list-avds'], capture_output=True, text=True) if result.returncode == 0: return [line.strip() for line in result.stdout.strip().split('\n') if line.strip()] except: pass return [] def create_avd_if_needed(self, avd_name, resolution_info): """Create a new AVD if it doesn't exist""" avds = self.get_available_avds() if avd_name in avds: return True print(f"Creating new AVD: {avd_name}") # Use avdmanager to create AVD if self.android_sdk_root: avdmanager_path = self.android_sdk_root / 'cmdline-tools' / 'latest' / 'bin' / 'avdmanager' if not avdmanager_path.exists(): avdmanager_path = self.android_sdk_root / 'tools' / 'bin' / 'avdmanager' if avdmanager_path.exists(): try: # Create AVD with specified resolution cmd = [ str(avdmanager_path), 'create', 'avd', '-n', avd_name, '-k', 'system-images;android-34;google_apis;x86_64', '-d', 'pixel', '--force' ] result = subprocess.run(cmd, input='no\n', text=True, capture_output=True) return result.returncode == 0 except Exception as e: print(f"Failed to create AVD: {e}") return False def wait_for_emulator(self, max_wait=120): """Wait for emulator to boot completely""" print("Waiting for emulator to boot...") start_time = time.time() while time.time() - start_time < max_wait: try: result = subprocess.run(['adb', 'shell', 'getprop', 'sys.boot_completed'], capture_output=True, text=True, timeout=5) if result.returncode == 0 and result.stdout.strip() == '1': print("Emulator is ready!") return True except: pass print(".", end="", flush=True) time.sleep(2) print("\nTimeout waiting for emulator to boot") return False def launch_emulator(self, avd_name, resolution_info, no_window=False, cold_boot=False): """Launch the emulator with specified options""" if not self.emulator_path: print("Error: Emulator not found!") return False cmd = [str(self.emulator_path), '-avd', avd_name] # Add resolution parameters if specified if resolution_info['width'] and resolution_info['height']: cmd.extend(['-skin', f"{resolution_info['width']}x{resolution_info['height']}"]) # Add optional parameters if no_window: cmd.append('-no-window') if cold_boot: cmd.append('-wipe-data') # Performance optimizations cmd.extend([ '-gpu', 'host', '-no-boot-anim', '-memory', '2048' ]) print(f"Starting emulator with command: {' '.join(cmd)}") try: # Start emulator in background process = subprocess.Popen(cmd) time.sleep(5) # Give emulator time to start if process.poll() is None: # Process is still running return True else: print("Emulator failed to start") return False except Exception as e: print(f"Failed to start emulator: {e}") return False def build_and_install_app(self, build_mode='debug'): """Build and install the ZenTap app""" print(f"Building app in {build_mode} mode...") # Change to project directory os.chdir(self.project_root) # Build the app build_cmd = ['flutter', 'build', 'apk'] if build_mode == 'debug': build_cmd.append('--debug') elif build_mode == 'release': build_cmd.append('--release') try: result = subprocess.run(build_cmd, check=True) print("Build successful!") except subprocess.CalledProcessError: print("Build failed!") return False # Install the app print("Installing app on emulator...") try: if build_mode == 'debug': apk_path = 'build/app/outputs/flutter-apk/app-debug.apk' else: apk_path = 'build/app/outputs/flutter-apk/app-release.apk' subprocess.run(['adb', 'install', '-r', apk_path], check=True) print("App installed successfully!") return True except subprocess.CalledProcessError: print("Installation failed!") return False def launch_app(self): """Launch the ZenTap app""" print("Launching ZenTap...") try: subprocess.run([ 'adb', 'shell', 'am', 'start', '-n', 'hu.fsociety.zentap/hu.fsociety.zentap.MainActivity' ], check=True) print("App launched successfully!") return True except subprocess.CalledProcessError: print("Failed to launch app!") return False def interactive_mode(self): """Run in interactive mode with menu selection""" print("=== ZenTap Android Emulator Launcher ===\n") # Check prerequisites if not self.emulator_path: print("Error: Android emulator not found!") print("Please install Android SDK and set ANDROID_SDK_ROOT environment variable.") return False # Show available AVDs avds = self.get_available_avds() print("Available Android Virtual Devices:") if avds: for i, avd in enumerate(avds, 1): print(f" {i}. {avd}") else: print(" No AVDs found. Will create a new one.") print("\nSelect resolution:") for key, info in RESOLUTIONS.items(): print(f" {key}. {info['name']}") # Get user input resolution_choice = input(f"\nChoose resolution (1-{len(RESOLUTIONS)}): ").strip() if resolution_choice not in RESOLUTIONS: print("Invalid choice!") return False resolution_info = RESOLUTIONS[resolution_choice] # Handle custom resolution if resolution_choice == '8': try: width = int(input("Enter width: ")) height = int(input("Enter height: ")) resolution_info['width'] = width resolution_info['height'] = height except ValueError: print("Invalid resolution values!") return False # Select or create AVD avd_name = f"ZenTap_{resolution_info['width']}x{resolution_info['height']}" # Additional options print("\nAdditional options:") cold_boot = input("Cold boot (wipe data)? (y/N): ").lower().startswith('y') build_mode = input("Build mode (debug/release) [debug]: ").strip() or 'debug' return self.run_full_cycle(avd_name, resolution_info, cold_boot, build_mode) def run_full_cycle(self, avd_name, resolution_info, cold_boot=False, build_mode='debug'): """Run the complete cycle: create AVD, launch emulator, build, install, and run app""" # Create AVD if needed if avd_name not in self.get_available_avds(): if not self.create_avd_if_needed(avd_name, resolution_info): print("Failed to create AVD. Using existing AVD if available.") avds = self.get_available_avds() if avds: avd_name = avds[0] print(f"Using existing AVD: {avd_name}") else: print("No AVDs available!") return False # Launch emulator if not self.launch_emulator(avd_name, resolution_info, cold_boot=cold_boot): return False # Wait for emulator to boot if not self.wait_for_emulator(): return False # Build and install app if not self.build_and_install_app(build_mode): return False # Launch app if not self.launch_app(): return False print("\n=== ZenTap is now running in the emulator! ===") print("Press Ctrl+C to stop the emulator when done.") try: # Keep script running while True: time.sleep(1) except KeyboardInterrupt: print("\nStopping emulator...") subprocess.run(['adb', 'emu', 'kill'], capture_output=True) print("Emulator stopped.") return True def main(): parser = argparse.ArgumentParser(description='Launch ZenTap in Android emulator') parser.add_argument('--resolution', '-r', choices=list(RESOLUTIONS.keys()), help='Resolution preset') parser.add_argument('--width', '-w', type=int, help='Custom width') parser.add_argument('--height', type=int, help='Custom height') parser.add_argument('--avd', '-a', help='Specific AVD name to use') parser.add_argument('--cold-boot', '-c', action='store_true', help='Cold boot (wipe data)') parser.add_argument('--build-mode', '-b', choices=['debug', 'release'], default='debug', help='Build mode') parser.add_argument('--list-avds', '-l', action='store_true', help='List available AVDs') args = parser.parse_args() launcher = ZenTapEmulatorLauncher() if args.list_avds: avds = launcher.get_available_avds() print("Available AVDs:") for avd in avds: print(f" - {avd}") return if args.resolution or (args.width and args.height): # Command line mode if args.resolution: resolution_info = RESOLUTIONS[args.resolution].copy() else: resolution_info = {'width': args.width, 'height': args.height, 'density': 160} avd_name = args.avd or f"ZenTap_{resolution_info['width']}x{resolution_info['height']}" launcher.run_full_cycle(avd_name, resolution_info, args.cold_boot, args.build_mode) else: # Interactive mode launcher.interactive_mode() if __name__ == '__main__': main()