mirror of
https://github.com/waydroid/waydroid.git
synced 2026-04-21 15:37:05 -04:00
460 lines
18 KiB
Python
460 lines
18 KiB
Python
# Copyright 2021 Erfan Abdi
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
import logging
|
|
import os
|
|
from tools import helpers
|
|
import tools.config
|
|
import stat
|
|
import sys
|
|
import threading
|
|
import multiprocessing
|
|
import select
|
|
import queue
|
|
import time
|
|
import dbus
|
|
import dbus.service
|
|
import argparse
|
|
from gi.repository import GLib
|
|
|
|
def is_initialized(args):
|
|
return os.path.isfile(args.config) and os.path.isdir(tools.config.defaults["rootfs"])
|
|
|
|
def get_vendor_type(args):
|
|
vndk_str = helpers.props.host_get(args, "ro.vndk.version")
|
|
ret = "MAINLINE"
|
|
if vndk_str != "":
|
|
vndk = int(vndk_str)
|
|
if vndk > 19:
|
|
halium_ver = vndk - 19
|
|
if vndk > 31:
|
|
halium_ver -= 1 # 12L -> Halium 12
|
|
ret = "HALIUM_" + str(halium_ver)
|
|
if vndk == 32:
|
|
ret += "L"
|
|
|
|
return ret
|
|
|
|
def setup_config(args):
|
|
cfg = tools.config.load(args)
|
|
args.arch = helpers.arch.host()
|
|
cfg["waydroid"]["arch"] = args.arch
|
|
|
|
args.vendor_type = get_vendor_type(args)
|
|
cfg["waydroid"]["vendor_type"] = args.vendor_type
|
|
|
|
helpers.drivers.setupBinderNodes(args)
|
|
cfg["waydroid"]["binder"] = args.BINDER_DRIVER
|
|
cfg["waydroid"]["vndbinder"] = args.VNDBINDER_DRIVER
|
|
cfg["waydroid"]["hwbinder"] = args.HWBINDER_DRIVER
|
|
|
|
has_preinstalled_images = False
|
|
preinstalled_images_paths = tools.config.defaults["preinstalled_images_paths"]
|
|
for preinstalled_images in preinstalled_images_paths:
|
|
if os.path.isdir(preinstalled_images):
|
|
system_path = preinstalled_images + "/system.img"
|
|
vendor_path = preinstalled_images + "/vendor.img"
|
|
system_exists = os.path.isfile(system_path) or stat.S_ISBLK(os.stat(system_path).st_mode)
|
|
vendor_exists = os.path.isfile(vendor_path) or stat.S_ISBLK(os.stat(vendor_path).st_mode)
|
|
if system_exists and vendor_exists:
|
|
has_preinstalled_images = True
|
|
args.images_path = preinstalled_images
|
|
break
|
|
else:
|
|
logging.warning("Found directory {} but missing system or vendor image, ignoring...".format(preinstalled_images))
|
|
|
|
if not args.images_path:
|
|
args.images_path = tools.config.defaults["images_path"]
|
|
cfg["waydroid"]["images_path"] = args.images_path
|
|
|
|
if has_preinstalled_images:
|
|
cfg["waydroid"]["system_ota"] = args.system_ota = "None"
|
|
cfg["waydroid"]["vendor_ota"] = args.vendor_ota = "None"
|
|
cfg["waydroid"]["system_datetime"] = tools.config.defaults["system_datetime"]
|
|
cfg["waydroid"]["vendor_datetime"] = tools.config.defaults["vendor_datetime"]
|
|
tools.config.save(args, cfg)
|
|
return True
|
|
|
|
channels_cfg = tools.config.load_channels()
|
|
if not args.system_channel:
|
|
args.system_channel = channels_cfg["channels"]["system_channel"]
|
|
if not args.vendor_channel:
|
|
args.vendor_channel = channels_cfg["channels"]["vendor_channel"]
|
|
if not args.rom_type:
|
|
args.rom_type = channels_cfg["channels"]["rom_type"]
|
|
if not args.system_type:
|
|
args.system_type = channels_cfg["channels"]["system_type"]
|
|
|
|
if not args.system_channel or not args.vendor_channel:
|
|
logging.error("ERROR: You must provide 'System OTA' and 'Vendor OTA' URLs.")
|
|
return False
|
|
|
|
args.system_ota = args.system_channel + "/" + args.rom_type + \
|
|
"/waydroid_" + args.arch + "/" + args.system_type + ".json"
|
|
system_request = helpers.http.retrieve(args.system_ota)
|
|
if system_request[0] != 200:
|
|
raise ValueError(
|
|
"Failed to get system OTA channel: {}, error: {}".format(args.system_ota, system_request[0]))
|
|
|
|
device_codename = helpers.props.host_get(args, "ro.product.device")
|
|
args.vendor_type = None
|
|
for vendor in [device_codename, get_vendor_type(args)]:
|
|
vendor_ota = args.vendor_channel + "/waydroid_" + \
|
|
args.arch + "/" + vendor.replace(" ", "_") + ".json"
|
|
vendor_request = helpers.http.retrieve(vendor_ota)
|
|
if vendor_request[0] == 200:
|
|
args.vendor_type = vendor
|
|
args.vendor_ota = vendor_ota
|
|
break
|
|
|
|
if not args.vendor_type:
|
|
raise ValueError(
|
|
"Failed to get vendor OTA channel: {}".format(vendor_ota))
|
|
|
|
if args.system_ota != cfg["waydroid"].get("system_ota"):
|
|
cfg["waydroid"]["system_datetime"] = tools.config.defaults["system_datetime"]
|
|
if args.vendor_ota != cfg["waydroid"].get("vendor_ota"):
|
|
cfg["waydroid"]["vendor_datetime"] = tools.config.defaults["vendor_datetime"]
|
|
|
|
cfg["waydroid"]["vendor_type"] = args.vendor_type
|
|
cfg["waydroid"]["system_ota"] = args.system_ota
|
|
cfg["waydroid"]["vendor_ota"] = args.vendor_ota
|
|
tools.config.save(args, cfg)
|
|
return True
|
|
|
|
def init(args):
|
|
if is_initialized(args) and not args.force:
|
|
logging.info("Already initialized")
|
|
|
|
if not setup_config(args):
|
|
return
|
|
|
|
status = "STOPPED"
|
|
session = None
|
|
if os.path.exists(tools.config.defaults["lxc"] + "/waydroid"):
|
|
status = helpers.lxc.status(args)
|
|
if status != "STOPPED":
|
|
if "running_init_in_service" in args:
|
|
session = args.session
|
|
tools.actions.container_manager.stop(args, False)
|
|
else:
|
|
logging.info("Stopping container")
|
|
try:
|
|
container = tools.helpers.ipc.DBusContainerService()
|
|
session = container.GetSession()
|
|
container.Stop(False)
|
|
except Exception as e:
|
|
logging.debug(e)
|
|
tools.actions.container_manager.stop(args, False)
|
|
if args.images_path not in tools.config.defaults["preinstalled_images_paths"]:
|
|
helpers.images.get(args)
|
|
else:
|
|
helpers.images.remove_overlay(args)
|
|
if not os.path.isdir(tools.config.defaults["rootfs"]):
|
|
os.mkdir(tools.config.defaults["rootfs"])
|
|
if not os.path.isdir(tools.config.defaults["overlay"]):
|
|
os.mkdir(tools.config.defaults["overlay"])
|
|
os.mkdir(tools.config.defaults["overlay"]+"/vendor")
|
|
if not os.path.isdir(tools.config.defaults["overlay_rw"]):
|
|
os.mkdir(tools.config.defaults["overlay_rw"])
|
|
os.mkdir(tools.config.defaults["overlay_rw"]+"/system")
|
|
os.mkdir(tools.config.defaults["overlay_rw"]+"/vendor")
|
|
helpers.drivers.probeAshmemDriver(args)
|
|
helpers.lxc.setup_host_perms(args)
|
|
helpers.lxc.set_lxc_config(args)
|
|
helpers.lxc.make_base_props(args)
|
|
if status != "STOPPED":
|
|
try:
|
|
if "running_init_in_service" in args:
|
|
tools.actions.container_manager.do_start(args, session)
|
|
else:
|
|
logging.info("Starting container")
|
|
container.Start(session)
|
|
except Exception as e:
|
|
logging.debug(e)
|
|
logging.error("Failed to restart container. Please do so manually.")
|
|
|
|
class DbusInitializer(dbus.service.Object):
|
|
def __init__(self, looper, bus, object_path, args):
|
|
self.args = args
|
|
self.looper = looper
|
|
self.worker_thread = None
|
|
dbus.service.Object.__init__(self, bus, object_path)
|
|
|
|
@helpers.logging.log_exceptions
|
|
@dbus.service.method("id.waydro.Initializer", in_signature='a{ss}', out_signature='', sender_keyword="sender", connection_keyword="conn")
|
|
def Init(self, params, sender=None, conn=None):
|
|
if self.worker_thread is not None:
|
|
self.worker_thread.kill()
|
|
self.worker_thread.join()
|
|
|
|
channels_cfg = tools.config.load_channels()
|
|
no_auth = params["system_channel"] == channels_cfg["channels"]["system_channel"] and \
|
|
params["vendor_channel"] == channels_cfg["channels"]["vendor_channel"]
|
|
if no_auth or ensure_polkit_auth(sender, conn, "id.waydro.Initializer.Init"):
|
|
self.worker_thread = remote_init_server(self.args, self, params)
|
|
else:
|
|
raise PermissionError("Polkit: Authentication failed")
|
|
|
|
@helpers.logging.log_exceptions
|
|
@dbus.service.method("id.waydro.Initializer", in_signature='', out_signature='')
|
|
def Cancel(self):
|
|
if self.worker_thread is not None:
|
|
self.worker_thread.kill()
|
|
self.worker_thread.join()
|
|
|
|
@dbus.service.signal("id.waydro.Initializer", signature='s')
|
|
def ProgressChanged(self, message):
|
|
pass
|
|
|
|
@dbus.service.signal("id.waydro.Initializer", signature='')
|
|
def Finished(self):
|
|
pass
|
|
|
|
@dbus.service.signal("id.waydro.Initializer", signature='')
|
|
def Interrupted(self):
|
|
pass
|
|
|
|
def ensure_polkit_auth(sender, conn, privilege):
|
|
dbus_info = dbus.Interface(conn.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus/Bus", False), "org.freedesktop.DBus")
|
|
pid = dbus_info.GetConnectionUnixProcessID(sender)
|
|
polkit = dbus.Interface(dbus.SystemBus().get_object("org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", False), "org.freedesktop.PolicyKit1.Authority")
|
|
try:
|
|
(is_auth, _, _) = polkit.CheckAuthorization(
|
|
("unix-process", {
|
|
"pid": dbus.UInt32(pid, variant_level=1),
|
|
"start-time": dbus.UInt64(0, variant_level=1)}),
|
|
privilege, {"AllowUserInteraction": "true"},
|
|
dbus.UInt32(1),
|
|
"",
|
|
timeout=300)
|
|
return is_auth
|
|
except dbus.DBusException:
|
|
raise PermissionError("Polkit: Authentication timed out")
|
|
|
|
class StdoutRedirect(logging.StreamHandler):
|
|
def __init__(self, pipe):
|
|
logging.StreamHandler.__init__(self)
|
|
self.pipe = pipe
|
|
def write(self, s):
|
|
self.pipe.send(s)
|
|
def flush(self):
|
|
pass
|
|
def emit(self, record):
|
|
if record.levelno >= logging.INFO:
|
|
self.write(self.format(record) + self.terminator)
|
|
|
|
def remote_init_proc_entry(args, pipe):
|
|
out = StdoutRedirect(pipe)
|
|
sys.stdout = sys.stderr = out
|
|
|
|
helpers.logging.add_verbose_log_level()
|
|
logging.getLogger().addHandler(out)
|
|
|
|
try:
|
|
init(args)
|
|
sys.exit(0)
|
|
except KeyboardInterrupt:
|
|
sys.exit(1)
|
|
except Exception:
|
|
logging.exception("Exception during init")
|
|
sys.exit(1)
|
|
finally:
|
|
pipe.close()
|
|
|
|
def remote_init_server(_args, dbus_obj, params):
|
|
args = argparse.Namespace()
|
|
tools.prep_args(args)
|
|
args.details_to_stdout = _args.details_to_stdout
|
|
args.verbose = _args.verbose
|
|
args.quiet = _args.quiet
|
|
args.force = True
|
|
args.images_path = ""
|
|
args.rom_type = ""
|
|
args.system_channel = params["system_channel"]
|
|
args.vendor_channel = params["vendor_channel"]
|
|
args.system_type = params["system_type"]
|
|
args.running_init_in_service = True
|
|
|
|
parent_conn, child_conn = multiprocessing.Pipe(False)
|
|
p = multiprocessing.Process(target=remote_init_proc_entry, args=(args, child_conn,), daemon=True)
|
|
p.start()
|
|
|
|
def monitor_init(p, pipe):
|
|
try:
|
|
while True:
|
|
dbus_obj.ProgressChanged(pipe.recv())
|
|
except EOFError:
|
|
pass
|
|
|
|
p.join()
|
|
if p.exitcode == 0:
|
|
GLib.idle_add(dbus_obj.Finished)
|
|
else:
|
|
GLib.idle_add(dbus_obj.Interrupted)
|
|
|
|
t = threading.Thread(target=monitor_init, args=(p, parent_conn,), daemon=True)
|
|
t.kill = lambda: p.kill()
|
|
t.start()
|
|
return t
|
|
|
|
|
|
def remote_init_client(args):
|
|
# Local imports cause Gtk is intrusive
|
|
import gi
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import Gtk
|
|
|
|
class WaydroidInitWindow(Gtk.Window):
|
|
def __init__(self):
|
|
super().__init__(title="Initialize Waydroid")
|
|
channels_cfg = tools.config.load_channels()
|
|
|
|
self.set_default_size(600, 250)
|
|
self.set_icon_name("waydroid")
|
|
|
|
grid = Gtk.Grid(row_spacing=6, column_spacing=6, margin=10, column_homogeneous=True)
|
|
grid.set_hexpand(True)
|
|
grid.set_vexpand(True)
|
|
self.add(grid)
|
|
|
|
sysOtaLabel = Gtk.Label("System OTA")
|
|
sysOtaEntry = Gtk.Entry()
|
|
sysOtaEntry.set_text(channels_cfg["channels"]["system_channel"])
|
|
grid.attach(sysOtaLabel, 0, 0, 1, 1)
|
|
grid.attach_next_to(sysOtaEntry ,sysOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
|
|
self.sysOta = sysOtaEntry.get_buffer()
|
|
|
|
vndOtaLabel = Gtk.Label("Vendor OTA")
|
|
vndOtaEntry = Gtk.Entry()
|
|
vndOtaEntry.set_text(channels_cfg["channels"]["vendor_channel"])
|
|
grid.attach(vndOtaLabel, 0, 1, 1, 1)
|
|
grid.attach_next_to(vndOtaEntry, vndOtaLabel, Gtk.PositionType.RIGHT, 2, 1)
|
|
self.vndOta = vndOtaEntry.get_buffer()
|
|
|
|
sysTypeLabel = Gtk.Label("Android Type")
|
|
sysTypesStore = Gtk.ListStore(str, str)
|
|
sysTypesStore.append(["VANILLA", "Minimal Android"])
|
|
sysTypesStore.append(["GAPPS", "Android with Google Apps"])
|
|
|
|
sysTypeCombo = Gtk.ComboBox.new_with_model(sysTypesStore)
|
|
renderer_text = Gtk.CellRendererText()
|
|
sysTypeCombo.pack_start(renderer_text, True)
|
|
sysTypeCombo.add_attribute(renderer_text, "text", 1)
|
|
sysTypeCombo.set_active(0)
|
|
grid.attach(sysTypeLabel, 0, 2, 1, 1)
|
|
grid.attach_next_to(sysTypeCombo, sysTypeLabel, Gtk.PositionType.RIGHT, 2, 1)
|
|
self.sysType = sysTypeCombo
|
|
|
|
downloadBtn = Gtk.Button("Download")
|
|
downloadBtn.connect("clicked", self.on_download_btn_clicked)
|
|
grid.attach(downloadBtn, 1,3,1,1)
|
|
self.downloadBtn = downloadBtn
|
|
|
|
doneBtn = Gtk.Button("Done")
|
|
doneBtn.connect("clicked", lambda x: self.destroy())
|
|
doneBtn.get_style_context().add_class('suggested-action')
|
|
grid.attach_next_to(doneBtn, downloadBtn, Gtk.PositionType.RIGHT, 1, 1)
|
|
self.doneBtn = doneBtn
|
|
|
|
outScrolledWindow = Gtk.ScrolledWindow()
|
|
outScrolledWindow.set_hexpand(True)
|
|
outScrolledWindow.set_vexpand(True)
|
|
outTextView = Gtk.TextView()
|
|
outTextView.set_property('editable', False)
|
|
outTextView.set_property('cursor-visible', False)
|
|
outScrolledWindow.add(outTextView)
|
|
grid.attach(outScrolledWindow, 0, 4, 3, 1)
|
|
self.outScrolledWindow = outScrolledWindow
|
|
self.outTextView = outTextView
|
|
self.outBuffer = outTextView.get_buffer()
|
|
self.outBuffer.create_mark("end", self.outBuffer.get_end_iter(), False)
|
|
|
|
self.bus_signals = []
|
|
self.initializing = False
|
|
|
|
self.connect("destroy", self.on_destroy)
|
|
|
|
def scroll_to_bottom(self):
|
|
self.outTextView.scroll_mark_onscreen(self.outBuffer.get_mark("end"))
|
|
|
|
def on_download_btn_clicked(self, widget):
|
|
widget.set_sensitive(False)
|
|
self.doneBtn.hide()
|
|
self.outTextView.show()
|
|
sysType = self.sysType.get_model()[self.sysType.get_active_iter()][0]
|
|
self.run_init(self.sysOta.get_text(), self.vndOta.get_text(), sysType)
|
|
|
|
def draw(self, s):
|
|
if s.startswith('\r'):
|
|
last = self.outBuffer.get_iter_at_line(self.outBuffer.get_line_count()-1)
|
|
last.backward_char()
|
|
self.outBuffer.delete(last, self.outBuffer.get_end_iter())
|
|
self.outBuffer.insert(self.outBuffer.get_end_iter(), s)
|
|
self.scroll_to_bottom()
|
|
|
|
def on_progress(self, message):
|
|
self.draw(message)
|
|
|
|
def on_finished(self):
|
|
self.initializing = False
|
|
if is_initialized(args):
|
|
self.doneBtn.show()
|
|
self.draw("\nDone\n")
|
|
|
|
def on_interrupted(self):
|
|
self.initializing = False
|
|
self.draw("\nInterrupted\n")
|
|
|
|
def on_reply(self):
|
|
self.downloadBtn.set_sensitive(True)
|
|
|
|
def on_bus_error(self, e):
|
|
if e.get_dbus_name() == "org.freedesktop.DBus.Python.PermissionError":
|
|
self.draw(e.get_dbus_message().splitlines()[-1] + "\n")
|
|
else:
|
|
self.draw(str(e))
|
|
self.downloadBtn.set_sensitive(True)
|
|
|
|
def on_destroy(self, _):
|
|
if self.initializing:
|
|
try:
|
|
tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer").Cancel()
|
|
except Exception as e:
|
|
logging.debug("Unexpected error while cancelling initializer: %s", e)
|
|
Gtk.main_quit()
|
|
|
|
def run_init(self, systemOta, vendorOta, systemType):
|
|
for signal in self.bus_signals:
|
|
signal.remove()
|
|
|
|
self.draw("\nWaiting for waydroid container service...\n")
|
|
self.bus_signals = []
|
|
self.initializing = True
|
|
|
|
try:
|
|
initializer = tools.helpers.ipc.DBusContainerService("/Initializer", "id.waydro.Initializer")
|
|
|
|
self.bus_signals.append(initializer.connect_to_signal("ProgressChanged", self.on_progress))
|
|
self.bus_signals.append(initializer.connect_to_signal("Finished", self.on_finished))
|
|
self.bus_signals.append(initializer.connect_to_signal("Interrupted", self.on_interrupted))
|
|
|
|
params = {
|
|
"system_channel": systemOta,
|
|
"vendor_channel": vendorOta,
|
|
"system_type": systemType
|
|
}
|
|
initializer.Init(params, reply_handler=self.on_reply, error_handler=self.on_bus_error)
|
|
except dbus.DBusException as e:
|
|
self.on_bus_error(e)
|
|
except Exception as e:
|
|
self.draw(f"{e}\n")
|
|
|
|
GLib.set_prgname("Waydroid")
|
|
win = WaydroidInitWindow()
|
|
|
|
win.show_all()
|
|
win.outTextView.hide()
|
|
win.doneBtn.hide()
|
|
|
|
Gtk.main()
|