Site Settings Desktop: Implement media picker for camera/mic categories.

BUG=614277, 543635
CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2039833004
Cr-Commit-Position: refs/heads/master@{#398708}
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index 4f98295..3373c22 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -9,6 +9,7 @@
 <link rel="import" href="/settings_shared_css.html">
 <link rel="import" href="/site_settings/all_sites.html">
 <link rel="import" href="/site_settings/constants.html">
+<link rel="import" href="/site_settings/media_picker.html">
 <link rel="import" href="/site_settings_page/site_settings_page.html">
 
 <if expr="use_nss_certs">
@@ -149,6 +150,7 @@
               selected-site="{{selectedSite}}"
               current-route="{{currentRoute}}"
               category="{{ContentSettingsTypes.CAMERA}}">
+            <media-picker type="camera" class="media-picker"></media-picker>
           </site-settings-category>
         </settings-subpage>
       </template>
@@ -216,6 +218,7 @@
               selected-site="{{selectedSite}}"
               current-route="{{currentRoute}}"
               category="{{ContentSettingsTypes.MIC}}">
+            <media-picker type="mic" class="media-picker"></media-picker>
           </site-settings-category>
         </settings-subpage>
       </template>
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index 66667bc..a442a2f 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -462,6 +462,12 @@
                    file="languages_page/edit_dictionary_page.js"
                    type="chrome_html" />
       </if>
+      <structure name="IDR_SETTINGS_MEDIA_PICKER_HTML"
+                 file="site_settings/media_picker.html"
+                 type="chrome_html" />
+      <structure name="IDR_SETTINGS_MEDIA_PICKER_JS"
+                 file="site_settings/media_picker.js"
+                 type="chrome_html" />
       <structure name="IDR_SETTINGS_PASSWORDS_AND_FORMS_PAGE_HTML"
                  file="passwords_and_forms_page/passwords_and_forms_page.html"
                  type="chrome_html" />
diff --git a/chrome/browser/resources/settings/site_settings/media_picker.html b/chrome/browser/resources/settings/site_settings/media_picker.html
new file mode 100644
index 0000000..f7dd9dc
--- /dev/null
+++ b/chrome/browser/resources/settings/site_settings/media_picker.html
@@ -0,0 +1,39 @@
+<link rel="import" href="chrome://resources/html/polymer.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-dropdown-menu/paper-dropdown-menu.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-item/paper-item.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-listbox/paper-listbox.html">
+<link rel="import" href="/settings_shared_css.html">
+
+<dom-module id="media-picker">
+  <template>
+    <style include="settings-shared">
+      :host {
+        display: block;
+      }
+
+      paper-dropdown-menu {
+        --paper-input-container-input: {
+          font-size: inherit;
+        };
+
+        --paper-font-caption: {
+          font-size: inherit;
+        }
+      }
+    </style>
+    <div class="settings-box" id="picker" hidden>
+      <paper-dropdown-menu>
+        <paper-listbox id="mediaPicker" class="dropdown-content"
+            attr-for-selected="media-picker-value"
+            on-iron-activate="onMediaPickerActivate_">
+          <template is="dom-repeat" items="[[devices]]">
+            <paper-item media-picker-value$="[[item.id]]">
+              [[item.name]]
+            </paper-item>
+          </template>
+        </paper-listbox>
+      </paper-dropdown-menu>
+    </div>
+  </template>
+  <script src="media_picker.js"></script>
+</dom-module>
diff --git a/chrome/browser/resources/settings/site_settings/media_picker.js b/chrome/browser/resources/settings/site_settings/media_picker.js
new file mode 100644
index 0000000..847213c
--- /dev/null
+++ b/chrome/browser/resources/settings/site_settings/media_picker.js
@@ -0,0 +1,57 @@
+// Copyright 2016 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.
+
+/**
+ * @fileoverview
+ * 'media-picker' handles showing the dropdown allowing users to select the
+ * default camera/microphone.
+ */
+Polymer({
+  is: 'media-picker',
+
+  behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
+
+  properties: {
+    /**
+     * The type of media picker, either 'camera' or 'mic'.
+     */
+    type: String,
+
+    /**
+     * The devices available to pick from.
+     * @type {Array<MediaPickerEntry>}
+     */
+    devices: Array,
+  },
+
+  ready: function() {
+    this.addWebUIListener('updateDevicesMenu',
+        this.updateDevicesMenu_.bind(this));
+    this.browserProxy.getDefaultCaptureDevices(this.type);
+  },
+
+  /**
+   * Updates the microphone/camera devices menu with the given entries.
+   * @param {string} type The device type.
+   * @param {!Array<MediaPickerEntry>} devices List of available devices.
+   * @param {string} defaultDevice The unique id of the current default device.
+   */
+  updateDevicesMenu_: function(type, devices, defaultDevice) {
+    if (type != this.type)
+      return;
+
+    this.$.picker.hidden = devices.length == 0;
+    if (devices.length > 0) {
+      this.devices = devices;
+      this.$.mediaPicker.selected = defaultDevice;
+    }
+  },
+
+  /**
+   * A handler for when an item is selected in the media picker.
+   */
+  onMediaPickerActivate_: function(event) {
+    this.browserProxy.setDefaultCaptureDevice(this.type, event.detail.selected);
+  },
+});
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_category.html b/chrome/browser/resources/settings/site_settings/site_settings_category.html
index 3e94571..ed8c236 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_category.html
+++ b/chrome/browser/resources/settings/site_settings/site_settings_category.html
@@ -43,6 +43,8 @@
         --paper-toggle-button-label-spacing: 0;
       }
     </style>
+    <content select=".media-picker"></content>
+
     <div class="settings-box first two-line">
       <div class="start secondary">
         [[computeCategoryDesc(category, categoryEnabled, showRecommendation)]]
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
index e70d087..322c9c0 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
+++ b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
@@ -34,6 +34,12 @@
  */
 var SiteSettingsPref;
 
+/**
+ * @typedef {{name: string,
+ *            id: string}}
+ */
+var MediaPickerEntry;
+
 cr.define('settings', function() {
   /** @interface */
   function SiteSettingsPrefsBrowserProxy() {}
@@ -89,6 +95,20 @@
      * @return {!Promise<boolean>} True if the pattern is valid.
      */
     isPatternValid: function(pattern) {},
+
+    /**
+     * Gets the list of default capture devices for a given type of media. List
+     * is returned through a JS call to updateDevicesMenu.
+     * @param {string} type The type to look up.
+     */
+    getDefaultCaptureDevices: function(type) {},
+
+    /**
+     * Sets a default devices for a given type of media.
+     * @param {string} type The type of media to configure.
+     * @param {string} defaultValue The id of the media device to set.
+     */
+    setDefaultCaptureDevice: function(type, defaultValue) {},
   };
 
   /**
@@ -136,6 +156,15 @@
       return cr.sendWithPromise('isPatternValid', pattern);
     },
 
+    /** @override */
+    getDefaultCaptureDevices: function(type) {
+      chrome.send('getDefaultCaptureDevices', [type]);
+    },
+
+    /** @override */
+    setDefaultCaptureDevice: function(type, defaultValue) {
+      chrome.send('setDefaultCaptureDevice', [type, defaultValue]);
+    },
   };
 
   return {
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chrome/browser/ui/webui/settings/md_settings_ui.cc
index b2ddfde..2846484 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_ui.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/ui/webui/settings/reset_settings_handler.h"
 #include "chrome/browser/ui/webui/settings/search_engines_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_clear_browsing_data_handler.h"
+#include "chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 #include "chrome/browser/ui/webui/settings/settings_startup_pages_handler.h"
 #include "chrome/browser/ui/webui/settings/site_settings_handler.h"
@@ -65,6 +66,7 @@
   AddSettingsPageUIHandler(new DownloadsHandler());
   AddSettingsPageUIHandler(new FontHandler(web_ui));
   AddSettingsPageUIHandler(new LanguagesHandler(web_ui));
+  AddSettingsPageUIHandler(new MediaDevicesSelectionHandler(profile));
   AddSettingsPageUIHandler(new PeopleHandler(profile));
   AddSettingsPageUIHandler(new ProfileInfoHandler(profile));
   AddSettingsPageUIHandler(new SearchEnginesHandler(profile));
diff --git a/chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.cc b/chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.cc
new file mode 100644
index 0000000..9e8be216
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.cc
@@ -0,0 +1,158 @@
+// Copyright 2016 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 "chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.h"
+
+#include <stddef.h>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/prefs/pref_service.h"
+
+namespace {
+
+const char kAudio[] = "mic";
+const char kVideo[] = "camera";
+
+}  // namespace
+
+namespace settings {
+
+MediaDevicesSelectionHandler::MediaDevicesSelectionHandler(Profile* profile)
+    : profile_(profile), observer_(this) {
+}
+
+MediaDevicesSelectionHandler::~MediaDevicesSelectionHandler() {
+}
+
+void MediaDevicesSelectionHandler::OnJavascriptAllowed() {
+  // Register to the device observer list to get up-to-date device lists.
+  observer_.Add(MediaCaptureDevicesDispatcher::GetInstance());
+}
+
+void MediaDevicesSelectionHandler::OnJavascriptDisallowed() {
+  observer_.RemoveAll();
+}
+
+void MediaDevicesSelectionHandler::RegisterMessages() {
+  web_ui()->RegisterMessageCallback("getDefaultCaptureDevices",
+      base::Bind(&MediaDevicesSelectionHandler::GetDefaultCaptureDevices,
+                 base::Unretained(this)));
+  web_ui()->RegisterMessageCallback("setDefaultCaptureDevice",
+      base::Bind(&MediaDevicesSelectionHandler::SetDefaultCaptureDevice,
+                 base::Unretained(this)));
+}
+
+void MediaDevicesSelectionHandler::OnUpdateAudioDevices(
+    const content::MediaStreamDevices& devices) {
+  UpdateDevicesMenu(AUDIO, devices);
+}
+
+void MediaDevicesSelectionHandler::OnUpdateVideoDevices(
+    const content::MediaStreamDevices& devices) {
+  UpdateDevicesMenu(VIDEO, devices);
+}
+
+void MediaDevicesSelectionHandler::GetDefaultCaptureDevices(
+    const base::ListValue* args) {
+  DCHECK_EQ(1U, args->GetSize());
+  std::string type;
+  if (!args->GetString(0, &type)) {
+    NOTREACHED();
+    return;
+  }
+  DCHECK(!type.empty());
+
+  if (type == kAudio)
+    UpdateDevicesMenuForType(AUDIO);
+  else if (type == kVideo)
+    UpdateDevicesMenuForType(VIDEO);
+}
+
+void MediaDevicesSelectionHandler::SetDefaultCaptureDevice(
+    const base::ListValue* args) {
+  DCHECK_EQ(2U, args->GetSize());
+  std::string type, device;
+  if (!(args->GetString(0, &type) && args->GetString(1, &device))) {
+    NOTREACHED();
+    return;
+  }
+
+  DCHECK(!type.empty());
+  DCHECK(!device.empty());
+
+  PrefService* prefs = profile_->GetPrefs();
+  if (type == kAudio)
+    prefs->SetString(prefs::kDefaultAudioCaptureDevice, device);
+  else if (type == kVideo)
+    prefs->SetString(prefs::kDefaultVideoCaptureDevice, device);
+  else
+    NOTREACHED();
+}
+
+void MediaDevicesSelectionHandler::UpdateDevicesMenu(
+    DeviceType type, const content::MediaStreamDevices& devices) {
+  AllowJavascript();
+
+  // Get the default device unique id from prefs.
+  PrefService* prefs = profile_->GetPrefs();
+  std::string default_device;
+  std::string device_type;
+  switch (type) {
+    case AUDIO:
+      default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
+      device_type = kAudio;
+      break;
+    case VIDEO:
+      default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
+      device_type = kVideo;
+      break;
+  }
+
+  // Build the list of devices to send to JS.
+  std::string default_id;
+  base::ListValue device_list;
+  for (size_t i = 0; i < devices.size(); ++i) {
+    base::DictionaryValue* entry = new base::DictionaryValue();
+    entry->SetString("name", devices[i].name);
+    entry->SetString("id",  devices[i].id);
+    device_list.Append(entry);
+    if (devices[i].id == default_device)
+      default_id = default_device;
+  }
+
+  // Use the first device as the default device if the preferred default device
+  // does not exist in the OS.
+  if (!devices.empty() && default_id.empty())
+    default_id = devices[0].id;
+
+  base::StringValue default_value(default_id);
+  base::StringValue type_value(device_type);
+  CallJavascriptFunction("cr.webUIListenerCallback",
+                         base::StringValue("updateDevicesMenu"),
+                         type_value,
+                         device_list,
+                         default_value);
+}
+
+void MediaDevicesSelectionHandler::UpdateDevicesMenuForType(DeviceType type) {
+  content::MediaStreamDevices devices;
+  switch (type) {
+    case AUDIO:
+      devices = MediaCaptureDevicesDispatcher::GetInstance()->
+          GetAudioCaptureDevices();
+      break;
+    case VIDEO:
+      devices = MediaCaptureDevicesDispatcher::GetInstance()->
+          GetVideoCaptureDevices();
+      break;
+  }
+
+  UpdateDevicesMenu(type, devices);
+}
+
+}  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.h b/chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.h
new file mode 100644
index 0000000..7489dfe
--- /dev/null
+++ b/chrome/browser/ui/webui/settings/settings_media_devices_selection_handler.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2016 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.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_MEDIA_DEVICES_SELECTION_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_MEDIA_DEVICES_SELECTION_HANDLER_H_
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/media/media_capture_devices_dispatcher.h"
+#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
+#include "content/public/browser/web_contents.h"
+
+namespace settings {
+
+// Handler for media devices selection in content settings.
+class MediaDevicesSelectionHandler
+    : public MediaCaptureDevicesDispatcher::Observer,
+      public SettingsPageUIHandler {
+ public:
+  explicit MediaDevicesSelectionHandler(Profile* profile);
+  ~MediaDevicesSelectionHandler() override;
+
+  // SettingsPageUIHandler:
+  void OnJavascriptAllowed() override;
+  void OnJavascriptDisallowed() override;
+  void RegisterMessages() override;
+
+  // MediaCaptureDevicesDispatcher::Observer:
+  void OnUpdateAudioDevices(
+      const content::MediaStreamDevices& devices) override;
+  void OnUpdateVideoDevices(
+      const content::MediaStreamDevices& devices) override;
+
+ private:
+  enum DeviceType {
+    AUDIO,
+    VIDEO,
+  };
+
+  // Fetches the list of default capture devices.
+  void GetDefaultCaptureDevices(const base::ListValue* args);
+
+  // Sets the default audio/video capture device for media. |args| includes the
+  // media type (kAuudio/kVideo) and the unique id of the new default device
+  // that the user has chosen.
+  void SetDefaultCaptureDevice(const base::ListValue* args);
+
+  // Helpers methods to update the device menus.
+  void UpdateDevicesMenuForType(DeviceType type);
+  void UpdateDevicesMenu(DeviceType type,
+                         const content::MediaStreamDevices& devices);
+
+  Profile* profile_;  // Weak pointer.
+
+  ScopedObserver<MediaCaptureDevicesDispatcher,
+                 MediaCaptureDevicesDispatcher::Observer> observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaDevicesSelectionHandler);
+};
+
+}  // namespace settings
+
+#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SETTINGS_MEDIA_DEVICES_SELECTION_HANDLER_H_
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index b06c8d8..ed1d8cb 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -2056,6 +2056,8 @@
       'browser/ui/webui/settings/settings_clear_browsing_data_handler.h',
       'browser/ui/webui/settings/settings_manage_profile_handler.cc',
       'browser/ui/webui/settings/settings_manage_profile_handler.h',
+      'browser/ui/webui/settings/settings_media_devices_selection_handler.cc',
+      'browser/ui/webui/settings/settings_media_devices_selection_handler.h',
       'browser/ui/webui/settings/settings_page_ui_handler.cc',
       'browser/ui/webui/settings/settings_page_ui_handler.h',
       'browser/ui/webui/settings/settings_startup_pages_handler.cc',