text: """Webhook handlers for mobile_app."""
  import logging
  
  from aiohttp.web import HTTPBadRequest, Response, Request
  import voluptuous as vol
  
  from homeassistant.components.cloud import (async_remote_ui_url,
                                              CloudNotAvailable)
  from homeassistant.components.frontend import MANIFEST_JSON
  from homeassistant.components.zone.const import DOMAIN as ZONE_DOMAIN
  
  from homeassistant.const import (ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA,
                                   CONF_WEBHOOK_ID, HTTP_BAD_REQUEST,
                                   HTTP_CREATED)
  from homeassistant.core import EventOrigin
  from homeassistant.exceptions import (HomeAssistantError,
                                        ServiceNotFound, TemplateError)
  from homeassistant.helpers import device_registry as dr
  from homeassistant.helpers.dispatcher import async_dispatcher_send
  from homeassistant.helpers.template import attach
  from homeassistant.helpers.typing import HomeAssistantType
  
  from .const import (ATTR_DEVICE_ID,
                      ATTR_DEVICE_NAME, ATTR_EVENT_DATA, ATTR_EVENT_TYPE,
                      ATTR_MANUFACTURER, ATTR_MODEL, ATTR_OS_VERSION,
                      ATTR_SENSOR_TYPE, ATTR_SENSOR_UNIQUE_ID,
                      ATTR_SUPPORTS_ENCRYPTION, ATTR_TEMPLATE,
                      ATTR_TEMPLATE_VARIABLES,
                      ATTR_WEBHOOK_DATA, ATTR_WEBHOOK_ENCRYPTED,
                      ATTR_WEBHOOK_ENCRYPTED_DATA, ATTR_WEBHOOK_TYPE,
                      CONF_CLOUDHOOK_URL, CONF_REMOTE_UI_URL, CONF_SECRET,
                      DATA_CONFIG_ENTRIES, DATA_DELETED_IDS, DATA_STORE, DOMAIN,
                      ERR_ENCRYPTION_REQUIRED, ERR_SENSOR_DUPLICATE_UNIQUE_ID,
                      ERR_SENSOR_NOT_REGISTERED, SIGNAL_SENSOR_UPDATE,
                      WEBHOOK_PAYLOAD_SCHEMA, WEBHOOK_SCHEMAS, WEBHOOK_TYPES,
                      WEBHOOK_TYPE_CALL_SERVICE, WEBHOOK_TYPE_FIRE_EVENT,
                      WEBHOOK_TYPE_GET_CONFIG, WEBHOOK_TYPE_GET_ZONES,
                      WEBHOOK_TYPE_REGISTER_SENSOR, WEBHOOK_TYPE_RENDER_TEMPLATE,
                      WEBHOOK_TYPE_UPDATE_LOCATION,
                      WEBHOOK_TYPE_UPDATE_REGISTRATION,
                      WEBHOOK_TYPE_UPDATE_SENSOR_STATES, SIGNAL_LOCATION_UPDATE)
  
  
  from .helpers import (_decrypt_payload, empty_okay_response, error_response,
                        registration_context, safe_registration, savable_state,
                        webhook_response)
  
  
  _LOGGER = logging.getLogger(__name__)
  
  
  async def handle_webhook(hass: HomeAssistantType, webhook_id: str,
                           request: Request) -> Response:
      """Handle webhook callback."""
      if webhook_id in hass.data[DOMAIN][DATA_DELETED_IDS]:
          return Response(status=410)
  
      headers = {}
  
      config_entry = hass.data[DOMAIN][DATA_CONFIG_ENTRIES][webhook_id]
  
      registration = config_entry.data
  
      try:
          req_data = await request.json()
      except ValueError:
          _LOGGER.warning('Received invalid JSON from mobile_app')
          return empty_okay_response(status=HTTP_BAD_REQUEST)
  
      if (ATTR_WEBHOOK_ENCRYPTED not in req_data and
              registration[ATTR_SUPPORTS_ENCRYPTION]):
          _LOGGER.warning("Refusing to accept unencrypted webhook from %s",
                          registration[ATTR_DEVICE_NAME])
          return error_response(ERR_ENCRYPTION_REQUIRED, "Encryption required")
  
      try:
          req_data = WEBHOOK_PAYLOAD_SCHEMA(req_data)
      except vol.Invalid as ex:
          err = vol.humanize.humanize_error(req_data, ex)
          _LOGGER.error('Received invalid webhook payload: %s', err)
          return empty_okay_response()
  
      webhook_type = req_data[ATTR_WEBHOOK_TYPE]
  
      webhook_payload = req_data.get(ATTR_WEBHOOK_DATA, {})
  
      if req_data[ATTR_WEBHOOK_ENCRYPTED]:
          enc_data = req_data[ATTR_WEBHOOK_ENCRYPTED_DATA]
          webhook_payload = _decrypt_payload(registration[CONF_SECRET], enc_data)
  
      if webhook_type not in WEBHOOK_TYPES:
          _LOGGER.error('Received invalid webhook type: %s', webhook_type)
          return empty_okay_response()
  
      data = webhook_payload
  
      _LOGGER.debug("Received webhook payload for type %s: %s", webhook_type,
                    data)
  
      if webhook_type in WEBHOOK_SCHEMAS:
          try:
              data = WEBHOOK_SCHEMAS[webhook_type](webhook_payload)
          except vol.Invalid as ex:
              err = vol.humanize.humanize_error(webhook_payload, ex)
              _LOGGER.error('Received invalid webhook payload: %s', err)
              return empty_okay_response(headers=headers)
  
      context = registration_context(registration)
  
      if webhook_type == WEBHOOK_TYPE_CALL_SERVICE:
          try:
              await hass.services.async_call(data[ATTR_DOMAIN],
                                             data[ATTR_SERVICE],
                                             data[ATTR_SERVICE_DATA],
                                             blocking=True, context=context)
          # noqa: E722 pylint: disable=broad-except
          except (vol.Invalid, ServiceNotFound, Exception) as ex:
              _LOGGER.error("Error when calling service during mobile_app "
                            "webhook (device name: %s): %s",
                            registration[ATTR_DEVICE_NAME], ex)
              raise HTTPBadRequest()
  
          return empty_okay_response(headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_FIRE_EVENT:
          event_type = data[ATTR_EVENT_TYPE]
          hass.bus.async_fire(event_type, data[ATTR_EVENT_DATA],
                              EventOrigin.remote,
                              context=context)
          return empty_okay_response(headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_RENDER_TEMPLATE:
          resp = {}
          for key, item in data.items():
              try:
                  tpl = item[ATTR_TEMPLATE]
                  attach(hass, tpl)
                  resp[key] = tpl.async_render(item.get(ATTR_TEMPLATE_VARIABLES))
              # noqa: E722 pylint: disable=broad-except
              except TemplateError as ex:
                  resp[key] = {"error": str(ex)}
  
          return webhook_response(resp, registration=registration,
                                  headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_UPDATE_LOCATION:
          hass.helpers.dispatcher.async_dispatcher_send(
              SIGNAL_LOCATION_UPDATE.format(config_entry.entry_id), data
          )
          return empty_okay_response(headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_UPDATE_REGISTRATION:
          new_registration = {**registration, **data}
  
          device_registry = await dr.async_get_registry(hass)
  
          device_registry.async_get_or_create(
              config_entry_id=config_entry.entry_id,
              identifiers={
                  (ATTR_DEVICE_ID, registration[ATTR_DEVICE_ID]),
                  (CONF_WEBHOOK_ID, registration[CONF_WEBHOOK_ID])
              },
              manufacturer=new_registration[ATTR_MANUFACTURER],
              model=new_registration[ATTR_MODEL],
              name=new_registration[ATTR_DEVICE_NAME],
              sw_version=new_registration[ATTR_OS_VERSION]
          )
  
          hass.config_entries.async_update_entry(config_entry,
                                                 data=new_registration)
  
          return webhook_response(safe_registration(new_registration),
                                  registration=registration, headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_REGISTER_SENSOR:
          entity_type = data[ATTR_SENSOR_TYPE]
  
          unique_id = data[ATTR_SENSOR_UNIQUE_ID]
  
          unique_store_key = "{}_{}".format(webhook_id, unique_id)
  
          if unique_store_key in hass.data[DOMAIN][entity_type]:
              _LOGGER.error("Refusing to re-register existing sensor %s!",
                            unique_id)
              return error_response(ERR_SENSOR_DUPLICATE_UNIQUE_ID,
                                    "{} {} already exists!".format(entity_type,
                                                                   unique_id),
                                    status=409)
  
          data[CONF_WEBHOOK_ID] = webhook_id
  
          hass.data[DOMAIN][entity_type][unique_store_key] = data
  
          try:
              await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass))
          except HomeAssistantError as ex:
              _LOGGER.error("Error registering sensor: %s", ex)
              return empty_okay_response()
  
          register_signal = '{}_{}_register'.format(DOMAIN,
                                                    data[ATTR_SENSOR_TYPE])
          async_dispatcher_send(hass, register_signal, data)
  
          return webhook_response({'success': True},
                                  registration=registration, status=HTTP_CREATED,
                                  headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_UPDATE_SENSOR_STATES:
          resp = {}
          for sensor in data:
              entity_type = sensor[ATTR_SENSOR_TYPE]
  
              unique_id = sensor[ATTR_SENSOR_UNIQUE_ID]
  
              unique_store_key = "{}_{}".format(webhook_id, unique_id)
  
              if unique_store_key not in hass.data[DOMAIN][entity_type]:
                  _LOGGER.error("Refusing to update non-registered sensor: %s",
                                unique_store_key)
                  err_msg = '{} {} is not registered'.format(entity_type,
                                                             unique_id)
                  resp[unique_id] = {
                      'success': False,
                      'error': {
                          'code': ERR_SENSOR_NOT_REGISTERED,
                          'message': err_msg
                      }
                  }
                  continue
  
              entry = hass.data[DOMAIN][entity_type][unique_store_key]
  
              new_state = {**entry, **sensor}
  
              hass.data[DOMAIN][entity_type][unique_store_key] = new_state
  
              safe = savable_state(hass)
  
              try:
                  await hass.data[DOMAIN][DATA_STORE].async_save(safe)
              except HomeAssistantError as ex:
                  _LOGGER.error("Error updating mobile_app registration: %s", ex)
                  return empty_okay_response()
  
              async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state)
  
              resp[unique_id] = {'success': True}
  
          return webhook_response(resp, registration=registration,
                                  headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_GET_ZONES:
          zones = (hass.states.get(entity_id) for entity_id
                   in sorted(hass.states.async_entity_ids(ZONE_DOMAIN)))
          return webhook_response(list(zones), registration=registration,
                                  headers=headers)
  
      if webhook_type == WEBHOOK_TYPE_GET_CONFIG:
  
          hass_config = hass.config.as_dict()
  
          resp = {
              'latitude': hass_config['latitude'],
              'longitude': hass_config['longitude'],
              'elevation': hass_config['elevation'],
              'unit_system': hass_config['unit_system'],
              'location_name': hass_config['location_name'],
              'time_zone': hass_config['time_zone'],
              'components': hass_config['components'],
              'version': hass_config['version'],
              'theme_color': MANIFEST_JSON['theme_color'],
          }
  
          if CONF_CLOUDHOOK_URL in registration:
              resp[CONF_CLOUDHOOK_URL] = registration[CONF_CLOUDHOOK_URL]
  
          try:
              resp[CONF_REMOTE_UI_URL] = async_remote_ui_url(hass)
          except CloudNotAvailable:
              pass
  
          return webhook_response(resp, registration=registration,
                                  headers=headers)
  