// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/midi/midi_manager.h" #include "base/bind.h" #include "base/location.h" #include "base/metrics/histogram_macros.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" namespace midi { namespace { using Sample = base::HistogramBase::Sample; using midi::mojom::PortState; using midi::mojom::Result; // If many users have more devices, this number will be increased. // But the number is expected to be big enough for now. constexpr Sample kMaxUmaDevices = 31; // Used to count events for usage histogram. enum class Usage { CREATED, CREATED_ON_UNSUPPORTED_PLATFORMS, SESSION_STARTED, SESSION_ENDED, INITIALIZED, INPUT_PORT_ADDED, OUTPUT_PORT_ADDED, // New items should be inserted here, and |MAX| should point the last item. MAX = OUTPUT_PORT_ADDED, }; void ReportUsage(Usage usage) { UMA_HISTOGRAM_ENUMERATION("Media.Midi.Usage", static_cast(usage), static_cast(Usage::MAX) + 1); } } // namespace MidiManager::MidiManager(MidiService* service) : initialization_state_(InitializationState::NOT_STARTED), finalized_(false), result_(Result::NOT_INITIALIZED), service_(service) { ReportUsage(Usage::CREATED); } MidiManager::~MidiManager() { // Make sure that Finalize() is called to clean up resources allocated on // the Chrome_IOThread. base::AutoLock auto_lock(lock_); CHECK(finalized_); } #if !defined(OS_MACOSX) && !defined(OS_WIN) && \ !(defined(USE_ALSA) && defined(USE_UDEV)) && !defined(OS_ANDROID) MidiManager* MidiManager::Create(MidiService* service) { ReportUsage(Usage::CREATED_ON_UNSUPPORTED_PLATFORMS); return new MidiManager(service); } #endif void MidiManager::Shutdown() { UMA_HISTOGRAM_ENUMERATION("Media.Midi.ResultOnShutdown", static_cast(result_), static_cast(Result::MAX) + 1); bool shutdown_synchronously = false; { base::AutoLock auto_lock(lock_); if (session_thread_runner_) { if (session_thread_runner_->BelongsToCurrentThread()) { shutdown_synchronously = true; } else { session_thread_runner_->PostTask( FROM_HERE, base::Bind(&MidiManager::ShutdownOnSessionThread, base::Unretained(this))); } session_thread_runner_ = nullptr; } else { finalized_ = true; } } if (shutdown_synchronously) ShutdownOnSessionThread(); } void MidiManager::StartSession(MidiManagerClient* client) { ReportUsage(Usage::SESSION_STARTED); bool needs_initialization = false; { base::AutoLock auto_lock(lock_); if (clients_.find(client) != clients_.end() || pending_clients_.find(client) != pending_clients_.end()) { // Should not happen. But just in case the renderer is compromised. NOTREACHED(); return; } // Do not accept a new request if Shutdown() was already called. if (finalized_) { client->CompleteStartSession(Result::INITIALIZATION_ERROR); return; } if (initialization_state_ == InitializationState::COMPLETED) { // Platform dependent initialization was already finished for previously // initialized clients. if (result_ == Result::OK) { AddInitialPorts(client); clients_.insert(client); } // Complete synchronously with |result_|; client->CompleteStartSession(result_); return; } // Do not accept a new request if the pending client list contains too // many clients. if (pending_clients_.size() >= kMaxPendingClientCount) { client->CompleteStartSession(Result::INITIALIZATION_ERROR); return; } if (initialization_state_ == InitializationState::NOT_STARTED) { // Set fields protected by |lock_| here and call StartInitialization() // later. needs_initialization = true; session_thread_runner_ = base::ThreadTaskRunnerHandle::Get(); initialization_state_ = InitializationState::STARTED; } pending_clients_.insert(client); } if (needs_initialization) { // Lazily initialize the MIDI back-end. TRACE_EVENT0("midi", "MidiManager::StartInitialization"); // CompleteInitialization() will be called asynchronously when platform // dependent initialization is finished. StartInitialization(); } } void MidiManager::EndSession(MidiManagerClient* client) { ReportUsage(Usage::SESSION_ENDED); // At this point, |client| can be in the destruction process, and calling // any method of |client| is dangerous. Calls on clients *must* be protected // by |lock_| to prevent race conditions. base::AutoLock auto_lock(lock_); clients_.erase(client); pending_clients_.erase(client); } void MidiManager::AccumulateMidiBytesSent(MidiManagerClient* client, size_t n) { base::AutoLock auto_lock(lock_); if (clients_.find(client) == clients_.end()) return; // Continue to hold lock_ here in case another thread is currently doing // EndSession. client->AccumulateMidiBytesSent(n); } void MidiManager::DispatchSendMidiData(MidiManagerClient* client, uint32_t port_index, const std::vector& data, double timestamp) { NOTREACHED(); } void MidiManager::StartInitialization() { CompleteInitialization(Result::NOT_SUPPORTED); } void MidiManager::CompleteInitialization(Result result) { bool complete_asynchronously = false; { base::AutoLock auto_lock(lock_); if (session_thread_runner_) { if (session_thread_runner_->BelongsToCurrentThread()) { complete_asynchronously = true; } else { session_thread_runner_->PostTask( FROM_HERE, base::Bind(&MidiManager::CompleteInitializationInternal, base::Unretained(this), result)); } } } if (complete_asynchronously) CompleteInitializationInternal(result); } void MidiManager::AddInputPort(const MidiPortInfo& info) { ReportUsage(Usage::INPUT_PORT_ADDED); base::AutoLock auto_lock(lock_); input_ports_.push_back(info); for (auto* client : clients_) client->AddInputPort(info); } void MidiManager::AddOutputPort(const MidiPortInfo& info) { ReportUsage(Usage::OUTPUT_PORT_ADDED); base::AutoLock auto_lock(lock_); output_ports_.push_back(info); for (auto* client : clients_) client->AddOutputPort(info); } void MidiManager::SetInputPortState(uint32_t port_index, PortState state) { base::AutoLock auto_lock(lock_); DCHECK_LT(port_index, input_ports_.size()); input_ports_[port_index].state = state; for (auto* client : clients_) client->SetInputPortState(port_index, state); } void MidiManager::SetOutputPortState(uint32_t port_index, PortState state) { base::AutoLock auto_lock(lock_); DCHECK_LT(port_index, output_ports_.size()); output_ports_[port_index].state = state; for (auto* client : clients_) client->SetOutputPortState(port_index, state); } void MidiManager::ReceiveMidiData(uint32_t port_index, const uint8_t* data, size_t length, double timestamp) { base::AutoLock auto_lock(lock_); for (auto* client : clients_) client->ReceiveMidiData(port_index, data, length, timestamp); } void MidiManager::CompleteInitializationInternal(Result result) { TRACE_EVENT0("midi", "MidiManager::CompleteInitialization"); ReportUsage(Usage::INITIALIZED); UMA_HISTOGRAM_ENUMERATION("Media.Midi.InputPorts", static_cast(input_ports_.size()), kMaxUmaDevices + 1); UMA_HISTOGRAM_ENUMERATION("Media.Midi.OutputPorts", static_cast(output_ports_.size()), kMaxUmaDevices + 1); base::AutoLock auto_lock(lock_); DCHECK(clients_.empty()); DCHECK_EQ(initialization_state_, InitializationState::STARTED); initialization_state_ = InitializationState::COMPLETED; result_ = result; for (auto* client : pending_clients_) { if (result_ == Result::OK) { AddInitialPorts(client); clients_.insert(client); } client->CompleteStartSession(result_); } pending_clients_.clear(); } void MidiManager::AddInitialPorts(MidiManagerClient* client) { lock_.AssertAcquired(); for (const auto& info : input_ports_) client->AddInputPort(info); for (const auto& info : output_ports_) client->AddOutputPort(info); } void MidiManager::ShutdownOnSessionThread() { Finalize(); base::AutoLock auto_lock(lock_); finalized_ = true; // Detach all clients so that they do not call MidiManager methods any more. for (auto* client : clients_) client->Detach(); } } // namespace midi