def connect_device_usb() -> NoReturn:
    global device, device_is_connected, device_name, device_android_version, device_user
    if not os.path.exists(cfg.adb_key_file_path):
        keygen(cfg.adb_key_file_path)
        log_info(f"[ADB] generated and stored a new adb-RSA-key (was missing)",
                 logger="debuglog")

    with open(cfg.adb_key_file_path) as f:
        priv = f.read()
    with open(cfg.adb_key_file_path + '.pub') as f:
        pub = f.read()
    signer = PythonRSASigner(pub, priv)
    try:
        device = AdbDeviceUsb(
        )  # TODO: there can be more than one phone, determine with "available", "list" or similar
    except UsbDeviceNotFoundError:
        device = None
        log_error(
            f"[ADB] is the device connected and ADB activated on device?",
            logger="debuglog")
    except DevicePathInvalidError:
        device = None
        log_error(
            "[ADB] installation seems incomplete, adb-shell[usb] is missing (or not working as intended) or adb-server is still running on your system",
            logger="debuglog")
    if device is not None:
        device.connect(rsa_keys=[signer], auth_timeout_s=30)
    if not is_device_available():
        return
    device_is_connected = True
    log_info(f"[ADB] connected to USB-Device", logger="debuglog")
    update_device_properties()
Exemplo n.º 2
0
def setup_androidtv(hass, config):
    """Generate an ADB key (if needed) and connect to the Android TV / Fire TV."""
    adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey"))
    if CONF_ADB_SERVER_IP not in config:
        # Use "adb_shell" (Python ADB implementation)
        if not os.path.isfile(adbkey):
            # Generate ADB key files
            keygen(adbkey)

        adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"

    else:
        # Use "pure-python-adb" (communicate with ADB server)
        adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"

    aftv = setup(
        config[CONF_HOST],
        config[CONF_PORT],
        adbkey,
        config.get(CONF_ADB_SERVER_IP, ""),
        config[CONF_ADB_SERVER_PORT],
        config[CONF_STATE_DETECTION_RULES],
        config[CONF_DEVICE_CLASS],
        10.0,
    )

    return aftv, adb_log
Exemplo n.º 3
0
    def test_connect_with_key(self):
        with patch('adb_shell.auth.sign_pythonrsa.open', open_priv_pub), patch('adb_shell.auth.keygen.open', open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        self.device._handle._bulk_read = b''.join(patchers.BULK_READ_LIST_WITH_AUTH)

        self.assertTrue(self.device.connect([signer]))
Exemplo n.º 4
0
 def test_value_error(self):
     with patch('adb_shell.auth.sign_pythonrsa.open',
                open_priv_pub), patch('adb_shell.auth.keygen.open',
                                      open_priv_pub):
         with patch('adb_shell.auth.sign_pythonrsa.decoder.decode',
                    return_value=([None, [None]], None)):
             with self.assertRaises(ValueError):
                 keygen('tests/adbkey')
                 self.signer = PythonRSASigner.FromRSAKeyPath(
                     'tests/adbkey')
Exemplo n.º 5
0
 def test_index_error(self):
     with patch('adb_shell.auth.sign_pythonrsa.open',
                open_priv_pub), patch('adb_shell.auth.keygen.open',
                                      open_priv_pub):
         with patch('adb_shell.auth.sign_pythonrsa.decoder.decode',
                    side_effect=IndexError):
             with self.assertRaises(ValueError):
                 keygen('tests/adbkey')
                 self.signer = PythonRSASigner.FromRSAKeyPath(
                     'tests/adbkey')
Exemplo n.º 6
0
    def test_connect_with_key_invalid_response(self):
        with patch('adb_shell.auth.sign_pythonrsa.open', open_priv_pub), patch('adb_shell.auth.keygen.open', open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        self.device._handle._bulk_read = b''.join(patchers.BULK_READ_LIST_WITH_AUTH_INVALID)

        with self.assertRaises(exceptions.InvalidResponseError):
            self.device.connect([signer])

        self.assertFalse(self.device.available)
Exemplo n.º 7
0
    async def test_connect_with_new_key(self):
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')
            signer.pub_key = u''

        self.device._transport._bulk_read = b''.join(
            patchers.BULK_READ_LIST_WITH_AUTH_NEW_KEY)

        self.assertTrue(await self.device.connect([signer]))
Exemplo n.º 8
0
    def test_connect_with_new_key_and_callback(self):
        with patch('adb_shell.auth.sign_pythonrsa.open', open_priv_pub), patch('adb_shell.auth.keygen.open', open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')
            signer.pub_key = u''

        self._callback_invoked = False
        def auth_callback(device):
            self._callback_invoked = True

        self.device._handle._bulk_read = b''.join(patchers.BULK_READ_LIST_WITH_AUTH_NEW_KEY)

        self.assertTrue(self.device.connect([signer], auth_callback=auth_callback))
        self.assertTrue(self._callback_invoked)
Exemplo n.º 9
0
def adb_connect():
    key_dir = os.path.join(expanduser("~"), '.pyadb')
    if not os.path.exists(key_dir):
        os.makedirs(key_dir)
    key_path = os.path.join(key_dir, 'adbkey')
    keygen.keygen(key_path)
    adbkey = str(key_path)
    with open(adbkey) as f:
        priv = f.read()
    with open(adbkey + '.pub') as f:
        pub = f.read()
    signer = PythonRSASigner(pub, priv)
    device = AdbDeviceUsb()
    device.connect(rsa_keys=[signer], auth_timeout_s=60)
    return device
Exemplo n.º 10
0
def _setup_androidtv(hass, config):
    """Generate an ADB key (if needed) and load it."""
    adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey"))
    if CONF_ADB_SERVER_IP not in config:
        # Use "adb_shell" (Python ADB implementation)
        if not os.path.isfile(adbkey):
            # Generate ADB key files
            keygen(adbkey)

        # Load the ADB key
        signer = ADBPythonSync.load_adbkey(adbkey)
        adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"

    else:
        # Use "pure-python-adb" (communicate with ADB server)
        signer = None
        adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"

    return adbkey, signer, adb_log
Exemplo n.º 11
0
 def setUp(self):
     with patch('adb_shell.auth.sign_pythonrsa.open',
                open_priv_pub), patch('adb_shell.auth.keygen.open',
                                      open_priv_pub):
         keygen('tests/adbkey')
         self.signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')
Exemplo n.º 12
0
 def setUp(self):
     with patch('adb_shell.auth.sign_cryptography.open',
                open_priv_pub), patch('adb_shell.auth.keygen.open',
                                      open_priv_pub):
         keygen('tests/adbkey')
         self.signer = CryptographySigner('tests/adbkey')
Exemplo n.º 13
0
 def connect(self):
     if not Path(ADBClient.key_path).exists():
         keygen(self.key_path)
     signer = PythonRSASigner.FromRSAKeyPath(self.key_path)
     self.device.connect(rsa_keys=[signer])
Exemplo n.º 14
0
 def _keygen(self):
     try:
         keygen(self._adbkey)
     except Exception as exc:
         self._logger.error(exc)
Exemplo n.º 15
0
    def test_issue29(self):
        # https://github.com/JeffLIrion/adb_shell/issues/29
        with patch('adb_shell.auth.sign_pythonrsa.open',
                   open_priv_pub), patch('adb_shell.auth.keygen.open',
                                         open_priv_pub):
            keygen('tests/adbkey')
            signer = PythonRSASigner.FromRSAKeyPath('tests/adbkey')

        msg1 = AdbMessage(command=constants.OKAY, arg0=1, arg1=1, data=b'\x00')
        msg2 = AdbMessage(command=constants.CLSE, arg0=1, arg1=1, data=b'')

        self.device._handle._bulk_read = b''.join([
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\xc5\n\x00\x00\xbe\xaa\xab\xb7',  # Line 22
            b"\x17\xbf\xbf\xff\xc7\xa2eo'Sh\xdf\x8e\xf5\xff\xe0\tJ6H",  # Line 23
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 26
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 27
            b'OKAY\x99\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 290 (modified --> Line 30)
            b'CLSE\xa2\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 291
            b'CLSE\xa2\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 292
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x001\x00\x00\x00\xa8\xad\xab\xba',  # Line 31
            b'1',  # Line 32
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x001\x00\x00\x00\xa8\xad\xab\xba',  # Line 35
            b'1',  # Line 36
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x000\x00\x00\x00\xa8\xad\xab\xba',  # Line 39
            b'0',  # Line 40
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x13\x00\x00\x000\x06\x00\x00\xa8\xad\xab\xba',  # Line 43
            b'Wake Locks: size=0\n',  # Line 44
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x1e\x00\x00\x00V\x0b\x00\x00\xa8\xad\xab\xba',  # Line 47
            b'com.google.android.youtube.tv\n',  # Line 48
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00\x98\x00\x00\x00\xa13\x00\x00\xa8\xad\xab\xba',  # Line 51
            b'      state=PlaybackState {state=0, position=0, buffered position=0, speed=0.0, updated=0, actions=0, custom actions=[], active item id=-1, error=null}\n',  # Line 52
            b'WRTE\x99\x00\x00\x00\x01\x00\x00\x00.\x01\x00\x00\xceP\x00\x00\xa8\xad\xab\xba',  # Line 55
            b'- STREAM_MUSIC:\n   Muted: false\n   Min: 0\n   Max: 15\n   Current: 2 (speaker): 11, 4 (headset): 10, 8 (headphone): 10, 400 (hdmi): 6, 40000000 (default): 11\n   Devices: hdmi\n- STREAM_ALARM:\n   Muted: false\n   Min: 0\n   Max: 7\n   Current: 40000000 (default): 6\n   Devices: speaker\n- STREAM_NOTIFICATION:\n',  # Line 56
            b'CLSE\x99\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 59
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x94\t\x00\x00\xbe\xaa\xab\xb7',  # Line 297
            b'P\xa5\x86\x97\xe8\x01\xb09\x8c>F\x9d\xc6\xbd\xc0J\x80!\xbb\x1a',  # Line 298
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 301
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 302
            b'OKAY\xa5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 305
            b'CLSE\xa5\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 306
            msg1.pack(),
            msg1.data,
            msg2.pack(),
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00e\x0c\x00\x00\xbe\xaa\xab\xb7',  # Line 315
            b'\xd3\xef\x7f_\xa6\xc0`b\x19\\z\xe4\xf3\xe2\xed\x8d\xe1W\xfbH',  # Line 316
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 319
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 320
            b'OKAY\xa7\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 323
            b'CLSE\xa7\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 324
            msg1.pack(),
            msg1.data,
            msg2.pack(),
            b'AUTH\x01\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x93\x08\x00\x00\xbe\xaa\xab\xb7',  # Line 333
            b's\xd4_e\xa4s\x02\x95\x0f\x1e\xec\n\x95Y9[`\x8e\xe1f',  # Line 334
            b"CNXN\x00\x00\x00\x01\x00\x10\x00\x00i\x00\x00\x00.'\x00\x00\xbc\xb1\xa7\xb1",  # Line 337
            b'device::ro.product.name=once;ro.product.model=MIBOX3;ro.product.device=once;features=stat_v2,cmd,shell_v2',  # Line 338
            b'OKAY\xa9\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\xb4\xbe\xa6',  # Line 341
            b'CLSE\xa9\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\xb3\xac\xba',  # Line 342
            msg1.pack(),
            msg1.data,
            msg2.pack()
        ])

        self.assertTrue(self.device.connect([signer]))

        self.device.shell('Android TV update command')

        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
        self.assertTrue(self.device.connect([signer]))
        self.device.shell('Android TV update command')
        self.device.shell('Android TV update command')
Exemplo n.º 16
0
 def setUp(self):
     with patch('adb_shell.auth.sign_pycryptodome.open', open_priv_pub), patch('adb_shell.auth.keygen.open', open_priv_pub):
         keygen('tests/adbkey')
         self.signer = PycryptodomeAuthSigner('tests/adbkey')
Exemplo n.º 17
0
    async def async_step_user(self, user_input=None):
        """Handle a flow initiated by the user."""
        error = None

        if user_input is not None:
            host = user_input[CONF_HOST]
            adb_key = user_input.get(CONF_ADBKEY)
            adb_server = user_input.get(CONF_ADB_SERVER_IP)

            if adb_key and adb_server:
                return self._show_setup_form(user_input, "key_and_server")

            if adb_key:
                isfile = await self.hass.async_add_executor_job(_is_file, adb_key)
                if not isfile:
                    return self._show_setup_form(user_input, "adbkey_not_file")

            ip_address = await self.hass.async_add_executor_job(_get_ip, host)
            if not ip_address:
                return self._show_setup_form(user_input, "invalid_host")

            self._async_abort_entries_match({CONF_HOST: host})
            if ip_address != host:
                self._async_abort_entries_match({CONF_HOST: ip_address})

            device_calss = user_input.get(CONF_DEVICE_CLASS)
            if device_calss == "ais":
                user_input[CONF_DEVICE_CLASS] = DEVICE_ANDROIDTV
                if host == "127.0.0.1" and ais_global.has_root():
                    await self.async_execute_command(
                        "su -c 'setprop persist.service.adb.enable 1'"
                    )
                    await self.async_execute_command(
                        "su -c 'setprop service.adb.tcp.port 5555'"
                    )
                    await self.async_execute_command("su -c 'stop adbd'")
                    await self.async_execute_command(
                        "su -c 'touch /data/misc/adb/adb_keys'"
                    )
                    adbkey = self.hass.config.path(STORAGE_DIR, "androidtv_adbkey")
                    if not os.path.isfile(adbkey):
                        # Generate ADB key files
                        keygen(adbkey)
                    await self.async_execute_command(
                        "su -c 'rm /data/misc/adb/adb_keys'"
                    )
                    await self.async_execute_command(
                        "su -c 'cat /data/data/pl.sviete.dom/files/home/AIS/.storage/androidtv_adbkey.pub >> "
                        "/data/misc/adb/adb_keys'"
                    )
                    # TODO add without rm
                    # await self.async_execute_command(
                    #     "su -c 'grep  -F -f /data/data/pl.sviete.dom/files/home/AIS"
                    #     "/.storage/androidtv_adbkey.pub /data/misc/adb/adb_keys || cat "
                    #     "/data/data/pl.sviete.dom/files/home/AIS/.storage"
                    #     "/androidtv_adbkey.pub >> /data/misc/adb/adb_keys'"
                    # )
                    await self.async_execute_command(
                        "su -c 'chmod 0644 /data/misc/adb/adb_keys'"
                    )
                    await self.async_execute_command("su -c 'start adbd'")

            error, unique_id = await self._async_check_connection(user_input)
            if error is None:
                if not unique_id:
                    return self.async_abort(reason="invalid_unique_id")

                await self.async_set_unique_id(unique_id)
                self._abort_if_unique_id_configured()

                return self.async_create_entry(
                    title=user_input.get(CONF_NAME) or host, data=user_input
                )

        user_input = user_input or {}
        return self._show_setup_form(user_input, error)
Exemplo n.º 18
0
def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Android TV / Fire TV platform."""
    hass.data.setdefault(ANDROIDTV_DOMAIN, {})

    host = f"{config[CONF_HOST]}:{config[CONF_PORT]}"

    if CONF_ADB_SERVER_IP not in config:
        # Use "adb_shell" (Python ADB implementation)
        if CONF_ADBKEY not in config:
            # Generate ADB key files (if they don't exist)
            adbkey = hass.config.path(STORAGE_DIR, "androidtv_adbkey")
            if not os.path.isfile(adbkey):
                keygen(adbkey)

            adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"

            aftv = setup(
                host,
                adbkey,
                device_class=config[CONF_DEVICE_CLASS],
                state_detection_rules=config[CONF_STATE_DETECTION_RULES],
                auth_timeout_s=10.0,
            )

        else:
            adb_log = (
                f"using Python ADB implementation with adbkey='{config[CONF_ADBKEY]}'"
            )

            aftv = setup(
                host,
                config[CONF_ADBKEY],
                device_class=config[CONF_DEVICE_CLASS],
                state_detection_rules=config[CONF_STATE_DETECTION_RULES],
                auth_timeout_s=10.0,
            )

    else:
        # Use "pure-python-adb" (communicate with ADB server)
        adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"

        aftv = setup(
            host,
            adb_server_ip=config[CONF_ADB_SERVER_IP],
            adb_server_port=config[CONF_ADB_SERVER_PORT],
            device_class=config[CONF_DEVICE_CLASS],
            state_detection_rules=config[CONF_STATE_DETECTION_RULES],
        )

    if not aftv.available:
        # Determine the name that will be used for the device in the log
        if CONF_NAME in config:
            device_name = config[CONF_NAME]
        elif config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV:
            device_name = "Android TV device"
        elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV:
            device_name = "Fire TV device"
        else:
            device_name = "Android TV / Fire TV device"

        _LOGGER.warning("Could not connect to %s at %s %s", device_name, host,
                        adb_log)
        raise PlatformNotReady

    if host in hass.data[ANDROIDTV_DOMAIN]:
        _LOGGER.warning("Platform already setup on %s, skipping", host)
    else:
        if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
            device = AndroidTVDevice(
                aftv,
                config[CONF_NAME],
                config[CONF_APPS],
                config.get(CONF_TURN_ON_COMMAND),
                config.get(CONF_TURN_OFF_COMMAND),
            )
            device_name = config[
                CONF_NAME] if CONF_NAME in config else "Android TV"
        else:
            device = FireTVDevice(
                aftv,
                config[CONF_NAME],
                config[CONF_APPS],
                config[CONF_GET_SOURCES],
                config.get(CONF_TURN_ON_COMMAND),
                config.get(CONF_TURN_OFF_COMMAND),
            )
            device_name = config[
                CONF_NAME] if CONF_NAME in config else "Fire TV"

        add_entities([device])
        _LOGGER.debug("Setup %s at %s %s", device_name, host, adb_log)
        hass.data[ANDROIDTV_DOMAIN][host] = device

    if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND):
        return

    def service_adb_command(service):
        """Dispatch service calls to target entities."""
        cmd = service.data.get(ATTR_COMMAND)
        entity_id = service.data.get(ATTR_ENTITY_ID)
        target_devices = [
            dev for dev in hass.data[ANDROIDTV_DOMAIN].values()
            if dev.entity_id in entity_id
        ]

        for target_device in target_devices:
            output = target_device.adb_command(cmd)

            # log the output, if there is any
            if output:
                _LOGGER.info(
                    "Output of command '%s' from '%s': %s",
                    cmd,
                    target_device.entity_id,
                    output,
                )

    hass.services.register(
        ANDROIDTV_DOMAIN,
        SERVICE_ADB_COMMAND,
        service_adb_command,
        schema=SERVICE_ADB_COMMAND_SCHEMA,
    )
Exemplo n.º 19
0
def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Android TV / Fire TV platform."""
    hass.data.setdefault(ANDROIDTV_DOMAIN, {})

    address = f"{config[CONF_HOST]}:{config[CONF_PORT]}"

    if address in hass.data[ANDROIDTV_DOMAIN]:
        _LOGGER.warning("Platform already setup on %s, skipping", address)
        return

    if CONF_ADB_SERVER_IP not in config:
        # Use "adb_shell" (Python ADB implementation)
        if CONF_ADBKEY not in config:
            # Generate ADB key files (if they don't exist)
            adbkey = hass.config.path(STORAGE_DIR, "androidtv_adbkey")
            if not os.path.isfile(adbkey):
                keygen(adbkey)

            adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"

            aftv = setup(
                config[CONF_HOST],
                config[CONF_PORT],
                adbkey,
                device_class=config[CONF_DEVICE_CLASS],
                state_detection_rules=config[CONF_STATE_DETECTION_RULES],
                auth_timeout_s=10.0,
            )

        else:
            adb_log = (
                f"using Python ADB implementation with adbkey='{config[CONF_ADBKEY]}'"
            )

            aftv = setup(
                config[CONF_HOST],
                config[CONF_PORT],
                config[CONF_ADBKEY],
                device_class=config[CONF_DEVICE_CLASS],
                state_detection_rules=config[CONF_STATE_DETECTION_RULES],
                auth_timeout_s=10.0,
            )

    else:
        # Use "pure-python-adb" (communicate with ADB server)
        adb_log = f"using ADB server at {config[CONF_ADB_SERVER_IP]}:{config[CONF_ADB_SERVER_PORT]}"

        aftv = setup(
            config[CONF_HOST],
            config[CONF_PORT],
            adb_server_ip=config[CONF_ADB_SERVER_IP],
            adb_server_port=config[CONF_ADB_SERVER_PORT],
            device_class=config[CONF_DEVICE_CLASS],
            state_detection_rules=config[CONF_STATE_DETECTION_RULES],
        )

    if not aftv.available:
        # Determine the name that will be used for the device in the log
        if CONF_NAME in config:
            device_name = config[CONF_NAME]
        elif config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV:
            device_name = "Android TV device"
        elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV:
            device_name = "Fire TV device"
        else:
            device_name = "Android TV / Fire TV device"

        _LOGGER.warning("Could not connect to %s at %s %s", device_name,
                        address, adb_log)
        raise PlatformNotReady

    device_args = [
        aftv,
        config[CONF_NAME],
        config[CONF_APPS],
        config[CONF_GET_SOURCES],
        config.get(CONF_TURN_ON_COMMAND),
        config.get(CONF_TURN_OFF_COMMAND),
        config[CONF_EXCLUDE_UNNAMED_APPS],
        config[CONF_SCREENCAP],
    ]

    if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
        device = AndroidTVDevice(*device_args)
        device_name = config.get(CONF_NAME, "Android TV")
    else:
        device = FireTVDevice(*device_args)
        device_name = config.get(CONF_NAME, "Fire TV")

    add_entities([device])
    _LOGGER.debug("Setup %s at %s %s", device_name, address, adb_log)
    hass.data[ANDROIDTV_DOMAIN][address] = device

    if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND):
        return

    def service_adb_command(service):
        """Dispatch service calls to target entities."""
        cmd = service.data[ATTR_COMMAND]
        entity_id = service.data[ATTR_ENTITY_ID]
        target_devices = [
            dev for dev in hass.data[ANDROIDTV_DOMAIN].values()
            if dev.entity_id in entity_id
        ]

        for target_device in target_devices:
            output = target_device.adb_command(cmd)

            # log the output, if there is any
            if output:
                _LOGGER.info(
                    "Output of command '%s' from '%s': %s",
                    cmd,
                    target_device.entity_id,
                    output,
                )

    hass.services.register(
        ANDROIDTV_DOMAIN,
        SERVICE_ADB_COMMAND,
        service_adb_command,
        schema=SERVICE_ADB_COMMAND_SCHEMA,
    )

    def service_download(service):
        """Download a file from your Android TV / Fire TV device to your Home Assistant instance."""
        local_path = service.data[ATTR_LOCAL_PATH]
        if not hass.config.is_allowed_path(local_path):
            _LOGGER.warning("'%s' is not secure to load data from!",
                            local_path)
            return

        device_path = service.data[ATTR_DEVICE_PATH]
        entity_id = service.data[ATTR_ENTITY_ID]
        target_device = [
            dev for dev in hass.data[ANDROIDTV_DOMAIN].values()
            if dev.entity_id in entity_id
        ][0]

        target_device.adb_pull(local_path, device_path)

    hass.services.register(
        ANDROIDTV_DOMAIN,
        SERVICE_DOWNLOAD,
        service_download,
        schema=SERVICE_DOWNLOAD_SCHEMA,
    )

    def service_upload(service):
        """Upload a file from your Home Assistant instance to an Android TV / Fire TV device."""
        local_path = service.data[ATTR_LOCAL_PATH]
        if not hass.config.is_allowed_path(local_path):
            _LOGGER.warning("'%s' is not secure to load data from!",
                            local_path)
            return

        device_path = service.data[ATTR_DEVICE_PATH]
        entity_id = service.data[ATTR_ENTITY_ID]
        target_devices = [
            dev for dev in hass.data[ANDROIDTV_DOMAIN].values()
            if dev.entity_id in entity_id
        ]

        for target_device in target_devices:
            target_device.adb_push(local_path, device_path)

    hass.services.register(ANDROIDTV_DOMAIN,
                           SERVICE_UPLOAD,
                           service_upload,
                           schema=SERVICE_UPLOAD_SCHEMA)