// Copyright 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Implementation of audio_daemon.h.

#include "audio_daemon.h"

#include <sysexits.h>

#include <base/bind.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/time/time.h>
#include <binderwrapper/binder_wrapper.h>
#include <linux/input.h>

#include "brillo_audio_service_impl.h"

namespace brillo {

static const char kAPSServiceName[] = "media.audio_policy";
static const char kInputDeviceDir[] = "/dev/input";
static const char kServiceName[] =
    "android.brillo.brilloaudioservice.BrilloAudioService";

AudioDaemon::~AudioDaemon() {}

void AudioDaemon::InitializeHandlers() {
  // Start and initialize the audio daemon handlers.
  audio_device_handler_ =
      std::shared_ptr<AudioDeviceHandler>(new AudioDeviceHandler());
  audio_volume_handler_ =
      std::unique_ptr<AudioVolumeHandler>(new AudioVolumeHandler());

  // Register a callback with the audio device handler to call when device state
  // changes.
  auto device_callback =
      base::Bind(&AudioDaemon::DeviceCallback, weak_ptr_factory_.GetWeakPtr());
  audio_device_handler_->RegisterDeviceCallback(device_callback);

  // Register a callback with the audio volume handler.
  auto volume_callback =
      base::Bind(&AudioDaemon::VolumeCallback, weak_ptr_factory_.GetWeakPtr());
  audio_volume_handler_->RegisterCallback(volume_callback);

  audio_device_handler_->Init(aps_);
  audio_volume_handler_->Init(aps_);

  // Poll on all files in kInputDeviceDir.
  base::FileEnumerator fenum(base::FilePath(kInputDeviceDir),
                             false /*recursive*/, base::FileEnumerator::FILES);
  for (base::FilePath name = fenum.Next(); !name.empty(); name = fenum.Next()) {
    base::File file(name, base::File::FLAG_OPEN | base::File::FLAG_READ);
    if (file.IsValid()) {
      MessageLoop* message_loop = MessageLoop::current();
      int fd = file.GetPlatformFile();
      // Move file to files_ and ensure that when binding we get a pointer from
      // the object in files_.
      files_.emplace(std::move(file));
      base::Closure file_callback =
          base::Bind(&AudioDaemon::EventCallback, weak_ptr_factory_.GetWeakPtr(),
                     &files_.top());
      message_loop->WatchFileDescriptor(fd, MessageLoop::kWatchRead,
                                        true /*persistent*/, file_callback);
    } else {
      LOG(WARNING) << "Could not open " << name.value() << " for reading. ("
                   << base::File::ErrorToString(file.error_details()) << ")";
    }
  }

  handlers_initialized_ = true;
  // Once the handlers have been initialized, we can register with service
  // manager.
  InitializeBrilloAudioService();
}

void AudioDaemon::InitializeBrilloAudioService() {
  brillo_audio_service_ = new BrilloAudioServiceImpl();
  brillo_audio_service_->RegisterHandlers(
      std::weak_ptr<AudioDeviceHandler>(audio_device_handler_),
      std::weak_ptr<AudioVolumeHandler>(audio_volume_handler_));
  android::BinderWrapper::Get()->RegisterService(kServiceName,
                                                 brillo_audio_service_);
  VLOG(1) << "Registered brilloaudioservice with the service manager.";
}

void AudioDaemon::ConnectToAPS() {
  android::BinderWrapper* binder_wrapper = android::BinderWrapper::Get();
  auto binder = binder_wrapper->GetService(kAPSServiceName);
  // If we didn't get the audio policy service, try again in 500 ms.
  if (!binder.get()) {
    LOG(INFO) << "Could not connect to audio policy service. Trying again...";
    brillo::MessageLoop::current()->PostDelayedTask(
        base::Bind(&AudioDaemon::ConnectToAPS, weak_ptr_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(500));
    return;
  }
  LOG(INFO) << "Connected to audio policy service.";
  binder_wrapper->RegisterForDeathNotifications(
      binder,
      base::Bind(&AudioDaemon::OnAPSDisconnected,
                 weak_ptr_factory_.GetWeakPtr()));
  VLOG(1) << "Registered death notification.";
  aps_ = android::interface_cast<android::IAudioPolicyService>(binder);
  if (!handlers_initialized_) {
    InitializeHandlers();
  } else {
    audio_device_handler_->APSConnect(aps_);
    audio_volume_handler_->APSConnect(aps_);
  }
}

void AudioDaemon::OnAPSDisconnected() {
  LOG(INFO) << "Audio policy service died. Will try to reconnect.";
  audio_device_handler_->APSDisconnect();
  audio_volume_handler_->APSDisconnect();
  aps_ = nullptr;
  ConnectToAPS();
}

// OnInit, we want to do the following:
//   - Get a binder to the audio policy service.
//   - Initialize the audio device and volume handlers.
//   - Set up polling on files in /dev/input.
int AudioDaemon::OnInit() {
  int exit_code = Daemon::OnInit();
  if (exit_code != EX_OK) return exit_code;
  // Initialize a binder wrapper.
  android::BinderWrapper::Create();
  // Initialize a binder watcher.
  binder_watcher_.Init();
  ConnectToAPS();
  return EX_OK;
}

void AudioDaemon::EventCallback(base::File* file) {
  input_event event;
  int bytes_read =
      file->ReadAtCurrentPos(reinterpret_cast<char*>(&event), sizeof(event));
  if (bytes_read != sizeof(event)) {
    LOG(WARNING) << "Couldn't read an input event.";
    return;
  }
  audio_device_handler_->ProcessEvent(event);
  audio_volume_handler_->ProcessEvent(event);
}

void AudioDaemon::DeviceCallback(
    AudioDeviceHandler::DeviceConnectionState state,
    const std::vector<int>& devices) {
  VLOG(1) << "Triggering device callback.";
  if (!brillo_audio_service_.get()) {
    LOG(ERROR) << "The Brillo audio service object is unavailble. Will try to "
               << "call the clients again once the service is up.";
    InitializeBrilloAudioService();
    DeviceCallback(state, devices);
    return;
  }
  if (state == AudioDeviceHandler::DeviceConnectionState::kDevicesConnected)
    brillo_audio_service_->OnDevicesConnected(devices);
  else
    brillo_audio_service_->OnDevicesDisconnected(devices);
}

void AudioDaemon::VolumeCallback(audio_stream_type_t stream,
                                 int previous_index,
                                 int current_index) {
  VLOG(1) << "Triggering volume button press callback.";
  if (!brillo_audio_service_.get()) {
    LOG(ERROR) << "The Brillo audio service object is unavailble. Will try to "
               << "call the clients again once the service is up.";
    InitializeBrilloAudioService();
    VolumeCallback(stream, previous_index, current_index);
    return;
  }
  brillo_audio_service_->OnVolumeChanged(stream, previous_index, current_index);
}

}  // namespace brillo