Add button to add new FSP services to Files app.

This CL adds a FSP suggest dialog which can be shown by clicking the button at
the end of the left nav.

TEST=Tested manually by clicking on the new button in the left nav.
BUG=471722

Review URL: https://codereview.chromium.org/1056433003

Cr-Commit-Position: refs/heads/master@{#323462}
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 8a58738..1c179e6 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -247,6 +247,9 @@
   <message name="IDS_FILE_BROWSER_CLOSE_VOLUME_BUTTON_LABEL" desc="Title of the action for closing either an archive volume or a volume provided by an extension.">
     Close
   </message>
+  <message name="IDS_FILE_BROWSER_ADD_NEW_SERVICES_BUTTON_LABEL" desc="Title of the button in the left nav to add new services (file systems) to Files app.">
+    Add new services
+  </message>
   <message name="IDS_FILE_BROWSER_ACTION_VIEW" desc="Title of the action to view (no edit) a file.">
     View
   </message>
@@ -681,6 +684,9 @@
   <message name="IDS_FILE_BROWSER_SUGGEST_DIALOG_TITLE" desc="Title of the suggest app dialog, which shows the list of the apps which supports the selected file.">
     Select an app to open this file
   </message>
+  <message name="IDS_FILE_BROWSER_SUGGEST_DIALOG_FOR_PROVIDERS_TITLE" desc="Title of the suggest app dialog, which shows the list of the apps that can be added to the left nav of Files app.">
+    Available services
+  </message>
   <message name="IDS_FILE_BROWSER_SUGGEST_DIALOG_LINK_TO_WEBSTORE" desc="Text of the link to the app list on Chrome Webstore, which shows the more apps than in the suggest app dialog.">
     See more...
   </message>
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
index 9278fc5..6606bc72 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_strings.cc
@@ -263,6 +263,8 @@
              IDS_FILE_BROWSER_CHANGE_DEFAULT_MENU_ITEM);
   SET_STRING("CLOSE_VOLUME_BUTTON_LABEL",
              IDS_FILE_BROWSER_CLOSE_VOLUME_BUTTON_LABEL);
+  SET_STRING("ADD_NEW_SERVICES_BUTTON_LABEL",
+             IDS_FILE_BROWSER_ADD_NEW_SERVICES_BUTTON_LABEL);
 
   SET_STRING("CLOUD_IMPORT_TITLE",
              IDS_FILE_BROWSER_CLOUD_IMPORT_TITLE);
@@ -523,6 +525,8 @@
   SET_STRING("SUGGEST_DIALOG_LINK_TO_WEBSTORE",
              IDS_FILE_BROWSER_SUGGEST_DIALOG_LINK_TO_WEBSTORE);
   SET_STRING("SUGGEST_DIALOG_TITLE", IDS_FILE_BROWSER_SUGGEST_DIALOG_TITLE);
+  SET_STRING("SUGGEST_DIALOG_FOR_PROVIDERS_TITLE",
+             IDS_FILE_BROWSER_SUGGEST_DIALOG_FOR_PROVIDERS_TITLE);
   SET_STRING("SYNC_DELETE_WITHOUT_PERMISSION_ERROR",
              IDS_FILE_BROWSER_SYNC_DELETE_WITHOUT_PERMISSION_ERROR);
   SET_STRING("SYNC_FILE_NAME", IDS_FILE_BROWSER_SYNC_FILE_NAME);
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css
index ca39b5b..5b7a17d 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -189,7 +189,7 @@
   width: 36px;
 }
 
-#directory-tree .tree-row > .volume-icon {
+#directory-tree .tree-row > .item-icon {
   flex: none;
   height: 16px;
   width: 16px;
diff --git a/ui/file_manager/file_manager/foreground/css/file_types.css b/ui/file_manager/file_manager/foreground/css/file_types.css
index fb39d54..832173b 100644
--- a/ui/file_manager/file_manager/foreground/css/file_types.css
+++ b/ui/file_manager/file_manager/foreground/css/file_types.css
@@ -328,3 +328,9 @@
   /* Apply the rgb(90, 90, 90) mask. */
   -webkit-filter: contrast(0) brightness(0.7143);
 }
+
+.tree-row [command-icon='add-new-services'] {
+  background-image: -webkit-image-set(
+      url(../images/volumes/add.png) 1x,
+      url(../images/volumes/2x/add.png) 2x);
+}
diff --git a/ui/file_manager/file_manager/foreground/images/volumes/2x/add.png b/ui/file_manager/file_manager/foreground/images/volumes/2x/add.png
new file mode 100644
index 0000000..08e26c1
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/images/volumes/2x/add.png
Binary files differ
diff --git a/ui/file_manager/file_manager/foreground/images/volumes/add.png b/ui/file_manager/file_manager/foreground/images/volumes/add.png
new file mode 100644
index 0000000..9061c7d
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/images/volumes/add.png
Binary files differ
diff --git a/ui/file_manager/file_manager/foreground/js/cws_container_client.js b/ui/file_manager/file_manager/foreground/js/cws_container_client.js
index d177630e..650f3c1 100644
--- a/ui/file_manager/file_manager/foreground/js/cws_container_client.js
+++ b/ui/file_manager/file_manager/foreground/js/cws_container_client.js
@@ -4,26 +4,21 @@
 
 /**
  * @param {WebView} webView Web View tag.
- * @param {?string} ext File extension.
- * @param {?string} mime File mime type.
- * @param {?string} searchQuery Search query.
  * @param {number} width Width of the CWS widget.
  * @param {number} height Height of the CWS widget.
  * @param {string} url Share Url for an entry.
  * @param {string} target Target (scheme + host + port) of the widget.
+ * @param {Object<string, *>} options Options to be sent to the dialog host.
  * @constructor
  * @extends {cr.EventTarget}
  */
-function CWSContainerClient(
-    webView, ext, mime, searchQuery, width, height, url, target) {
+function CWSContainerClient(webView, width, height, url, target, options) {
   this.webView_ = webView;
-  this.ext_ = (ext && ext[0] == '.') ? ext.substr(1) : ext;
-  this.mime_ = mime;
-  this.searchQuery_ = searchQuery;
   this.width_ = width;
   this.height_ = height;
   this.url_ = url;
   this.target_ = target;
+  this.options_ = options;
 
   this.loaded_ = false;
   this.loading_ = false;
@@ -190,11 +185,10 @@
     v: 1
   };
 
-  if (this.searchQuery_) {
-    message['search_query'] = this.searchQuery_;
-  } else {
-    message['file_extension'] = this.ext_;
-    message['mime_type'] = this.mime_;
+  if (this.options_) {
+    Object.keys(this.options_).forEach(function(key) {
+      message[key] = this.options_[key];
+    }.bind(this));
   }
 
   this.postMessage_(message);
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
index 2ccc3b9..c760fb2 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -946,7 +946,10 @@
                            assert(this.metadataModel_),
                            fakeEntriesVisible);
     directoryTree.dataModel = new NavigationListModel(
-        this.volumeManager_, this.folderShortcutsModel_);
+        this.volumeManager_,
+        this.folderShortcutsModel_,
+        new NavigationModelCommandItem(
+          util.queryDecoratedElement('#add-new-services', cr.ui.Command)));
 
     this.ui_.initDirectoryTree(directoryTree);
   };
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index 0b8bdbe..85bf8a8f 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -1239,3 +1239,18 @@
   },
   canExecute: CommandUtil.canExecuteAlways
 });
+
+/**
+ * Shows a suggest dialog with new services to be added to the left nav.
+ * @type {Command}
+ */
+CommandHandler.COMMANDS_['add-new-services'] = /** @type {Command} */ ({
+  /**
+   * @param {!Event} event Command event.
+   * @param {!FileManager} fileManager FileManager to use.
+   */
+  execute: function(event, fileManager) {
+    fileManager.ui.suggestAppsDialog.showProviders(function() {});
+  },
+  canExecute: CommandUtil.canExecuteAlways
+});
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
index 5195f86..38f2671 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
@@ -11,24 +11,14 @@
   this.label_ = label;
 }
 
-NavigationModelItem.prototype = {
-  get label() { return this.label_; }
+NavigationModelItem.Type = {
+  SHORTCUT: 'shortcut',
+  VOLUME: 'volume',
+  COMMAND: 'command'
 };
 
-/**
- * Check whether given two model items are same.
- * @param {NavigationModelItem} item1 The first item to be compared.
- * @param {NavigationModelItem} item2 The second item to be compared.
- * @return {boolean} True if given two model items are same.
- */
-NavigationModelItem.isSame = function(item1, item2) {
-  if (item1.isVolume != item2.isVolume)
-    return false;
-
-  if (item1.isVolume)
-    return item1.volumeInfo === item2.volumeInfo;
-  else
-    return util.isSameEntry(item1.entry, item2.entry);
+NavigationModelItem.prototype = {
+  get label() { return this.label_; }
 };
 
 /**
@@ -38,18 +28,17 @@
  * @param {!DirectoryEntry} entry Entry. Cannot be null.
  * @constructor
  * @extends {NavigationModelItem}
+ * @struct
  */
 function NavigationModelShortcutItem(label, entry) {
   NavigationModelItem.call(this, label);
   this.entry_ = entry;
-  Object.freeze(this);
 }
 
 NavigationModelShortcutItem.prototype = {
   __proto__: NavigationModelItem.prototype,
   get entry() { return this.entry_; },
-  get isVolume() { return false; },
-  get isShortcut() { return true; }
+  get type() { return NavigationModelItem.Type.SHORTCUT; }
 };
 
 /**
@@ -59,6 +48,7 @@
  * @param {!VolumeInfo} volumeInfo Volume info for the volume. Cannot be null.
  * @constructor
  * @extends {NavigationModelItem}
+ * @struct
  */
 function NavigationModelVolumeItem(label, volumeInfo) {
   NavigationModelItem.call(this, label);
@@ -67,29 +57,49 @@
   // for determining executability of commands.
   this.volumeInfo_.resolveDisplayRoot(
       function() {}, function() {});
-  Object.freeze(this);
 }
 
 NavigationModelVolumeItem.prototype = {
   __proto__: NavigationModelItem.prototype,
   get volumeInfo() { return this.volumeInfo_; },
-  get isVolume() { return true; },
-  get isShortcut() { return false; }
+  get type() { return NavigationModelItem.Type.VOLUME; }
 };
 
 /**
- * A navigation list model. This model combines the 2 lists.
+ * Item of NavigationListModel for commands.
+ *
+ * @param {cr.ui.Command} command
+ * @constructor
+ * @extends {NavigationModelItem}
+ * @struct
+ */
+function NavigationModelCommandItem(command) {
+  NavigationModelItem.call(this, command.label);
+  this.command_ = command;
+}
+
+NavigationModelCommandItem.prototype = {
+  __proto__: NavigationModelItem.prototype,
+  get command() { return this.command_; },
+  get type() { return NavigationModelItem.Type.COMMAND; }
+};
+
+/**
+ * A navigation list model. This model combines multiple models.
  * @param {VolumeManagerWrapper} volumeManager VolumeManagerWrapper instance.
  * @param {(cr.ui.ArrayDataModel|FolderShortcutsDataModel)} shortcutListModel
  *     The list of folder shortcut.
+ * @param {NavigationModelCommandItem} commandModel, Command button at the
+ *     end of the list.
  * @constructor
  * @extends {cr.EventTarget}
  */
-function NavigationListModel(volumeManager, shortcutListModel) {
+function NavigationListModel(volumeManager, shortcutListModel, commandModel) {
   cr.EventTarget.call(this);
 
   this.volumeManager_ = volumeManager;
   this.shortcutListModel_ = shortcutListModel;
+  this.commandModel_ = commandModel;
 
   var volumeInfoToModelItem = function(volumeInfo) {
     return new NavigationModelVolumeItem(
@@ -242,10 +252,12 @@
  * @return {NavigationModelItem} The item at the given index.
  */
 NavigationListModel.prototype.item = function(index) {
-  var offset = this.volumeList_.length;
-  if (index < offset)
+  if (index < this.volumeList_.length)
     return this.volumeList_[index];
-  return this.shortcutList_[index - offset];
+  if (index < this.volumeList_.length + this.shortcutList_.length)
+    return this.shortcutList_[index - this.volumeList_.length];
+  if (index === this.length_() - 1)
+    return this.commandModel_;
 };
 
 /**
@@ -254,7 +266,8 @@
  * @private
  */
 NavigationListModel.prototype.length_ = function() {
-  return this.volumeList_.length + this.shortcutList_.length;
+  return this.volumeList_.length + this.shortcutList_.length +
+      (this.commandModel_ ? 1 : 0);
 };
 
 /**
@@ -277,12 +290,6 @@
  * @param {NavigationModelItem} modelItem The entry which is not found.
  */
 NavigationListModel.prototype.onItemNotFoundError = function(modelItem) {
-  if (modelItem.isVolume) {
-    // TODO(mtomasz, yoshiki): Implement when needed.
-    return;
-  }
-  if (modelItem.isShortcut) {
-    // For shortcuts, lets the shortcut model handle this situation.
+  if (modelItem.type ===  NavigationModelItem.Type.SHORTCUT)
     this.shortcutListModel_.onItemNotFoundError(modelItem.entry);
-  }
 };
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
index a699aa5..3230707 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -347,7 +347,7 @@
 
   // Sets up icons of the item.
   var icon = item.querySelector('.icon');
-  icon.classList.add('volume-icon');
+  icon.classList.add('item-icon');
   var location = tree.volumeManager.getLocationInfo(item.entry);
   if (location && location.rootType && location.isRootEntry) {
     icon.setAttribute('volume-type-icon', location.rootType);
@@ -505,7 +505,7 @@
  * @private
  */
 VolumeItem.prototype.setupIcon_ = function(icon, volumeInfo) {
-  icon.classList.add('volume-icon');
+  icon.classList.add('item-icon');
   if (volumeInfo.volumeType === VolumeManagerCommon.VolumeType.PROVIDED) {
     var backgroundImage = '-webkit-image-set(' +
         'url(chrome://extension-icon/' + volumeInfo.extensionId +
@@ -689,7 +689,7 @@
   item.innerHTML = TREE_ITEM_INNTER_HTML;
 
   var icon = item.querySelector('.icon');
-  icon.classList.add('volume-icon');
+  icon.classList.add('item-icon');
   icon.setAttribute('volume-type-icon', VolumeManagerCommon.VolumeType.DRIVE);
 
   if (tree.contextMenuForRootItems)
@@ -782,6 +782,77 @@
 };
 
 ////////////////////////////////////////////////////////////////////////////////
+// CommandItem
+
+/**
+ * A TreeItem which represents a command button.
+ * Command items are displayed as top-level children of DirectoryTree.
+ *
+ * @param {NavigationModelCommandItem} modelItem
+ * @param {DirectoryTree} tree Current tree, which contains this item.
+ * @extends {cr.ui.TreeItem}
+ * @constructor
+ */
+function CommandItem(modelItem, tree) {
+  var item = new cr.ui.TreeItem();
+  item.__proto__ = CommandItem.prototype;
+
+  item.parentTree_ = tree;
+  item.modelItem_ = modelItem;
+
+  item.innerHTML = TREE_ITEM_INNTER_HTML;
+
+  var icon = item.querySelector('.icon');
+  icon.classList.add('item-icon');
+  icon.setAttribute('command-icon', modelItem.command.id);
+
+  item.label = modelItem.label;
+  return item;
+}
+
+CommandItem.prototype = {
+  __proto__: cr.ui.TreeItem.prototype,
+  get entry() {
+    return null;
+  },
+  get modelItem() {
+    return this.modelItem_;
+  },
+  get labelElement() {
+    return this.firstElementChild.querySelector('.label');
+  }
+};
+
+/**
+ * @param {!DirectoryEntry|!FakeEntry} entry
+ * @return {boolean} True if the parent item is found.
+ */
+CommandItem.prototype.searchAndSelectByEntry = function(entry) {
+  return false;
+};
+
+/**
+ * @override
+ */
+CommandItem.prototype.handleClick = function(e) {
+  this.activate();
+};
+
+/**
+ * @param {!DirectoryEntry} entry
+ */
+CommandItem.prototype.selectByEntry = function(entry) {
+};
+
+/**
+ * Executes the command.
+ */
+CommandItem.prototype.activate = function() {
+  this.modelItem_.command.execute();
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
 // DirectoryTree
 
 /**
@@ -887,8 +958,9 @@
   for (var i = 0; i < this.items.length;) {
     var found = false;
     for (var j = 0; j < this.dataModel.length; j++) {
-      if (NavigationModelItem.isSame(this.items[i].modelItem,
-                                     this.dataModel.item(j))) {
+      // Comparison by references, which is safe here, as model items are long
+      // living.
+      if (this.items[i].modelItem === this.dataModel.item(j)) {
         found = true;
         break;
       }
@@ -907,21 +979,26 @@
   var itemIndex = 0;
   while (modelIndex < this.dataModel.length) {
     if (itemIndex < this.items.length &&
-        NavigationModelItem.isSame(this.items[itemIndex].modelItem,
-                                   this.dataModel.item(modelIndex))) {
+        this.items[itemIndex].modelItem === this.dataModel.item(modelIndex)) {
       if (recursive && this.items[itemIndex] instanceof VolumeItem)
         this.items[itemIndex].updateSubDirectories(true);
     } else {
       var modelItem = this.dataModel.item(modelIndex);
-      if (modelItem.isVolume) {
-        if (modelItem.volumeInfo.volumeType ===
-            VolumeManagerCommon.VolumeType.DRIVE) {
-          this.addAt(new DriveVolumeItem(modelItem, this), itemIndex);
-        } else {
-          this.addAt(new VolumeItem(modelItem, this), itemIndex);
-        }
-      } else {
-        this.addAt(new ShortcutItem(modelItem, this), itemIndex);
+      switch (modelItem.type) {
+        case NavigationModelItem.Type.VOLUME:
+          if (modelItem.volumeInfo.volumeType ===
+              VolumeManagerCommon.VolumeType.DRIVE) {
+            this.addAt(new DriveVolumeItem(modelItem, this), itemIndex);
+          } else {
+            this.addAt(new VolumeItem(modelItem, this), itemIndex);
+          }
+          break;
+        case NavigationModelItem.Type.SHORTCUT:
+          this.addAt(new ShortcutItem(modelItem, this), itemIndex);
+          break;
+        case NavigationModelItem.Type.COMMAND:
+          this.addAt(new CommandItem(modelItem, this), itemIndex);
+          break;
       }
     }
     itemIndex++;
diff --git a/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js b/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
index 2c7c38c..bd356a0 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/suggest_apps_dialog.js
@@ -21,12 +21,13 @@
 var WEBVIEW_HEIGHT = 480;
 
 /**
- * The URL of the widget.
+ * The URL of the widget showing suggested apps.
  * @type {string}
  * @const
  */
 var CWS_WIDGET_URL =
     'https://clients5.google.com/webstore/wall/cros-widget-container';
+
 /**
  * The origin of the widget.
  * @type {string}
@@ -62,6 +63,7 @@
   this.frame_.appendChild(this.buttons_);
 
   this.webstoreButton_ = this.document_.createElement('div');
+  this.webstoreButton_.hidden = true;
   this.webstoreButton_.id = 'webstore-button';
   this.webstoreButton_.innerHTML = str('SUGGEST_DIALOG_LINK_TO_WEBSTORE');
   this.webstoreButton_.addEventListener(
@@ -72,13 +74,11 @@
 
   this.webview_ = null;
   this.accessToken_ = null;
-  this.widgetUrl_ =
-      state.overrideCwsContainerUrlForTest || CWS_WIDGET_URL;
-  this.widgetOrigin_ =
-      state.overrideCwsContainerOriginForTest || CWS_WIDGET_ORIGIN;
+  this.widgetUrl_ = state.overrideCwsContainerUrlForTest || CWS_WIDGET_URL;
+  this.widgetOrigin_ = state.overrideCwsContainerOriginForTest ||
+      CWS_WIDGET_ORIGIN;
 
-  this.extension_ = null;
-  this.mime_ = null;
+  this.options_ = null;
   this.installingItemId_ = null;
   this.state_ = SuggestAppsDialog.State.UNINITIALIZED;
 
@@ -176,7 +176,7 @@
 /**
  * Shows suggest-apps dialog by file extension and mime.
  *
- * @param {string} extension Extension of the file.
+ * @param {string} extension Extension of the file with a trailing dot.
  * @param {string} mime Mime of the file.
  * @param {function(boolean)} onDialogClosed Called when the dialog is closed.
  *     The argument is the result of installation: true if an app is installed,
@@ -184,32 +184,58 @@
  */
 SuggestAppsDialog.prototype.showByExtensionAndMime =
     function(extension, mime, onDialogClosed) {
-  this.text_.hidden = true;
-  this.dialogText_ = '';
-  this.showInternal_(null, extension, mime, onDialogClosed);
+  assert(extension && extension[0] === '.');
+  this.showInternal_(
+      {
+        file_extension: extension.substr(1),
+        mime_type: mime
+      },
+      str('SUGGEST_DIALOG_TITLE'),
+      FileTasks.createWebStoreLink(extension, mime),
+      onDialogClosed);
+};
+
+/**
+ * Shows suggest-apps dialog for FSP API
+ * @param {function(boolean)} onDialogClosed Called when the dialog is closed.
+ *     The argument is the result of installation: true if an app is installed,
+ *     false otherwise.
+ */
+SuggestAppsDialog.prototype.showProviders = function(onDialogClosed) {
+  this.showInternal_(
+      {
+        file_system_provider: true
+      },
+      str('SUGGEST_DIALOG_FOR_PROVIDERS_TITLE'),
+      null /* webStoreUrl */,
+      onDialogClosed);
 };
 
 /**
  * Internal method to show a dialog. This should be called only from 'Suggest.
  * appDialog.showXxxx()' functions.
  *
- * @param {?string} filename Filename (without extension) of the file.
- * @param {?string} extension Extension of the file.
- * @param {?string} mime Mime of the file.
+ * @param {!Object<string, *>} options Map of options for the dialog.
+ * @param {string} title Title of the dialog.
+ * @param {?string} webStoreUrl Url for more results. Null if not supported.
  * @param {function(boolean)} onDialogClosed Called when the dialog is closed.
  *     The argument is the result of installation: true if an app is installed,
  *     false otherwise.
  *     @private
  */
 SuggestAppsDialog.prototype.showInternal_ =
-    function(filename, extension, mime, onDialogClosed) {
+    function(options, title, webStoreUrl, onDialogClosed) {
   if (this.state_ != SuggestAppsDialog.State.UNINITIALIZED) {
     console.error('Invalid state.');
     return;
   }
 
-  this.extension_ = extension;
-  this.mimeType_ = mime;
+  this.text_.hidden = true;
+  this.webstoreButton_.hidden = (webStoreUrl === null);
+  this.dialogText_ = '';
+
+  this.webStoreUrl_ = webStoreUrl;
+  this.options_ = options;
   this.onDialogClosed_ = onDialogClosed;
   this.state_ = SuggestAppsDialog.State.INITIALIZING;
 
@@ -224,7 +250,6 @@
       return;
     }
 
-    var title = str('SUGGEST_DIALOG_TITLE');
     var show = this.dialogText_ ?
         FileManagerDialogBase.prototype.showTitleAndTextDialog.call(
             this, title, this.dialogText_) :
@@ -259,9 +284,11 @@
 
     this.webviewClient_ = new CWSContainerClient(
         this.webview_,
-        extension, mime, filename,
-        WEBVIEW_WIDTH, WEBVIEW_HEIGHT,
-        this.widgetUrl_, this.widgetOrigin_);
+        WEBVIEW_WIDTH,
+        WEBVIEW_HEIGHT,
+        this.widgetUrl_,
+        this.widgetOrigin_,
+        this.options_);
     this.webviewClient_.addEventListener(CWSContainerClient.Events.LOADED,
                                          this.onWidgetLoaded_.bind(this));
     this.webviewClient_.addEventListener(CWSContainerClient.Events.LOAD_FAILED,
@@ -279,9 +306,9 @@
  * @private
  */
 SuggestAppsDialog.prototype.onWebstoreLinkClicked_ = function(e) {
-  var webStoreUrl =
-      FileTasks.createWebStoreLink(this.extension_, this.mimeType_);
-  util.visitURL(webStoreUrl);
+  if (!this.webStoreUrl_)
+    return;
+  util.visitURL(this.webStoreUrl_);
   this.state_ = SuggestAppsDialog.State.OPENING_WEBSTORE_CLOSING;
   this.hide();
 };
@@ -427,8 +454,8 @@
 
   this.webviewContainer_.removeChild(this.webview_);
   this.webview_ = null;
-  this.extension_ = null;
-  this.mime_ = null;
+  this.webStoreUrl_ = null;
+  this.options_ = null;
 
   FileManagerDialogBase.prototype.hide.call(
       this,
diff --git a/ui/file_manager/file_manager/main.html b/ui/file_manager/file_manager/main.html
index a8fab23..da3aebd 100644
--- a/ui/file_manager/file_manager/main.html
+++ b/ui/file_manager/file_manager/main.html
@@ -251,6 +251,9 @@
       <command id="inspect-console" shortcut="U+004A-Shift-Ctrl">
       <command id="inspect-element" shortcut="U+0043-Shift-Ctrl">
       <command id="inspect-background" shortcut="U+0042-Shift-Ctrl">
+
+      <command id="add-new-services"
+               i18n-values="label:ADD_NEW_SERVICES_BUTTON_LABEL">
     </commands>
 
     <cr-menu id="file-context-menu" class="chrome-menu" showShortcuts>
diff --git a/ui/file_manager/integration_tests/file_manager/copy_between_windows.js b/ui/file_manager/integration_tests/file_manager/copy_between_windows.js
index 4b72648..0235463 100644
--- a/ui/file_manager/integration_tests/file_manager/copy_between_windows.js
+++ b/ui/file_manager/integration_tests/file_manager/copy_between_windows.js
@@ -58,7 +58,7 @@
 };
 
 var REMOVABLE_VOLUME_QUERY = '#directory-tree > .tree-item > .tree-row ' +
-    '.volume-icon[volume-type-icon="removable"]';
+    '.item-icon[volume-type-icon="removable"]';
 
 testcase.copyBetweenWindowsDriveToLocal = function() {
   var windowId1;
diff --git a/ui/file_manager/integration_tests/file_manager/file_display.js b/ui/file_manager/integration_tests/file_manager/file_display.js
index e3d174f..54f9e48 100644
--- a/ui/file_manager/integration_tests/file_manager/file_display.js
+++ b/ui/file_manager/integration_tests/file_manager/file_display.js
@@ -55,7 +55,7 @@
 testcase.fileDisplayMtp = function() {
   var appId;
   var MTP_VOLUME_QUERY = '#directory-tree > .tree-item > .tree-row > ' +
-    '.volume-icon[volume-type-icon="mtp"]';
+    '.item-icon[volume-type-icon="mtp"]';
 
   StepsRunner.run([
     function() {
diff --git a/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js b/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js
index 9e35436..a61d5a4 100644
--- a/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js
+++ b/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js
@@ -15,7 +15,7 @@
 var TREEITEM_D = TREEITEM_DRIVE + '> .tree-children > div:nth-child(2) ';
 var TREEITEM_E = TREEITEM_D + '> .tree-children > div:nth-child(1) ';
 var EXPAND_ICON = '> .tree-row > .expand-icon';
-var VOLUME_ICON = '> .tree-row > .volume-icon';
+var ITEM_ICON = '> .tree-row > .item-icon';
 var EXPANDED_SUBTREE = '> .tree-children[expanded]';
 
 /**
@@ -42,13 +42,13 @@
     contents: [ENTRIES.directoryA.getExpectedRow(),
                ENTRIES.directoryD.getExpectedRow()],
     name: 'Drive',
-    navItem: '#tree-item-autogen-id-2',
+    navItem: '#tree-item-autogen-id-3',
     treeItem: TREEITEM_DRIVE
   },
   A: {
     contents: [ENTRIES.directoryB.getExpectedRow()],
     name: 'A',
-    navItem: '#tree-item-autogen-id-13',
+    navItem: '#tree-item-autogen-id-14',
     treeItem: TREEITEM_A
   },
   B: {
@@ -59,13 +59,13 @@
   C: {
     contents: [],
     name: 'C',
-    navItem: '#tree-item-autogen-id-13',
+    navItem: '#tree-item-autogen-id-14',
     treeItem: TREEITEM_C
   },
   D: {
     contents: [ENTRIES.directoryE.getExpectedRow()],
     name: 'D',
-    navItem: '#tree-item-autogen-id-12',
+    navItem: '#tree-item-autogen-id-13',
     treeItem: TREEITEM_D
   },
   E: {
@@ -134,9 +134,9 @@
  */
 function navigateToDirectory(windowId, directory) {
   return remoteCall.waitForElement(
-      windowId, directory.treeItem + VOLUME_ICON).then(function() {
+      windowId, directory.treeItem + ITEM_ICON).then(function() {
     return remoteCall.callRemoteTestUtil(
-        'fakeMouseClick', windowId, [directory.treeItem + VOLUME_ICON]);
+        'fakeMouseClick', windowId, [directory.treeItem + ITEM_ICON]);
   }).then(function(result) {
     chrome.test.assertTrue(result);
     return remoteCall.waitForFiles(windowId, directory.contents);