//
//  Copyright (C) 2015 Google, Inc.
//
//  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.
//

#pragma once

#include <functional>
#include <mutex>
#include <unordered_map>

#include <base/logging.h>
#include <base/macros.h>
#include <binder/IBinder.h>
#include <binder/IInterface.h>

namespace ipc {
namespace binder {

// Takes care of the grunt work of maintaining a list of remote interfaces,
// typically for the use of performing registered callbacks from a remote
// service. This is a native equivalent of the the android.os.RemoteCallbackList
// Java class. The type "T" must inherit from android::IInterface.
//
// TODO(armansito): We need to unit test this class. Right now it's defined as a
// simple template interface over the native libbinder types directly and we
// can't compile libbinder for host unless the Binder kernel module is enabled
// on the system. Figure out whether to:
//    1) write the Binder test-code in a new target-only executable;
//    2) conditionally compile into the host-native test suite if the Binder
//       module is built;
//    3) provide a test-only static library that re-defines the libbinder
//       classes as mocks.
// (See http://b/23386387)
//
// TODO(armansito): We should make this class non-final and the template
// interface abstract, so that code that depends on this can be unit tested
// against a mock version of this class.
//
// TODO(armansito): Consider submitting this class to frameworks/native/binder.
template <typename T>
class RemoteCallbackList final {
 public:
  RemoteCallbackList() = default;
  ~RemoteCallbackList();

  // Register and unregister a callback interface. Registering will
  // automatically start tracking for death notifications in case the remote
  // process hosting the Binder dies. In such a case, the Binder is
  // automatically removed from the list.
  bool Register(const android::sp<T>& callback);
  bool Unregister(const android::sp<T>& callback);

  // Calls the given function on each of the contained callbacks. The internal
  // mutex is locked for the duration of the iteration.
  void ForEach(const std::function<void(T*)>& callback);

 private:
  class CallbackDeathRecipient : public android::IBinder::DeathRecipient {
   public:
    CallbackDeathRecipient(const android::sp<T>& callback,
                           RemoteCallbackList* owner);

    android::sp<T> get_callback() const { return callback_; }

    // android::IBinder::DeathRecipient override:
    void binderDied(const android::wp<android::IBinder>& who) override;

   private:
    android::sp<T> callback_;
    RemoteCallbackList<T>* owner_;  // weak
  };

  // Typedef for internal map container. This keeps track of a given Binder and
  // a death receiver associated with it.
  using CallbackMap = std::unordered_map<android::IBinder*,
                                         android::sp<CallbackDeathRecipient>>;

  bool UnregisterInternal(typename CallbackMap::iterator iter);

  std::mutex map_lock_;
  CallbackMap callbacks_;

  DISALLOW_COPY_AND_ASSIGN(RemoteCallbackList);
};

// Template Implementation details below
// ========================================================

using android::IBinder;
using android::IInterface;
using android::sp;
using android::wp;

template <typename T>
RemoteCallbackList<T>::~RemoteCallbackList() {
  std::lock_guard<std::mutex> lock(map_lock_);
  for (auto iter = callbacks_.begin(); iter != callbacks_.end(); ++iter)
    UnregisterInternal(iter);
}

template <typename T>
bool RemoteCallbackList<T>::Register(const sp<T>& callback) {
  std::lock_guard<std::mutex> lock(map_lock_);

  sp<IBinder> binder = IInterface::asBinder(callback.get());
  if (callbacks_.find(binder.get()) != callbacks_.end()) {
    VLOG(1) << "Callback list already contains given callback";
    return false;
  }

  sp<CallbackDeathRecipient> dr(new CallbackDeathRecipient(callback, this));

  if (binder->linkToDeath(dr) != android::NO_ERROR) {
    LOG(ERROR) << "Failed to link death recipient to binder";
    return false;
  }

  callbacks_[binder.get()] = dr;

  VLOG(2) << "Callback successfully registered with list";

  return true;
}

template <typename T>
bool RemoteCallbackList<T>::Unregister(const sp<T>& callback) {
  std::lock_guard<std::mutex> lock(map_lock_);

  sp<IBinder> binder = IInterface::asBinder(callback.get());
  auto iter = callbacks_.find(binder.get());
  if (iter == callbacks_.end()) {
    VLOG(2) << "Given callback not registered with this list";
    return false;
  }

  return UnregisterInternal(iter);
}

template <typename T>
void RemoteCallbackList<T>::ForEach(const std::function<void(T*)>& callback) {
  std::lock_guard<std::mutex> lock(map_lock_);
  for (const auto& iter : callbacks_)
    callback(iter.second->get_callback().get());
}

template <typename T>
bool RemoteCallbackList<T>::UnregisterInternal(
    typename CallbackMap::iterator iter) {
  sp<CallbackDeathRecipient> dr = iter->second;
  callbacks_.erase(iter);

  if (IInterface::asBinder(dr->get_callback().get())->unlinkToDeath(dr) !=
      android::NO_ERROR) {
    LOG(ERROR) << "Failed to unlink death recipient from binder";

    // We removed the entry from |map_| but unlinkToDeath failed. There isn't
    // really much we can do here other than deleting the binder and returning
    // an error.
    return false;
  }

  VLOG(2) << "Callback successfully removed from list";

  return true;
}

template <typename T>
RemoteCallbackList<T>::CallbackDeathRecipient::CallbackDeathRecipient(
    const sp<T>& callback, RemoteCallbackList<T>* owner)
    : callback_(callback), owner_(owner) {
  CHECK(callback_.get());
  CHECK(owner_);
}

template <typename T>
void RemoteCallbackList<T>::CallbackDeathRecipient::binderDied(
    const wp<IBinder>& who) {
  VLOG(1) << "Received binderDied";

  sp<IBinder> binder = IInterface::asBinder(callback_.get());
  CHECK(who.unsafe_get() == binder.get());

  // Remove the callback but no need to call unlinkToDeath.
  std::lock_guard<std::mutex> lock(owner_->map_lock_);
  auto iter = owner_->callbacks_.find(binder.get());
  CHECK(iter != owner_->callbacks_.end());
  owner_->callbacks_.erase(iter);

  VLOG(1) << "Callback from dead process unregistered";
}

}  // namespace binder
}  // namespace ipc
