1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/media/webrtc_internals.h"
6
7#include "base/path_service.h"
8#include "base/strings/string_number_conversions.h"
9#include "content/browser/media/webrtc_internals_ui_observer.h"
10#include "content/browser/web_contents/web_contents_view.h"
11#include "content/public/browser/browser_thread.h"
12#include "content/public/browser/content_browser_client.h"
13#include "content/public/browser/notification_service.h"
14#include "content/public/browser/notification_types.h"
15#include "content/public/browser/power_save_blocker.h"
16#include "content/public/browser/render_process_host.h"
17#include "content/public/browser/web_contents.h"
18
19using base::ProcessId;
20using std::string;
21
22namespace content {
23
24namespace {
25
26static base::LazyInstance<WebRTCInternals>::Leaky g_webrtc_internals =
27    LAZY_INSTANCE_INITIALIZER;
28
29// Makes sure that |dict| has a ListValue under path "log".
30static base::ListValue* EnsureLogList(base::DictionaryValue* dict) {
31  base::ListValue* log = NULL;
32  if (!dict->GetList("log", &log)) {
33    log = new base::ListValue();
34    if (log)
35      dict->Set("log", log);
36  }
37  return log;
38}
39
40}  // namespace
41
42WebRTCInternals::WebRTCInternals()
43    : aec_dump_enabled_(false) {
44  registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED,
45                 NotificationService::AllBrowserContextsAndSources());
46// TODO(grunell): Shouldn't all the webrtc_internals* files be excluded from the
47// build if WebRTC is disabled?
48#if defined(ENABLE_WEBRTC)
49  aec_dump_file_path_ =
50      GetContentClient()->browser()->GetDefaultDownloadDirectory();
51  if (aec_dump_file_path_.empty()) {
52    // In this case the default path (|aec_dump_file_path_|) will be empty and
53    // the platform default path will be used in the file dialog (with no
54    // default file name). See SelectFileDialog::SelectFile. On Android where
55    // there's no dialog we'll fail to open the file.
56    VLOG(1) << "Could not get the download directory.";
57  } else {
58    aec_dump_file_path_ =
59        aec_dump_file_path_.Append(FILE_PATH_LITERAL("audio.aecdump"));
60  }
61#endif  // defined(ENABLE_WEBRTC)
62}
63
64WebRTCInternals::~WebRTCInternals() {
65}
66
67WebRTCInternals* WebRTCInternals::GetInstance() {
68  return g_webrtc_internals.Pointer();
69}
70
71void WebRTCInternals::OnAddPeerConnection(int render_process_id,
72                                          ProcessId pid,
73                                          int lid,
74                                          const string& url,
75                                          const string& rtc_configuration,
76                                          const string& constraints) {
77  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
78
79  base::DictionaryValue* dict = new base::DictionaryValue();
80  if (!dict)
81    return;
82
83  dict->SetInteger("rid", render_process_id);
84  dict->SetInteger("pid", static_cast<int>(pid));
85  dict->SetInteger("lid", lid);
86  dict->SetString("rtcConfiguration", rtc_configuration);
87  dict->SetString("constraints", constraints);
88  dict->SetString("url", url);
89  peer_connection_data_.Append(dict);
90  CreateOrReleasePowerSaveBlocker();
91
92  if (observers_.might_have_observers())
93    SendUpdate("addPeerConnection", dict);
94}
95
96void WebRTCInternals::OnRemovePeerConnection(ProcessId pid, int lid) {
97  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
98  for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
99    base::DictionaryValue* dict = NULL;
100    peer_connection_data_.GetDictionary(i, &dict);
101
102    int this_pid = 0;
103    int this_lid = 0;
104    dict->GetInteger("pid", &this_pid);
105    dict->GetInteger("lid", &this_lid);
106
107    if (this_pid != static_cast<int>(pid) || this_lid != lid)
108      continue;
109
110    peer_connection_data_.Remove(i, NULL);
111    CreateOrReleasePowerSaveBlocker();
112
113    if (observers_.might_have_observers()) {
114      base::DictionaryValue id;
115      id.SetInteger("pid", static_cast<int>(pid));
116      id.SetInteger("lid", lid);
117      SendUpdate("removePeerConnection", &id);
118    }
119    break;
120  }
121}
122
123void WebRTCInternals::OnUpdatePeerConnection(
124    ProcessId pid, int lid, const string& type, const string& value) {
125  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
126
127  for (size_t i = 0; i < peer_connection_data_.GetSize(); ++i) {
128    base::DictionaryValue* record = NULL;
129    peer_connection_data_.GetDictionary(i, &record);
130
131    int this_pid = 0, this_lid = 0;
132    record->GetInteger("pid", &this_pid);
133    record->GetInteger("lid", &this_lid);
134
135    if (this_pid != static_cast<int>(pid) || this_lid != lid)
136      continue;
137
138    // Append the update to the end of the log.
139    base::ListValue* log = EnsureLogList(record);
140    if (!log)
141      return;
142
143    base::DictionaryValue* log_entry = new base::DictionaryValue();
144    if (!log_entry)
145      return;
146
147    double epoch_time = base::Time::Now().ToJsTime();
148    string time = base::DoubleToString(epoch_time);
149    log_entry->SetString("time", time);
150    log_entry->SetString("type", type);
151    log_entry->SetString("value", value);
152    log->Append(log_entry);
153
154    if (observers_.might_have_observers()) {
155      base::DictionaryValue update;
156      update.SetInteger("pid", static_cast<int>(pid));
157      update.SetInteger("lid", lid);
158      update.MergeDictionary(log_entry);
159
160      SendUpdate("updatePeerConnection", &update);
161    }
162    return;
163  }
164}
165
166void WebRTCInternals::OnAddStats(base::ProcessId pid, int lid,
167                                 const base::ListValue& value) {
168  if (!observers_.might_have_observers())
169    return;
170
171  base::DictionaryValue dict;
172  dict.SetInteger("pid", static_cast<int>(pid));
173  dict.SetInteger("lid", lid);
174
175  base::ListValue* list = value.DeepCopy();
176  if (!list)
177    return;
178
179  dict.Set("reports", list);
180
181  SendUpdate("addStats", &dict);
182}
183
184void WebRTCInternals::OnGetUserMedia(int rid,
185                                     base::ProcessId pid,
186                                     const std::string& origin,
187                                     bool audio,
188                                     bool video,
189                                     const std::string& audio_constraints,
190                                     const std::string& video_constraints) {
191  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192
193  base::DictionaryValue* dict = new base::DictionaryValue();
194  dict->SetInteger("rid", rid);
195  dict->SetInteger("pid", static_cast<int>(pid));
196  dict->SetString("origin", origin);
197  if (audio)
198    dict->SetString("audio", audio_constraints);
199  if (video)
200    dict->SetString("video", video_constraints);
201
202  get_user_media_requests_.Append(dict);
203
204  if (observers_.might_have_observers())
205    SendUpdate("addGetUserMedia", dict);
206}
207
208void WebRTCInternals::AddObserver(WebRTCInternalsUIObserver *observer) {
209  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
210  observers_.AddObserver(observer);
211}
212
213void WebRTCInternals::RemoveObserver(WebRTCInternalsUIObserver *observer) {
214  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
215  observers_.RemoveObserver(observer);
216
217  // Disables the AEC recording if it is enabled and the last webrtc-internals
218  // page is going away.
219  if (aec_dump_enabled_ && !observers_.might_have_observers())
220    DisableAecDump();
221}
222
223void WebRTCInternals::UpdateObserver(WebRTCInternalsUIObserver* observer) {
224  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
225  if (peer_connection_data_.GetSize() > 0)
226    observer->OnUpdate("updateAllPeerConnections", &peer_connection_data_);
227
228  for (base::ListValue::iterator it = get_user_media_requests_.begin();
229       it != get_user_media_requests_.end();
230       ++it) {
231    observer->OnUpdate("addGetUserMedia", *it);
232  }
233}
234
235void WebRTCInternals::EnableAecDump(content::WebContents* web_contents) {
236#if defined(ENABLE_WEBRTC)
237#if defined(OS_ANDROID)
238  EnableAecDumpOnAllRenderProcessHosts();
239#else
240  select_file_dialog_ = ui::SelectFileDialog::Create(this, NULL);
241  select_file_dialog_->SelectFile(
242      ui::SelectFileDialog::SELECT_SAVEAS_FILE,
243      base::string16(),
244      aec_dump_file_path_,
245      NULL,
246      0,
247      FILE_PATH_LITERAL(""),
248      web_contents->GetTopLevelNativeWindow(),
249      NULL);
250#endif
251#endif
252}
253
254void WebRTCInternals::DisableAecDump() {
255#if defined(ENABLE_WEBRTC)
256  aec_dump_enabled_ = false;
257
258  // Tear down the dialog since the user has unchecked the AEC dump box.
259  select_file_dialog_ = NULL;
260
261  for (RenderProcessHost::iterator i(
262           content::RenderProcessHost::AllHostsIterator());
263       !i.IsAtEnd(); i.Advance()) {
264    i.GetCurrentValue()->DisableAecDump();
265  }
266#endif
267}
268
269void WebRTCInternals::ResetForTesting() {
270  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
271  observers_.Clear();
272  peer_connection_data_.Clear();
273  CreateOrReleasePowerSaveBlocker();
274  get_user_media_requests_.Clear();
275  aec_dump_enabled_ = false;
276}
277
278void WebRTCInternals::SendUpdate(const string& command, base::Value* value) {
279  DCHECK(observers_.might_have_observers());
280
281  FOR_EACH_OBSERVER(WebRTCInternalsUIObserver,
282                    observers_,
283                    OnUpdate(command, value));
284}
285
286void WebRTCInternals::Observe(int type,
287                              const NotificationSource& source,
288                              const NotificationDetails& details) {
289  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
290  DCHECK_EQ(type, NOTIFICATION_RENDERER_PROCESS_TERMINATED);
291  OnRendererExit(Source<RenderProcessHost>(source)->GetID());
292}
293
294void WebRTCInternals::FileSelected(const base::FilePath& path,
295                                   int /* unused_index */,
296                                   void* /*unused_params */) {
297#if defined(ENABLE_WEBRTC)
298  aec_dump_file_path_ = path;
299  EnableAecDumpOnAllRenderProcessHosts();
300#endif
301}
302
303void WebRTCInternals::FileSelectionCanceled(void* params) {
304#if defined(ENABLE_WEBRTC)
305  SendUpdate("aecRecordingFileSelectionCancelled", NULL);
306#endif
307}
308
309void WebRTCInternals::OnRendererExit(int render_process_id) {
310  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
311
312  // Iterates from the end of the list to remove the PeerConnections created
313  // by the exitting renderer.
314  for (int i = peer_connection_data_.GetSize() - 1; i >= 0; --i) {
315    base::DictionaryValue* record = NULL;
316    peer_connection_data_.GetDictionary(i, &record);
317
318    int this_rid = 0;
319    record->GetInteger("rid", &this_rid);
320
321    if (this_rid == render_process_id) {
322      if (observers_.might_have_observers()) {
323        int lid = 0, pid = 0;
324        record->GetInteger("lid", &lid);
325        record->GetInteger("pid", &pid);
326
327        base::DictionaryValue update;
328        update.SetInteger("lid", lid);
329        update.SetInteger("pid", pid);
330        SendUpdate("removePeerConnection", &update);
331      }
332      peer_connection_data_.Remove(i, NULL);
333    }
334  }
335  CreateOrReleasePowerSaveBlocker();
336
337  bool found_any = false;
338  // Iterates from the end of the list to remove the getUserMedia requests
339  // created by the exiting renderer.
340  for (int i = get_user_media_requests_.GetSize() - 1; i >= 0; --i) {
341    base::DictionaryValue* record = NULL;
342    get_user_media_requests_.GetDictionary(i, &record);
343
344    int this_rid = 0;
345    record->GetInteger("rid", &this_rid);
346
347    if (this_rid == render_process_id) {
348      get_user_media_requests_.Remove(i, NULL);
349      found_any = true;
350    }
351  }
352
353  if (found_any && observers_.might_have_observers()) {
354    base::DictionaryValue update;
355    update.SetInteger("rid", render_process_id);
356    SendUpdate("removeGetUserMediaForRenderer", &update);
357  }
358}
359
360#if defined(ENABLE_WEBRTC)
361void WebRTCInternals::EnableAecDumpOnAllRenderProcessHosts() {
362  aec_dump_enabled_ = true;
363  for (RenderProcessHost::iterator i(
364           content::RenderProcessHost::AllHostsIterator());
365       !i.IsAtEnd(); i.Advance()) {
366    i.GetCurrentValue()->EnableAecDump(aec_dump_file_path_);
367  }
368}
369#endif
370
371void WebRTCInternals::CreateOrReleasePowerSaveBlocker() {
372  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
373
374  if (peer_connection_data_.empty() && power_save_blocker_) {
375    DVLOG(1) << ("Releasing the block on application suspension since no "
376                 "PeerConnections are active anymore.");
377    power_save_blocker_.reset();
378  } else if (!peer_connection_data_.empty() && !power_save_blocker_) {
379    DVLOG(1) << ("Preventing the application from being suspended while one or "
380                 "more PeerConnections are active.");
381    power_save_blocker_ = content::PowerSaveBlocker::Create(
382        content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
383        "WebRTC has active PeerConnections.").Pass();
384  }
385}
386
387}  // namespace content
388