| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- #!/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()
|