Move external install warning logic from ExtService to ExternalInstallManager

Move the logic to create warnings about externally-installed extensions from
ExtensionService into ExternalInstallManager.

Next step: Move tests from extension_service_unittest.

BUG=378042
BUG=351891

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@281071 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 47076d0..42cad4f 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -109,21 +109,6 @@
 
 namespace {
 
-// Histogram values for logging events related to externally installed
-// extensions.
-enum ExternalExtensionEvent {
-  EXTERNAL_EXTENSION_INSTALLED = 0,
-  EXTERNAL_EXTENSION_IGNORED,
-  EXTERNAL_EXTENSION_REENABLED,
-  EXTERNAL_EXTENSION_UNINSTALLED,
-  EXTERNAL_EXTENSION_BUCKET_BOUNDARY,
-};
-
-#if defined(ENABLE_EXTENSIONS)
-// Prompt the user this many times before considering an extension acknowledged.
-const int kMaxExtensionAcknowledgePromptCount = 3;
-#endif
-
 // Wait this many seconds after an extensions becomes idle before updating it.
 const int kUpdateIdleDelay = 5;
 
@@ -297,8 +282,6 @@
       browser_terminating_(false),
       installs_delayed_for_gc_(false),
       is_first_run_(false),
-      external_install_manager_(
-          new extensions::ExternalInstallManager(profile_)),
       shared_module_service_(new extensions::SharedModuleService(profile_)) {
   CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
@@ -364,6 +347,8 @@
 
   error_controller_.reset(
       new extensions::ExtensionErrorController(profile_, is_first_run_));
+  external_install_manager_.reset(
+      new extensions::ExternalInstallManager(profile_, is_first_run_));
 
 #if defined(ENABLE_EXTENSIONS)
   extension_action_storage_manager_.reset(
@@ -562,7 +547,7 @@
       installer->set_grant_permissions(false);
     creation_flags = pending_extension_info->creation_flags();
     if (pending_extension_info->mark_acknowledged())
-      AcknowledgeExternalExtension(id);
+      external_install_manager_->AcknowledgeExternalExtension(id);
   } else if (extension) {
     installer->set_install_source(extension->location());
   }
@@ -746,20 +731,6 @@
 
   system_->install_verifier()->Remove(extension->id());
 
-  if (IsUnacknowledgedExternalExtension(extension.get())) {
-    UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
-                              EXTERNAL_EXTENSION_UNINSTALLED,
-                              EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    if (extensions::ManifestURL::UpdatesFromGallery(extension.get())) {
-      UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
-                                EXTERNAL_EXTENSION_UNINSTALLED,
-                                EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    } else {
-      UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
-                                EXTERNAL_EXTENSION_UNINSTALLED,
-                                EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    }
-  }
   UMA_HISTOGRAM_ENUMERATION("Extensions.UninstallType",
                             extension->GetType(), 100);
   RecordPermissionMessagesHistogram(extension.get(),
@@ -860,22 +831,6 @@
   if (!extension)
     return;
 
-  if (IsUnacknowledgedExternalExtension(extension)) {
-    UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
-                              EXTERNAL_EXTENSION_REENABLED,
-                              EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    if (extensions::ManifestURL::UpdatesFromGallery(extension)) {
-      UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
-                                EXTERNAL_EXTENSION_REENABLED,
-                                EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    } else {
-      UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
-                                EXTERNAL_EXTENSION_REENABLED,
-                                EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    }
-    AcknowledgeExternalExtension(extension->id());
-  }
-
   // Move it over to the enabled list.
   registry_->AddEnabled(make_scoped_refptr(extension));
   registry_->RemoveDisabled(extension->id());
@@ -1288,78 +1243,10 @@
 
   error_controller_->ShowErrorIfNeeded();
 
-  UpdateExternalExtensionAlert();
+  external_install_manager_->UpdateExternalExtensionAlert();
 #endif
 }
 
-void ExtensionService::AcknowledgeExternalExtension(const std::string& id) {
-  extension_prefs_->AcknowledgeExternalExtension(id);
-  UpdateExternalExtensionAlert();
-}
-
-bool ExtensionService::IsUnacknowledgedExternalExtension(
-    const Extension* extension) {
-  if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled())
-    return false;
-
-  return (Manifest::IsExternalLocation(extension->location()) &&
-          !extension_prefs_->IsExternalExtensionAcknowledged(extension->id()) &&
-          !(extension_prefs_->GetDisableReasons(extension->id()) &
-                Extension::DISABLE_SIDELOAD_WIPEOUT));
-}
-
-void ExtensionService::UpdateExternalExtensionAlert() {
-#if defined(ENABLE_EXTENSIONS)
-  if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled())
-    return;
-
-  const Extension* extension = NULL;
-  const ExtensionSet& disabled_extensions = registry_->disabled_extensions();
-  for (ExtensionSet::const_iterator iter = disabled_extensions.begin();
-       iter != disabled_extensions.end(); ++iter) {
-    const Extension* e = iter->get();
-    if (IsUnacknowledgedExternalExtension(e)) {
-      extension = e;
-      break;
-    }
-  }
-
-  if (extension) {
-    if (!external_install_manager_->HasExternalInstallError()) {
-      if (extension_prefs_->IncrementAcknowledgePromptCount(extension->id()) >
-              kMaxExtensionAcknowledgePromptCount) {
-        // Stop prompting for this extension, and check if there's another
-        // one that needs prompting.
-        extension_prefs_->AcknowledgeExternalExtension(extension->id());
-        UpdateExternalExtensionAlert();
-        UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
-                                  EXTERNAL_EXTENSION_IGNORED,
-                                  EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-        if (extensions::ManifestURL::UpdatesFromGallery(extension)) {
-          UMA_HISTOGRAM_ENUMERATION(
-              "Extensions.ExternalExtensionEventWebstore",
-              EXTERNAL_EXTENSION_IGNORED,
-              EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-        } else {
-          UMA_HISTOGRAM_ENUMERATION(
-              "Extensions.ExternalExtensionEventNonWebstore",
-              EXTERNAL_EXTENSION_IGNORED,
-              EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-        }
-        return;
-      }
-      if (is_first_run_)
-        extension_prefs_->SetExternalInstallFirstRun(extension->id());
-      // first_run is true if the extension was installed during a first run
-      // (even if it's post-first run now).
-      bool first_run = extension_prefs_->IsExternalInstallFirstRun(
-          extension->id());
-      external_install_manager_->AddExternalInstallError(extension, first_run);
-    }
-  }
-#endif  // defined(ENABLE_EXTENSIONS)
-}
-
 void ExtensionService::UnloadExtension(
     const std::string& extension_id,
     UnloadedExtensionInfo::Reason reason) {
@@ -1775,11 +1662,6 @@
                               extension->location(), Manifest::NUM_LOCATIONS);
   }
 
-  // Certain extension locations are specific enough that we can
-  // auto-acknowledge any extension that came from one of them.
-  if (Manifest::IsPolicyLocation(extension->location()) ||
-      extension->location() == Manifest::EXTERNAL_COMPONENT)
-    AcknowledgeExternalExtension(extension->id());
   const Extension::State initial_state =
       initial_enable ? Extension::ENABLED : Extension::DISABLED;
   if (ShouldDelayExtensionUpdate(
@@ -1920,8 +1802,6 @@
   registry_->TriggerOnWillBeInstalled(
       extension, is_update, from_ephemeral, old_name);
 
-  bool unacknowledged_external = IsUnacknowledgedExternalExtension(extension);
-
   // Unpacked extensions default to allowing file access, but if that has been
   // overridden, don't reset the value.
   if (Manifest::ShouldAlwaysAllowFileAccess(extension->location()) &&
@@ -1934,30 +1814,10 @@
   // Notify observers that need to know when an installation is complete.
   registry_->TriggerOnInstalled(extension);
 
-  // If this is a new external extension that was disabled, alert the user
-  // so he can reenable it. We do this last so that it has already been
-  // added to our list of extensions.
-  if (unacknowledged_external && !is_update) {
-    UpdateExternalExtensionAlert();
-    UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
-                              EXTERNAL_EXTENSION_INSTALLED,
-                              EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    if (extensions::ManifestURL::UpdatesFromGallery(extension)) {
-      UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
-                                EXTERNAL_EXTENSION_INSTALLED,
-                                EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    } else {
-      UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
-                                EXTERNAL_EXTENSION_INSTALLED,
-                                EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
-    }
-  }
-
   // Check extensions that may have been delayed only because this shared module
   // was not available.
-  if (SharedModuleInfo::IsSharedModule(extension)) {
+  if (SharedModuleInfo::IsSharedModule(extension))
     MaybeFinishDelayedInstallations();
-  }
 }
 
 void ExtensionService::PromoteEphemeralApp(
@@ -2120,7 +1980,7 @@
   // notification on installation. For such extensions, mark them acknowledged
   // now to suppress the notification.
   if (mark_acknowledged)
-    AcknowledgeExternalExtension(id);
+    external_install_manager_->AcknowledgeExternalExtension(id);
 
   return true;
 #else
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index 943e3e3..db8611c 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -313,13 +313,6 @@
   // Changes sequenced task runner for crx installation tasks to |task_runner|.
   void SetFileTaskRunnerForTesting(base::SequencedTaskRunner* task_runner);
 
-  // Checks if there are any new external extensions to notify the user about.
-  void UpdateExternalExtensionAlert();
-
-  // Given a (presumably just-installed) extension id, mark that extension as
-  // acknowledged.
-  void AcknowledgeExternalExtension(const std::string& id);
-
   // Postpone installations so that we don't have to worry about race
   // conditions.
   void OnGarbageCollectIsolatedStorageStart();
@@ -471,11 +464,6 @@
   // external extensions.
   void OnAllExternalProvidersReady();
 
-  // Returns true if this extension is an external one that has yet to be
-  // marked as acknowledged.
-  bool IsUnacknowledgedExternalExtension(
-      const extensions::Extension* extension);
-
   // Return true if the sync type of |extension| matches |type|.
   void OnExtensionInstallPrefChanged();
 
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 25907ff6..8bec09dd 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -6607,7 +6607,7 @@
       new MockExtensionProvider(service(), Manifest::EXTERNAL_PREF);
   AddMockExternalProvider(provider);
 
-  service()->UpdateExternalExtensionAlert();
+  service()->external_install_manager()->UpdateExternalExtensionAlert();
   // Should return false, meaning there aren't any extensions that the user
   // needs to know about.
   EXPECT_FALSE(
@@ -6719,19 +6719,22 @@
 
   service()->EnableExtension(page_action);
   EXPECT_TRUE(service()->external_install_manager()->HasExternalInstallError());
-  EXPECT_FALSE(
-      service()->external_install_manager()->HasExternalInstallBubble());
+  EXPECT_FALSE(service()
+                   ->external_install_manager()
+                   ->HasExternalInstallBubbleForTesting());
 
   service()->EnableExtension(theme_crx);
   EXPECT_TRUE(service()->external_install_manager()->HasExternalInstallError());
-  EXPECT_FALSE(
-      service()->external_install_manager()->HasExternalInstallBubble());
+  EXPECT_FALSE(service()
+                   ->external_install_manager()
+                   ->HasExternalInstallBubbleForTesting());
 
   service()->EnableExtension(good_crx);
   EXPECT_FALSE(
       service()->external_install_manager()->HasExternalInstallError());
-  EXPECT_FALSE(
-      service()->external_install_manager()->HasExternalInstallBubble());
+  EXPECT_FALSE(service()
+                   ->external_install_manager()
+                   ->HasExternalInstallBubbleForTesting());
 }
 
 // Test that there is a bubble for external extensions that update
@@ -6762,8 +6765,9 @@
   service()->CheckForExternalUpdates();
   observer.Wait();
   EXPECT_TRUE(service()->external_install_manager()->HasExternalInstallError());
-  EXPECT_TRUE(
-      service()->external_install_manager()->HasExternalInstallBubble());
+  EXPECT_TRUE(service()
+                  ->external_install_manager()
+                  ->HasExternalInstallBubbleForTesting());
   EXPECT_FALSE(service()->IsExtensionEnabled(updates_from_webstore));
 }
 
@@ -6790,8 +6794,9 @@
   service()->CheckForExternalUpdates();
   observer.Wait();
   EXPECT_TRUE(service()->external_install_manager()->HasExternalInstallError());
-  EXPECT_FALSE(
-      service()->external_install_manager()->HasExternalInstallBubble());
+  EXPECT_FALSE(service()
+                   ->external_install_manager()
+                   ->HasExternalInstallBubbleForTesting());
   EXPECT_FALSE(service()->IsExtensionEnabled(updates_from_webstore));
 }
 
diff --git a/chrome/browser/extensions/external_install_error.cc b/chrome/browser/extensions/external_install_error.cc
index 6b35e54..a08a40c 100644
--- a/chrome/browser/extensions/external_install_error.cc
+++ b/chrome/browser/extensions/external_install_error.cc
@@ -295,15 +295,6 @@
   }
 }
 
-void ExternalInstallError::AcknowledgeExtension() {
-  const Extension* extension = GetExtension();
-  if (extension) {
-    ExtensionSystem::Get(browser_context_)
-        ->extension_service()
-        ->AcknowledgeExternalExtension(extension->id());
-  }
-}
-
 void ExternalInstallError::ShowDialog(Browser* browser) {
   DCHECK(install_ui_.get());
   DCHECK(prompt_.get());
diff --git a/chrome/browser/extensions/external_install_error.h b/chrome/browser/extensions/external_install_error.h
index 5704a8f..ff65e498 100644
--- a/chrome/browser/extensions/external_install_error.h
+++ b/chrome/browser/extensions/external_install_error.h
@@ -54,9 +54,6 @@
   virtual void InstallUIProceed() OVERRIDE;
   virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
 
-  // Acknowledge the associated external extension.
-  void AcknowledgeExtension();
-
   // Show the associated dialog. This should only be called once the dialog is
   // ready.
   void ShowDialog(Browser* browser);
diff --git a/chrome/browser/extensions/external_install_manager.cc b/chrome/browser/extensions/external_install_manager.cc
index dcdf764f..daf3e72 100644
--- a/chrome/browser/extensions/external_install_manager.cc
+++ b/chrome/browser/extensions/external_install_manager.cc
@@ -7,22 +7,63 @@
 #include <string>
 
 #include "base/logging.h"
+#include "base/metrics/histogram.h"
 #include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/external_install_error.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/manifest_url_handler.h"
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_source.h"
+#include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
-#include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/manifest.h"
 
 namespace extensions {
 
+namespace {
+
+// Histogram values for logging events related to externally installed
+// extensions.
+enum ExternalExtensionEvent {
+  EXTERNAL_EXTENSION_INSTALLED = 0,
+  EXTERNAL_EXTENSION_IGNORED,
+  EXTERNAL_EXTENSION_REENABLED,
+  EXTERNAL_EXTENSION_UNINSTALLED,
+  EXTERNAL_EXTENSION_BUCKET_BOUNDARY,
+};
+
+#if defined(ENABLE_EXTENSIONS)
+// Prompt the user this many times before considering an extension acknowledged.
+const int kMaxExtensionAcknowledgePromptCount = 3;
+#endif
+
+void LogExternalExtensionEvent(const Extension* extension,
+                               ExternalExtensionEvent event) {
+  UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
+                            event,
+                            EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
+  if (ManifestURL::UpdatesFromGallery(extension)) {
+    UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
+                              event,
+                              EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
+  } else {
+    UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
+                              event,
+                              EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
+  }
+}
+
+}  // namespace
+
 ExternalInstallManager::ExternalInstallManager(
-    content::BrowserContext* browser_context)
-    : browser_context_(browser_context), extension_registry_observer_(this) {
+    content::BrowserContext* browser_context,
+    bool is_first_run)
+    : browser_context_(browser_context),
+      is_first_run_(is_first_run),
+      extension_prefs_(ExtensionPrefs::Get(browser_context_)),
+      extension_registry_observer_(this) {
   DCHECK(browser_context_);
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
   registrar_.Add(
@@ -39,33 +80,79 @@
   if (HasExternalInstallError())
     return;  // Only have one external install error at a time.
 
-  if (ManifestURL::UpdatesFromGallery(extension) && !is_new_profile) {
-    error_.reset(new ExternalInstallError(browser_context_,
-                                          extension->id(),
-                                          ExternalInstallError::BUBBLE_ALERT,
-                                          this));
-  } else {
-    error_.reset(new ExternalInstallError(browser_context_,
-                                          extension->id(),
-                                          ExternalInstallError::MENU_ALERT,
-                                          this));
-  }
+  ExternalInstallError::AlertType alert_type =
+      (ManifestURL::UpdatesFromGallery(extension) && !is_new_profile)
+          ? ExternalInstallError::BUBBLE_ALERT
+          : ExternalInstallError::MENU_ALERT;
+
+  error_.reset(new ExternalInstallError(
+      browser_context_, extension->id(), alert_type, this));
 }
 
 void ExternalInstallManager::RemoveExternalInstallError() {
-  if (error_.get()) {
-    error_.reset();
-    ExtensionSystem::Get(browser_context_)
-        ->extension_service()
-        ->UpdateExternalExtensionAlert();
-  }
+  error_.reset();
+  UpdateExternalExtensionAlert();
 }
 
 bool ExternalInstallManager::HasExternalInstallError() const {
   return error_.get() != NULL;
 }
 
-bool ExternalInstallManager::HasExternalInstallBubble() const {
+void ExternalInstallManager::UpdateExternalExtensionAlert() {
+#if defined(ENABLE_EXTENSIONS)
+  // If the feature is not enabled, or there is already an error displayed, do
+  // nothing.
+  if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled() ||
+      HasExternalInstallError()) {
+    return;
+  }
+
+  // Look for any extensions that were disabled because of being unacknowledged
+  // external extensions.
+  const Extension* extension = NULL;
+  const ExtensionSet& disabled_extensions =
+      ExtensionRegistry::Get(browser_context_)->disabled_extensions();
+  for (ExtensionSet::const_iterator iter = disabled_extensions.begin();
+       iter != disabled_extensions.end();
+       ++iter) {
+    if (IsUnacknowledgedExternalExtension(iter->get())) {
+      extension = iter->get();
+      break;
+    }
+  }
+
+  if (!extension)
+    return;  // No unacknowledged external extensions.
+
+  // Otherwise, warn the user about the suspicious extension.
+  if (extension_prefs_->IncrementAcknowledgePromptCount(extension->id()) >
+      kMaxExtensionAcknowledgePromptCount) {
+    // Stop prompting for this extension and record metrics.
+    extension_prefs_->AcknowledgeExternalExtension(extension->id());
+    LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_IGNORED);
+
+    // Check if there's another extension that needs prompting.
+    UpdateExternalExtensionAlert();
+    return;
+  }
+
+  if (is_first_run_)
+    extension_prefs_->SetExternalInstallFirstRun(extension->id());
+
+  // |first_run| is true if the extension was installed during a first run
+  // (even if it's post-first run now).
+  AddExternalInstallError(
+      extension, extension_prefs_->IsExternalInstallFirstRun(extension->id()));
+#endif  // defined(ENABLE_EXTENSIONS)
+}
+
+void ExternalInstallManager::AcknowledgeExternalExtension(
+    const std::string& id) {
+  extension_prefs_->AcknowledgeExternalExtension(id);
+  UpdateExternalExtensionAlert();
+}
+
+bool ExternalInstallManager::HasExternalInstallBubbleForTesting() const {
   return error_.get() &&
          error_->alert_type() == ExternalInstallError::BUBBLE_ALERT;
 }
@@ -73,13 +160,54 @@
 void ExternalInstallManager::OnExtensionLoaded(
     content::BrowserContext* browser_context,
     const Extension* extension) {
-  // The error is invalidated if the extension has been loaded or removed.
-  if (error_.get() && extension->id() == error_->extension_id()) {
-    // We treat loading as acknowledgement (since the user consciously chose to
-    // re-enable the extension).
-    error_->AcknowledgeExtension();
+  if (!IsUnacknowledgedExternalExtension(extension))
+    return;
+
+  // We treat loading as acknowledgement (since the user consciously chose to
+  // re-enable the extension).
+  AcknowledgeExternalExtension(extension->id());
+  LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_REENABLED);
+
+  // If we had an error for this extension, remove it.
+  if (error_.get() && extension->id() == error_->extension_id())
     RemoveExternalInstallError();
+}
+
+void ExternalInstallManager::OnExtensionInstalled(
+    content::BrowserContext* browser_context,
+    const Extension* extension) {
+  // Certain extension locations are specific enough that we can
+  // auto-acknowledge any extension that came from one of them.
+  if (Manifest::IsPolicyLocation(extension->location()) ||
+      extension->location() == Manifest::EXTERNAL_COMPONENT) {
+    AcknowledgeExternalExtension(extension->id());
+    return;
   }
+
+  if (!IsUnacknowledgedExternalExtension(extension))
+    return;
+
+  LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_INSTALLED);
+
+  UpdateExternalExtensionAlert();
+}
+
+void ExternalInstallManager::OnExtensionUninstalled(
+    content::BrowserContext* browser_context,
+    const Extension* extension) {
+  if (IsUnacknowledgedExternalExtension(extension))
+    LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_UNINSTALLED);
+}
+
+bool ExternalInstallManager::IsUnacknowledgedExternalExtension(
+    const Extension* extension) const {
+  if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled())
+    return false;
+
+  return (Manifest::IsExternalLocation(extension->location()) &&
+          !extension_prefs_->IsExternalExtensionAcknowledged(extension->id()) &&
+          !(extension_prefs_->GetDisableReasons(extension->id()) &
+            Extension::DISABLE_SIDELOAD_WIPEOUT));
 }
 
 void ExternalInstallManager::Observe(
diff --git a/chrome/browser/extensions/external_install_manager.h b/chrome/browser/extensions/external_install_manager.h
index 59e30370..72e32f7 100644
--- a/chrome/browser/extensions/external_install_manager.h
+++ b/chrome/browser/extensions/external_install_manager.h
@@ -12,8 +12,6 @@
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/extension_registry_observer.h"
 
-class GlobalErrorService;
-
 namespace content {
 class BrowserContext;
 class NotificationDetails;
@@ -23,28 +21,32 @@
 namespace extensions {
 class Extension;
 class ExtensionRegistry;
+class ExtensionPrefs;
 class ExternalInstallError;
 
 class ExternalInstallManager : public ExtensionRegistryObserver,
                                public content::NotificationObserver {
  public:
-  explicit ExternalInstallManager(content::BrowserContext* browser_context);
+  ExternalInstallManager(content::BrowserContext* browser_context,
+                         bool is_first_run);
   virtual ~ExternalInstallManager();
 
-  // Adds a global error informing the user that an external extension was
-  // installed. If |is_new_profile| is true, then this error is from the first
-  // time our profile checked for new extensions.
-  void AddExternalInstallError(const Extension* extension, bool is_new_profile);
-
   // Removes the global error, if one existed.
   void RemoveExternalInstallError();
 
   // Returns true if there is a global error for an external install.
   bool HasExternalInstallError() const;
 
+  // Checks if there are any new external extensions to notify the user about.
+  void UpdateExternalExtensionAlert();
+
+  // Given a (presumably just-installed) extension id, mark that extension as
+  // acknowledged.
+  void AcknowledgeExternalExtension(const std::string& extension_id);
+
   // Returns true if there is a global error with a bubble view for an external
   // install. Used for testing.
-  bool HasExternalInstallBubble() const;
+  bool HasExternalInstallBubbleForTesting() const;
 
   // Returns the current install error, if one exists.
   const ExternalInstallError* error() { return error_.get(); }
@@ -56,15 +58,34 @@
   // ExtensionRegistryObserver implementation.
   virtual void OnExtensionLoaded(content::BrowserContext* browser_context,
                                  const Extension* extension) OVERRIDE;
+  virtual void OnExtensionInstalled(content::BrowserContext* browser_context,
+                                    const Extension* extension) OVERRIDE;
+  virtual void OnExtensionUninstalled(content::BrowserContext* browser_context,
+                                      const Extension* extension) OVERRIDE;
 
   // content::NotificationObserver implementation.
   virtual void Observe(int type,
                        const content::NotificationSource& source,
                        const content::NotificationDetails& details) OVERRIDE;
 
+  // Adds a global error informing the user that an external extension was
+  // installed. If |is_new_profile| is true, then this error is from the first
+  // time our profile checked for new extensions.
+  void AddExternalInstallError(const Extension* extension, bool is_new_profile);
+
+  // Returns true if this extension is an external one that has yet to be
+  // marked as acknowledged.
+  bool IsUnacknowledgedExternalExtension(const Extension* extension) const;
+
   // The associated BrowserContext.
   content::BrowserContext* browser_context_;
 
+  // Whether or not this is the first run for the profile.
+  bool is_first_run_;
+
+  // The associated ExtensionPrefs.
+  ExtensionPrefs* extension_prefs_;
+
   // The current ExternalInstallError, if one exists.
   scoped_ptr<ExternalInstallError> error_;