diff --git a/androidtv/androidtv/androidtv_async.py b/androidtv/androidtv/androidtv_async.py index 5dcbf3ff..ba48a294 100644 --- a/androidtv/androidtv/androidtv_async.py +++ b/androidtv/androidtv/androidtv_async.py @@ -122,14 +122,14 @@ async def get_properties(self, get_running_apps=True, lazy=False): """ if lazy: if get_running_apps: - output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_RUNNING_APPS) + output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_LAZY_RUNNING_APPS) else: - output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_NO_RUNNING_APPS) + output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_NO_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_LAZY_NO_RUNNING_APPS) else: if get_running_apps: - output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_RUNNING_APPS) + output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_NOT_LAZY_RUNNING_APPS) else: - output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS) + output = await self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS) _LOGGER.debug("Android TV %s:%d `get_properties` response: %s", self.host, self.port, output) return self._get_properties(output, get_running_apps) diff --git a/androidtv/androidtv/androidtv_sync.py b/androidtv/androidtv/androidtv_sync.py index ff8794a5..d927d007 100644 --- a/androidtv/androidtv/androidtv_sync.py +++ b/androidtv/androidtv/androidtv_sync.py @@ -122,14 +122,14 @@ def get_properties(self, get_running_apps=True, lazy=False): """ if lazy: if get_running_apps: - output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_RUNNING_APPS) + output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_LAZY_RUNNING_APPS) else: - output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_NO_RUNNING_APPS) + output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_LAZY_NO_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_LAZY_NO_RUNNING_APPS) else: if get_running_apps: - output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_RUNNING_APPS) + output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_NOT_LAZY_RUNNING_APPS) else: - output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS) + output = self._adb.shell(constants.CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS if not self._is_google_tv else constants.CMD_GOOGLE_TV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS) _LOGGER.debug("Android TV %s:%d `get_properties` response: %s", self.host, self.port, output) return self._get_properties(output, get_running_apps) diff --git a/androidtv/basetv/basetv.py b/androidtv/basetv/basetv.py index ff60543c..bab9f568 100644 --- a/androidtv/basetv/basetv.py +++ b/androidtv/basetv/basetv.py @@ -75,6 +75,7 @@ def __init__(self, adb, host, port=5555, adbkey='', adb_server_ip='', adb_server self.adb_server_port = adb_server_port self._state_detection_rules = state_detection_rules self.device_properties = {} + self._is_google_tv = False # make sure the rules are valid if self._state_detection_rules: @@ -133,6 +134,10 @@ def _parse_device_properties(self, properties): manufacturer, model, serialno, version, mac_wlan0_output, mac_eth0_output = lines + # Is this a Google Chromecast Android TV? + if "Google" in manufacturer and "Chromecast" in model: + self._is_google_tv = True + if not serialno.strip(): _LOGGER.warning("Could not obtain serialno for %s:%d, got: '%s'", self.host, self.port, serialno) serialno = None diff --git a/androidtv/constants.py b/androidtv/constants.py index a2ddd993..d92fe8b9 100644 --- a/androidtv/constants.py +++ b/androidtv/constants.py @@ -31,6 +31,9 @@ #: Get the current app CMD_CURRENT_APP = "CURRENT_APP=$(dumpsys window windows | grep mCurrentFocus) && CURRENT_APP=${CURRENT_APP#*{* * } && CURRENT_APP=${CURRENT_APP%%/*} && echo $CURRENT_APP" +#: Get the current app for a Google TV device +CMD_CURRENT_APP_GOOGLE_TV = "CURRENT_APP=$(dumpsys window windows | grep 'Window #1') && CURRENT_APP=${CURRENT_APP#*{* * } && CURRENT_APP=${CURRENT_APP%%/*} && echo $CURRENT_APP" + #: Launch an app if it is not already the current app CMD_LAUNCH_APP = "CURRENT_APP=$(dumpsys window windows | grep mCurrentFocus) && CURRENT_APP=${{CURRENT_APP#*{{* * }} && CURRENT_APP=${{CURRENT_APP%%/*}} && if [ $CURRENT_APP != '{0}' ]; then monkey -p {0} -c " + INTENT_LAUNCH + " --pct-syskeys 0 1; fi" @@ -50,7 +53,7 @@ CMD_SCREEN_ON = "(dumpsys power | grep 'Display Power' | grep -q 'state=ON' || dumpsys power | grep -q 'mScreenOn=true')" #: Get the "STREAM_MUSIC" block from ``dumpsys audio`` -CMD_STREAM_MUSIC = r"dumpsys audio | grep '\- STREAM_MUSIC:' -A 12" +CMD_STREAM_MUSIC = r"dumpsys audio | grep '\- STREAM_MUSIC:' -A 11" #: Get the wake lock size CMD_WAKE_LOCK_SIZE = "dumpsys power | grep Locks | grep 'size='" @@ -67,6 +70,18 @@ #: Get the properties for an Android TV device (``lazy=False, get_running_apps=False``); see :py:meth:`androidtv.androidtv.androidtv_sync.AndroidTVSync.get_properties` and :py:meth:`androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties` CMD_ANDROIDTV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS = CMD_SCREEN_ON + CMD_SUCCESS1_FAILURE0 + " && " + CMD_AWAKE + CMD_SUCCESS1_FAILURE0 + " && (" + CMD_AUDIO_STATE + ") && " + CMD_WAKE_LOCK_SIZE + " && " + CMD_CURRENT_APP + " && (" + CMD_MEDIA_SESSION_STATE + " || echo) && " + CMD_STREAM_MUSIC +#: Get the properties for a Google TV device (``lazy=True, get_running_apps=True``); see :py:meth:`androidtv.androidtv.androidtv_sync.AndroidTVSync.get_properties` and :py:meth:`androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties` +CMD_GOOGLE_TV_PROPERTIES_LAZY_RUNNING_APPS = CMD_SCREEN_ON + CMD_SUCCESS1 + " && " + CMD_AWAKE + CMD_SUCCESS1 + " && (" + CMD_AUDIO_STATE + ") && " + CMD_WAKE_LOCK_SIZE + " && " + CMD_CURRENT_APP_GOOGLE_TV + " && (" + CMD_MEDIA_SESSION_STATE + " || echo) && " + CMD_STREAM_MUSIC + " && " + CMD_ANDROIDTV_RUNNING_APPS + +#: Get the properties for a Google TV device (``lazy=True, get_running_apps=False``); see :py:meth:`androidtv.androidtv.androidtv_sync.AndroidTVSync.get_properties` and :py:meth:`androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties` +CMD_GOOGLE_TV_PROPERTIES_LAZY_NO_RUNNING_APPS = CMD_SCREEN_ON + CMD_SUCCESS1 + " && " + CMD_AWAKE + CMD_SUCCESS1 + " && (" + CMD_AUDIO_STATE + ") && " + CMD_WAKE_LOCK_SIZE + " && " + CMD_CURRENT_APP_GOOGLE_TV + " && (" + CMD_MEDIA_SESSION_STATE + " || echo) && " + CMD_STREAM_MUSIC + +#: Get the properties for a Google TV device (``lazy=False, get_running_apps=True``); see :py:meth:`androidtv.androidtv.androidtv_sync.AndroidTVSync.get_properties` and :py:meth:`androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties` +CMD_GOOGLE_TV_PROPERTIES_NOT_LAZY_RUNNING_APPS = CMD_SCREEN_ON + CMD_SUCCESS1_FAILURE0 + " && " + CMD_AWAKE + CMD_SUCCESS1_FAILURE0 + " && (" + CMD_AUDIO_STATE + ") && " + CMD_WAKE_LOCK_SIZE + " && " + CMD_CURRENT_APP_GOOGLE_TV + " && (" + CMD_MEDIA_SESSION_STATE + " || echo) && " + CMD_STREAM_MUSIC + " && " + CMD_ANDROIDTV_RUNNING_APPS + +#: Get the properties for a Google TV device (``lazy=False, get_running_apps=False``); see :py:meth:`androidtv.androidtv.androidtv_sync.AndroidTVSync.get_properties` and :py:meth:`androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties` +CMD_GOOGLE_TV_PROPERTIES_NOT_LAZY_NO_RUNNING_APPS = CMD_SCREEN_ON + CMD_SUCCESS1_FAILURE0 + " && " + CMD_AWAKE + CMD_SUCCESS1_FAILURE0 + " && (" + CMD_AUDIO_STATE + ") && " + CMD_WAKE_LOCK_SIZE + " && " + CMD_CURRENT_APP_GOOGLE_TV + " && (" + CMD_MEDIA_SESSION_STATE + " || echo) && " + CMD_STREAM_MUSIC + #: Get the properties for a Fire TV device (``lazy=True, get_running_apps=True``); see :py:meth:`androidtv.firetv.firetv_sync.FireTVSync.get_properties` and :py:meth:`androidtv.firetv.firetv_async.FireTVAsync.get_properties` CMD_FIRETV_PROPERTIES_LAZY_RUNNING_APPS = CMD_SCREEN_ON + CMD_SUCCESS1 + " && " + CMD_AWAKE + CMD_SUCCESS1 + " && " + CMD_WAKE_LOCK_SIZE + " && " + CMD_CURRENT_APP + " && (" + CMD_MEDIA_SESSION_STATE + " || echo) && " + CMD_FIRETV_RUNNING_APPS diff --git a/tests/test_androidtv_sync.py b/tests/test_androidtv_sync.py index 6547152c..41b8b1b6 100644 --- a/tests/test_androidtv_sync.py +++ b/tests/test_androidtv_sync.py @@ -213,6 +213,38 @@ 'running_apps': None} STATE_NONE = (None, None, None, None, None, None) +# Source: https://community.home-assistant.io/t/new-chromecast-w-android-tv-integration-only-showing-as-off-or-idle/234424/17 +GET_PROPERTIES_OUTPUT_GOOGLE_TV = """111Wake Locks: size=4 +com.google.android.youtube.tv + state=PlaybackState {state=3, position=610102, buffered position=0, speed=1.0, updated=234649304, actions=379, custom actions=[], active item id=-1, error=null} +- STREAM_MUSIC: + Muted: false + Min: 0 + Max: 25 + streamVolume:25 + Current: 4 (headset): 10, 8 (headphone): 10, 400 (hdmi): 25, 4000000 (usb_headset): 6, 40000000 (default): 20 + Devices: hdmi +- STREAM_ALARM: + Muted: false + Min: 1 + Max: 7 + streamVolume:6 +u0_a38 16522 16265 1348552 89960 0 0 S com.android.systemui +u0_a56 16765 16265 1343904 94124 0 0 S com.google.android.inputmethod.latin +u0_a42 16783 16265 1302956 67868 0 0 S com.google.android.tv.remote.service +""" + +GET_PROPERTIES_DICT_GOOGLE_TV = {'screen_on': True, + 'awake': True, + 'audio_state': constants.STATE_PAUSED, + 'wake_lock_size': 4, + 'current_app': 'com.google.android.youtube.tv', + 'media_session_state': 3, + 'audio_output_device': 'hdmi', + 'is_volume_muted': False, + 'volume': 25, + 'running_apps': ['com.android.systemui', 'com.google.android.inputmethod.latin', 'com.google.android.tv.remote.service']} + # https://community.home-assistant.io/t/testers-needed-custom-state-detection-rules-for-android-tv-fire-tv/129493/6?u=jefflirion STATE_DETECTION_RULES_PLEX = {'com.plexapp.android': [{'playing': {'media_session_state': 3, 'wake_lock_size': 3}}, @@ -627,6 +659,10 @@ def test_get_properties(self): properties = self.atv.get_properties_dict(get_running_apps=True, lazy=False) self.assertEqual(properties, true_properties) + with patchers.patch_shell(GET_PROPERTIES_OUTPUT_GOOGLE_TV)[self.PATCH_KEY]: + properties = self.atv.get_properties_dict(get_running_apps=True, lazy=True) + self.assertEqual(properties, GET_PROPERTIES_DICT_GOOGLE_TV) + def test_update(self): """Check that the ``update`` method works correctly. diff --git a/tests/test_basetv_async.py b/tests/test_basetv_async.py index ff380853..daaec960 100644 --- a/tests/test_basetv_async.py +++ b/tests/test_basetv_async.py @@ -58,6 +58,15 @@ 'wifimac': None, 'ethmac': 'ab:cd:ef:gh:ij:kl'} +# Source: https://community.home-assistant.io/t/new-chromecast-w-android-tv-integration-only-showing-as-off-or-idle/234424/15 +DEVICE_PROPERTIES_GOOGLE_TV = """Google +Chromecast +SERIALNO +10 + link/ether ab:cd:ef:gh:ij:kl brd ff:ff:ff:ff:ff:ff +Device "eth0" does not exist. +""" + MEDIA_SESSION_STATE_OUTPUT = "com.amazon.tv.launcher\nstate=PlaybackState {state=2, position=0, buffered position=0, speed=0.0, updated=65749, actions=240640, custom actions=[], active item id=-1, error=null}" STATE_DETECTION_RULES1 = {'com.amazon.tv.launcher': ['off']} @@ -330,6 +339,10 @@ async def test_get_device_properties(self): device_properties = await self.btv.get_device_properties() self.assertDictEqual({}, device_properties) + with async_patchers.patch_shell(DEVICE_PROPERTIES_GOOGLE_TV)[self.PATCH_KEY]: + device_properties = await self.btv.get_device_properties() + self.assertTrue(self.btv._is_google_tv) + @awaiter async def test_awake(self): """Check that the ``awake`` property works correctly. diff --git a/tests/test_basetv_sync.py b/tests/test_basetv_sync.py index 4463a61a..3032e8d9 100644 --- a/tests/test_basetv_sync.py +++ b/tests/test_basetv_sync.py @@ -52,6 +52,15 @@ link/ether ab:cd:ef:gh:ij:kl brd ff:ff:ff:ff:ff:ff """ +# Source: https://community.home-assistant.io/t/new-chromecast-w-android-tv-integration-only-showing-as-off-or-idle/234424/15 +DEVICE_PROPERTIES_GOOGLE_TV = """Google +Chromecast +SERIALNO +10 + link/ether ab:cd:ef:gh:ij:kl brd ff:ff:ff:ff:ff:ff +Device "eth0" does not exist. +""" + DEVICE_PROPERTIES_DICT3 = {'manufacturer': 'Not Amazon', 'model': 'AFTT', 'serialno': 'SERIALNO', @@ -324,6 +333,10 @@ def test_get_device_properties(self): device_properties = self.btv.get_device_properties() self.assertDictEqual({}, device_properties) + with patchers.patch_shell(DEVICE_PROPERTIES_GOOGLE_TV)[self.PATCH_KEY]: + device_properties = self.btv.get_device_properties() + self.assertTrue(self.btv._is_google_tv) + def test_awake(self): """Check that the ``awake`` property works correctly.