@@ -531,3 +531,117 @@ async def sync_firmware_entries(
531531
532532 await db .commit ()
533533 return {"success" : True , "imported" : imported }
534+
535+
536+ # ── Samsung System Image preparation ─────────────────────────────────────────
537+
538+ class SystemImagePrepRequest (BaseModel ):
539+ firmware_entry_id : int
540+ device_id : Optional [int ] = None # if set, start device with the prepared system image
541+
542+
543+ @router .post ("/prepare-system-image" )
544+ async def prepare_samsung_system_image (
545+ req : SystemImagePrepRequest ,
546+ db : AsyncSession = Depends (get_db ),
547+ _user : User = Depends (get_current_user ),
548+ ) -> Dict [str , Any ]:
549+ """
550+ Extract Samsung One UI system.img from AP firmware and prepare it for AVD use.
551+
552+ Pipeline:
553+ AP_*.tar.md5 → super.img → system.img (+ vendor.img)
554+ OR: extract Samsung APKs directly for sideloading.
555+
556+ Returns paths to prepared images and list of extractable Samsung APKs.
557+ If device_id is provided, stores the system image path on the device record
558+ so the next start uses it via -system flag.
559+ """
560+ from core .firmware .system_image_prep import SamsungSystemImagePrep
561+
562+ entry_row = await db .execute (select (FirmwareEntry ).where (FirmwareEntry .id == req .firmware_entry_id ))
563+ entry = entry_row .scalar_one_or_none ()
564+ if not entry :
565+ raise HTTPException (404 , "Firmware entry not found" )
566+
567+ fw_dir = Path (settings .FIRMWARE_PACKAGES_DIR )
568+ if entry .filename :
569+ tar_path = fw_dir / entry .filename
570+ firmware_dir = tar_path .parent if tar_path .exists () else fw_dir
571+ else :
572+ firmware_dir = fw_dir
573+
574+ work_dir = firmware_dir / "_prep" / (entry .device_model or "unknown" )
575+ prep = SamsungSystemImagePrep (firmware_dir = firmware_dir , work_dir = work_dir )
576+
577+ result = await prep .prepare ()
578+
579+ # If APK-only (no simg2img/lpunpack), try lighter extraction
580+ if result ["mode" ] == "none" or not result ["apks" ]:
581+ apks = await prep .extract_apks_only ()
582+ if apks :
583+ result ["apks" ] = apks
584+ if result ["mode" ] == "none" :
585+ result ["mode" ] = "apk_sideload"
586+
587+ return {
588+ "firmware_entry_id" : req .firmware_entry_id ,
589+ "device_model" : entry .device_model ,
590+ "mode" : result ["mode" ],
591+ "system_img" : result ["system_img" ],
592+ "vendor_img" : result ["vendor_img" ],
593+ "boot_img" : result ["boot_img" ],
594+ "apks" : [
595+ {"name" : a ["name" ], "package" : a ["package" ], "path" : a ["path" ]}
596+ for a in result ["apks" ]
597+ ],
598+ "apks_count" : len (result ["apks" ]),
599+ "error" : result ["error" ],
600+ "tools_available" : {
601+ "simg2img" : bool (__import__ ("shutil" ).which ("simg2img" )),
602+ "lpunpack" : bool (__import__ ("shutil" ).which ("lpunpack" )),
603+ "lz4" : bool (__import__ ("shutil" ).which ("lz4" )),
604+ "debugfs" : bool (__import__ ("shutil" ).which ("debugfs" )),
605+ },
606+ }
607+
608+
609+ @router .post ("/install-samsung-apks/{device_id}" )
610+ async def install_samsung_apks_on_device (
611+ device_id : int ,
612+ apk_paths : List [str ],
613+ db : AsyncSession = Depends (get_db ),
614+ _user : User = Depends (get_current_user ),
615+ ) -> Dict [str , Any ]:
616+ """
617+ Sideload extracted Samsung APKs onto a running device.
618+ apk_paths: list of absolute paths returned by prepare-system-image.
619+ """
620+ from db .models import Device
621+ from core .tools .adb import ADBTool
622+
623+ device_row = await db .execute (select (Device ).where (Device .id == device_id ))
624+ device = device_row .scalar_one_or_none ()
625+ if not device :
626+ raise HTTPException (404 , "Device not found" )
627+ if not device .adb_serial :
628+ raise HTTPException (400 , "Device is not running (no adb_serial)" )
629+
630+ adb = ADBTool ()
631+ installed , failed , skipped = [], [], []
632+
633+ for apk_path in apk_paths :
634+ p = Path (apk_path )
635+ if not p .exists ():
636+ skipped .append (f"{ apk_path } : file not found" )
637+ continue
638+ try :
639+ out = await adb ._run (["-s" , device .adb_serial , "install" , "-r" , "-t" , str (p )], check = False )
640+ if "Success" in (out or "" ):
641+ installed .append (p .name )
642+ else :
643+ failed .append (f"{ p .name } : { (out or '' )[:100 ]} " )
644+ except Exception as e :
645+ failed .append (f"{ p .name } : { e } " )
646+
647+ return {"installed" : installed , "failed" : failed , "skipped" : skipped }
0 commit comments