This implements half of the activityLogPrivate API. The API is only available to a single whitelisted extension. It includes the schema and events. It is missing the implementation of its function, which I'll add as a separate CL.

BUG=241672

Review URL: https://chromiumcodereview.appspot.com/16061002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@204796 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/activity_log/activity_actions.cc b/chrome/browser/extensions/activity_log/activity_actions.cc
index b6195af..7cff66d 100644
--- a/chrome/browser/extensions/activity_log/activity_actions.cc
+++ b/chrome/browser/extensions/activity_log/activity_actions.cc
@@ -5,18 +5,22 @@
 #include <string>
 #include "base/logging.h"
 #include "base/stringprintf.h"
-#include "chrome/browser/extensions/activity_log/api_actions.h"
+#include "chrome/browser/extensions/activity_log/activity_actions.h"
 
 namespace extensions {
 
+using api::activity_log_private::ExtensionActivity;
+
 const char* Action::kTableBasicFields =
     "extension_id LONGVARCHAR NOT NULL, "
     "time INTEGER NOT NULL";
 
 Action::Action(const std::string& extension_id,
-               const base::Time& time)
+               const base::Time& time,
+               ExtensionActivity::ActivityType activity_type)
     : extension_id_(extension_id),
-      time_(time) {}
+      time_(time),
+      activity_type_(activity_type) {}
 
 // static
 bool Action::InitializeTableInternal(sql::Connection* db,
diff --git a/chrome/browser/extensions/activity_log/activity_actions.h b/chrome/browser/extensions/activity_log/activity_actions.h
index da7c4999..547d2d8 100644
--- a/chrome/browser/extensions/activity_log/activity_actions.h
+++ b/chrome/browser/extensions/activity_log/activity_actions.h
@@ -9,6 +9,7 @@
 #include "base/memory/ref_counted_memory.h"
 #include "base/time.h"
 #include "base/values.h"
+#include "chrome/common/extensions/api/activity_log_private.h"
 #include "sql/connection.h"
 #include "sql/statement.h"
 #include "sql/transaction.h"
@@ -27,14 +28,22 @@
   // Record the action in the database.
   virtual void Record(sql::Connection* db) = 0;
 
+  // Flatten the activity's type-specific fields into an ExtensionActivity.
+  virtual scoped_ptr<api::activity_log_private::ExtensionActivity>
+      ConvertToExtensionActivity() = 0;
+
   // Print an action as a regular string for debugging purposes.
   virtual std::string PrintForDebug() = 0;
 
   const std::string& extension_id() const { return extension_id_; }
   const base::Time& time() const { return time_; }
+  api::activity_log_private::ExtensionActivity::ActivityType activity_type()
+      const { return activity_type_; }
 
  protected:
-  Action(const std::string& extension_id, const base::Time& time);
+  Action(const std::string& extension_id,
+         const base::Time& time,
+         api::activity_log_private::ExtensionActivity::ActivityType type);
   virtual ~Action() {}
 
   // Initialize the table for a given action type.
@@ -53,6 +62,7 @@
 
   std::string extension_id_;
   base::Time time_;
+  api::activity_log_private::ExtensionActivity::ActivityType activity_type_;
 
   DISALLOW_COPY_AND_ASSIGN(Action);
 };
diff --git a/chrome/browser/extensions/activity_log/activity_database_unittest.cc b/chrome/browser/extensions/activity_log/activity_database_unittest.cc
index d53e785c..e101b1a 100644
--- a/chrome/browser/extensions/activity_log/activity_database_unittest.cc
+++ b/chrome/browser/extensions/activity_log/activity_database_unittest.cc
@@ -45,11 +45,6 @@
     test_user_manager_.reset(new chromeos::ScopedTestUserManager());
 #endif
     CommandLine command_line(CommandLine::NO_PROGRAM);
-    profile_ =
-        Profile::FromBrowserContext(web_contents()->GetBrowserContext());
-    extension_service_ = static_cast<TestExtensionSystem*>(
-        ExtensionSystem::Get(profile_))->CreateExtensionService(
-            &command_line, base::FilePath(), false);
     CommandLine::ForCurrentProcess()->AppendSwitch(
         switches::kEnableExtensionActivityLogTesting);
   }
@@ -61,10 +56,6 @@
     ChromeRenderViewHostTestHarness::TearDown();
   }
 
- protected:
-  ExtensionService* extension_service_;
-  Profile* profile_;
-
  private:
 #if defined OS_CHROMEOS
   chromeos::ScopedStubCrosEnabler stub_cros_enabler_;
@@ -217,9 +208,9 @@
   activity_db->RecordAction(extra_dom_action);
 
   // Read them back
-  std::string api_print = "ID: punky, CATEGORY: CALL, "
+  std::string api_print = "ID: punky, CATEGORY: call, "
       "API: brewster, ARGS: woof";
-  std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: MODIFIED";
+  std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: modified";
   scoped_ptr<std::vector<scoped_refptr<Action> > > actions =
       activity_db->GetActions("punky", 0);
   ASSERT_EQ(2, static_cast<int>(actions->size()));
@@ -288,9 +279,9 @@
   activity_db->RecordAction(tooold_dom_action);
 
   // Read them back
-  std::string api_print = "ID: punky, CATEGORY: CALL, "
+  std::string api_print = "ID: punky, CATEGORY: call, "
       "API: brewster, ARGS: woof";
-  std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: MODIFIED";
+  std::string dom_print = "DOM API CALL: lets, ARGS: vamoose, VERB: modified";
   scoped_ptr<std::vector<scoped_refptr<Action> > > actions =
       activity_db->GetActions("punky", 3);
   ASSERT_EQ(2, static_cast<int>(actions->size()));
diff --git a/chrome/browser/extensions/activity_log/activity_log.cc b/chrome/browser/extensions/activity_log/activity_log.cc
index c02d93da..5910840 100644
--- a/chrome/browser/extensions/activity_log/activity_log.cc
+++ b/chrome/browser/extensions/activity_log/activity_log.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/extensions/activity_log/activity_log.h"
 #include "chrome/browser/extensions/activity_log/api_actions.h"
 #include "chrome/browser/extensions/activity_log/blocked_actions.h"
+#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_system.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
@@ -41,14 +42,6 @@
   return call_signature;
 }
 
-// Concatenate an API call with its arguments.
-std::string MakeCallSignature(const std::string& name, const ListValue* args) {
-  std::string call_signature = name + "(";
-  call_signature += MakeArgList(args);
-  call_signature += ")";
-  return call_signature;
-}
-
 // Computes whether the activity log is enabled in this browser (controlled by
 // command-line flags) and caches the value (which is assumed never to change).
 class LogIsEnabled {
@@ -106,12 +99,9 @@
 
 // Use GetInstance instead of directly creating an ActivityLog.
 ActivityLog::ActivityLog(Profile* profile) : profile_(profile) {
-  // enable-extension-activity-logging and enable-extension-activity-ui
-  log_activity_to_stdout_ = CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kEnableExtensionActivityLogging);
-
   // enable-extension-activity-log-testing
   // This controls whether arguments are collected.
+  // It also controls whether logging statements are printed.
   testing_mode_ = CommandLine::ForCurrentProcess()->HasSwitch(
       switches::kEnableExtensionActivityLogTesting);
   if (!testing_mode_) {
@@ -130,6 +120,8 @@
     dispatch_thread_ = BrowserThread::UI;
   }
 
+  observers_ = new ObserverListThreadSafe<Observer>;
+
   // If the database cannot be initialized for some reason, we keep
   // chugging along but nothing will get recorded. If the UI is
   // available, things will still get sent to the UI even if nothing
@@ -158,12 +150,11 @@
 }
 
 void ActivityLog::AddObserver(ActivityLog::Observer* observer) {
-  if (!IsLogEnabled()) return;
-  // TODO(felt) Re-implement Observer notification HERE for the API.
+  observers_->AddObserver(observer);
 }
 
 void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) {
-  // TODO(felt) Re-implement Observer notification HERE for the API.
+  observers_->RemoveObserver(observer);
 }
 
 void ActivityLog::LogAPIActionInternal(const std::string& extension_id,
@@ -185,9 +176,8 @@
         MakeArgList(args),
         extra);
     ScheduleAndForget(&ActivityDatabase::RecordAction, action);
-    // TODO(felt) Re-implement Observer notification HERE for the API.
-    if (log_activity_to_stdout_)
-      LOG(INFO) << action->PrintForDebug();
+    observers_->Notify(&Observer::OnExtensionActivity, action);
+    if (testing_mode_) LOG(INFO) << action->PrintForDebug();
   } else {
     LOG(ERROR) << "Unknown API call! " << api_call;
   }
@@ -198,7 +188,8 @@
                                const std::string& api_call,
                                ListValue* args,
                                const std::string& extra) {
-  if (!IsLogEnabled()) return;
+  if (!IsLogEnabled() ||
+      ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return;
   if (!testing_mode_ &&
       arg_whitelist_api_.find(api_call) == arg_whitelist_api_.end())
     args->Clear();
@@ -217,7 +208,8 @@
                                  const std::string& api_call,
                                  ListValue* args,
                                  const std::string& extra) {
-  if (!IsLogEnabled()) return;
+  if (!IsLogEnabled() ||
+      ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return;
   if (!testing_mode_ &&
       arg_whitelist_api_.find(api_call) == arg_whitelist_api_.end())
     args->Clear();
@@ -233,7 +225,8 @@
                                    ListValue* args,
                                    BlockedAction::Reason reason,
                                    const std::string& extra) {
-  if (!IsLogEnabled()) return;
+  if (!IsLogEnabled() ||
+      ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return;
   if (!testing_mode_ &&
       arg_whitelist_api_.find(blocked_call) == arg_whitelist_api_.end())
     args->Clear();
@@ -244,9 +237,8 @@
                                                           reason,
                                                           extra);
   ScheduleAndForget(&ActivityDatabase::RecordAction, action);
-  // TODO(felt) Re-implement Observer notification HERE for the API.
-  if (log_activity_to_stdout_)
-    LOG(INFO) << action->PrintForDebug();
+  observers_->Notify(&Observer::OnExtensionActivity, action);
+  if (testing_mode_) LOG(INFO) << action->PrintForDebug();
 }
 
 void ActivityLog::LogDOMAction(const std::string& extension_id,
@@ -256,7 +248,8 @@
                                const ListValue* args,
                                DomActionType::Type call_type,
                                const std::string& extra) {
-  if (!IsLogEnabled()) return;
+  if (!IsLogEnabled() ||
+      ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return;
   if (call_type == DomActionType::METHOD && api_call == "XMLHttpRequest.open")
     call_type = DomActionType::XHR;
   scoped_refptr<DOMAction> action = new DOMAction(
@@ -269,9 +262,8 @@
       MakeArgList(args),
       extra);
   ScheduleAndForget(&ActivityDatabase::RecordAction, action);
-  // TODO(felt) Re-implement Observer notification HERE for the API.
-  if (log_activity_to_stdout_)
-    LOG(INFO) << action->PrintForDebug();
+  observers_->Notify(&Observer::OnExtensionActivity, action);
+  if (testing_mode_) LOG(INFO) << action->PrintForDebug();
 }
 
 void ActivityLog::LogWebRequestAction(const std::string& extension_id,
@@ -280,7 +272,8 @@
                                       scoped_ptr<DictionaryValue> details,
                                       const std::string& extra) {
   string16 null_title;
-  if (!IsLogEnabled()) return;
+  if (!IsLogEnabled() ||
+      ActivityLogAPI::IsExtensionWhitelisted(extension_id)) return;
 
   // Strip details of the web request modifications (for privacy reasons),
   // unless testing is enabled.
@@ -305,9 +298,8 @@
       details_string,
       extra);
   ScheduleAndForget(&ActivityDatabase::RecordAction, action);
-  // TODO(felt) Re-implement Observer notification HERE for the API.
-  if (log_activity_to_stdout_)
-    LOG(INFO) << action->PrintForDebug();
+  observers_->Notify(&Observer::OnExtensionActivity, action);
+  if (testing_mode_) LOG(INFO) << action->PrintForDebug();
 }
 
 void ActivityLog::GetActions(
@@ -340,7 +332,7 @@
   for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
        it != extension_ids.end(); ++it) {
     const Extension* extension = extensions->GetByID(it->first);
-    if (!extension)
+    if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id()))
       continue;
 
     // If OnScriptsExecuted is fired because of tabs.executeScript, the list
diff --git a/chrome/browser/extensions/activity_log/activity_log.h b/chrome/browser/extensions/activity_log/activity_log.h
index 2314f0e..763f0d2 100644
--- a/chrome/browser/extensions/activity_log/activity_log.h
+++ b/chrome/browser/extensions/activity_log/activity_log.h
@@ -38,7 +38,8 @@
 class ActivityLog : public BrowserContextKeyedService,
                     public TabHelper::ScriptExecutionObserver {
  public:
-  // Observers can listen for activity events.
+  // Observers can listen for activity events. There is probably only one
+  // observer: the activityLogPrivate API.
   class Observer {
    public:
     virtual void OnExtensionActivity(scoped_refptr<Action> activity) = 0;
@@ -57,7 +58,8 @@
   // really intended for use by unit tests.
   static void RecomputeLoggingIsEnabled();
 
-  // Add/remove observer.
+  // Add/remove observer: the activityLogPrivate API only listens when the
+  // ActivityLog extension is registered for an event.
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
@@ -170,6 +172,7 @@
   }
 
   typedef ObserverListThreadSafe<Observer> ObserverList;
+  scoped_refptr<ObserverList> observers_;
 
   // The database wrapper that does the actual database I/O.
   // We initialize this on the same thread as the ActivityLog, but then
@@ -182,14 +185,11 @@
   // we dispatch to the UI thread.
   BrowserThread::ID dispatch_thread_;
 
-  // Whether to log activity to stdout or the UI. These are set by switches.
-  bool log_activity_to_stdout_;
-  bool log_activity_to_ui_;
-
   // testing_mode_ controls whether to log API call arguments. By default, we
   // don't log most arguments to avoid saving too much data. In testing mode,
   // argument collection is enabled. We also whitelist some arguments for
   // collection regardless of whether this bool is true.
+  // When testing_mode_ is enabled, we also print to the console.
   bool testing_mode_;
   base::hash_set<std::string> arg_whitelist_api_;
 
diff --git a/chrome/browser/extensions/activity_log/activity_log_browsertest.cc b/chrome/browser/extensions/activity_log/activity_log_browsertest.cc
index f0cfeb3..196ed311 100644
--- a/chrome/browser/extensions/activity_log/activity_log_browsertest.cc
+++ b/chrome/browser/extensions/activity_log/activity_log_browsertest.cc
@@ -26,6 +26,7 @@
   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
     ExtensionBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendSwitch(switches::kEnableExtensionActivityLogging);
+    command_line->AppendSwitch(switches::kEnableExtensionActivityLogTesting);
   }
 };
 
diff --git a/chrome/browser/extensions/activity_log/activity_log_unittest.cc b/chrome/browser/extensions/activity_log/activity_log_unittest.cc
index 15f00a1..e8a3492 100644
--- a/chrome/browser/extensions/activity_log/activity_log_unittest.cc
+++ b/chrome/browser/extensions/activity_log/activity_log_unittest.cc
@@ -76,7 +76,7 @@
     scoped_refptr<Action> last = i->front();
     std::string id(kExtensionId);
     std::string noargs = "ID: " + id + ", CATEGORY: "
-      "CALL, API: tabs.testMethod, ARGS: ";
+      "call, API: tabs.testMethod, ARGS: ";
     ASSERT_EQ(noargs, last->PrintForDebug());
   }
 
@@ -85,7 +85,7 @@
     scoped_refptr<Action> last = i->front();
     std::string id(kExtensionId);
     std::string args = "ID: " + id + ", CATEGORY: "
-      "CALL, API: extension.connect, ARGS: \"hello\", \"world\"";
+      "call, API: extension.connect, ARGS: \"hello\", \"world\"";
     ASSERT_EQ(args, last->PrintForDebug());
   }
 
diff --git a/chrome/browser/extensions/activity_log/api_actions.cc b/chrome/browser/extensions/activity_log/api_actions.cc
index e982b30..6a7cb654 100644
--- a/chrome/browser/extensions/activity_log/api_actions.cc
+++ b/chrome/browser/extensions/activity_log/api_actions.cc
@@ -96,6 +96,11 @@
 
 namespace extensions {
 
+using api::activity_log_private::ExtensionActivity;
+using api::activity_log_private::DomActivityDetail;
+using api::activity_log_private::ChromeActivityDetail;
+using api::activity_log_private::BlockedChromeActivityDetail;
+
 const char* APIAction::kTableName = "activitylog_apis";
 const char* APIAction::kTableContentFields[] =
     {"api_type", "api_call", "args", "extra"};
@@ -115,7 +120,7 @@
                      const std::string& api_call,
                      const std::string& args,
                      const std::string& extra)
-    : Action(extension_id, time),
+    : Action(extension_id, time, ExtensionActivity::ACTIVITY_TYPE_CHROME),
       type_(type),
       api_call_(api_call),
       args_(args),
@@ -123,7 +128,8 @@
 
 APIAction::APIAction(const sql::Statement& s)
     : Action(s.ColumnString(0),
-          base::Time::FromInternalValue(s.ColumnInt64(1))),
+             base::Time::FromInternalValue(s.ColumnInt64(1)),
+             ExtensionActivity::ACTIVITY_TYPE_CHROME),
       type_(static_cast<Type>(s.ColumnInt(2))),
       api_call_(APINameMap::GetInstance()->ShortnameToApi(s.ColumnString(3))),
       args_(s.ColumnString(4)),
@@ -132,6 +138,23 @@
 APIAction::~APIAction() {
 }
 
+scoped_ptr<ExtensionActivity> APIAction::ConvertToExtensionActivity() {
+  scoped_ptr<ExtensionActivity> formatted_activity;
+  formatted_activity.reset(new ExtensionActivity);
+  formatted_activity->extension_id.reset(
+      new std::string(extension_id()));
+  formatted_activity->activity_type = activity_type();
+  formatted_activity->time.reset(new double(time().ToJsTime()));
+  ChromeActivityDetail* details = new ChromeActivityDetail;
+  details->api_activity_type = ChromeActivityDetail::ParseApiActivityType(
+      TypeAsString());
+  details->api_call.reset(new std::string(api_call_));
+  details->args.reset(new std::string(args_));
+  details->extra.reset(new std::string(extra_));
+  formatted_activity->chrome_activity_detail.reset(details);
+  return formatted_activity.Pass();
+}
+
 // static
 bool APIAction::InitializeTable(sql::Connection* db) {
   // The original table schema was different than the existing one.
@@ -229,11 +252,11 @@
 std::string APIAction::TypeAsString() const {
   switch (type_) {
     case CALL:
-      return "CALL";
+      return "call";
     case EVENT_CALLBACK:
-      return "EVENT_CALLBACK";
+      return "event_callback";
     default:
-      return "UNKNOWN_TYPE";
+      return "unknown_type";
   }
 }
 
diff --git a/chrome/browser/extensions/activity_log/api_actions.h b/chrome/browser/extensions/activity_log/api_actions.h
index 3744f52..1269444 100644
--- a/chrome/browser/extensions/activity_log/api_actions.h
+++ b/chrome/browser/extensions/activity_log/api_actions.h
@@ -47,6 +47,9 @@
   // Record the action in the database.
   virtual void Record(sql::Connection* db) OVERRIDE;
 
+  virtual scoped_ptr<api::activity_log_private::ExtensionActivity>
+      ConvertToExtensionActivity() OVERRIDE;
+
   // Used to associate tab IDs with URLs. It will swap out the int in args with
   // a URL as a string. If the tab is in incognito mode, we leave it alone as
   // the original int. There is a small chance that the URL translation could
diff --git a/chrome/browser/extensions/activity_log/blocked_actions.cc b/chrome/browser/extensions/activity_log/blocked_actions.cc
index b773299..aa35f70 100644
--- a/chrome/browser/extensions/activity_log/blocked_actions.cc
+++ b/chrome/browser/extensions/activity_log/blocked_actions.cc
@@ -11,6 +11,11 @@
 
 namespace extensions {
 
+using api::activity_log_private::ExtensionActivity;
+using api::activity_log_private::DomActivityDetail;
+using api::activity_log_private::ChromeActivityDetail;
+using api::activity_log_private::BlockedChromeActivityDetail;
+
 const char* BlockedAction::kTableName = "activitylog_blocked";
 const char* BlockedAction::kTableContentFields[] =
     {"api_call", "args", "reason", "extra"};
@@ -23,7 +28,9 @@
                              const std::string& args,
                              const BlockedAction::Reason reason,
                              const std::string& extra)
-    : Action(extension_id, time),
+    : Action(extension_id,
+             time,
+             ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME),
       api_call_(api_call),
       args_(args),
       reason_(reason),
@@ -31,7 +38,8 @@
 
 BlockedAction::BlockedAction(const sql::Statement& s)
     : Action(s.ColumnString(0),
-          base::Time::FromInternalValue(s.ColumnInt64(1))),
+             base::Time::FromInternalValue(s.ColumnInt64(1)),
+             ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME),
       api_call_(s.ColumnString(2)),
       args_(s.ColumnString(3)),
       reason_(static_cast<Reason>(s.ColumnInt(4))),
@@ -40,6 +48,23 @@
 BlockedAction::~BlockedAction() {
 }
 
+scoped_ptr<ExtensionActivity> BlockedAction::ConvertToExtensionActivity() {
+  scoped_ptr<ExtensionActivity> formatted_activity;
+  formatted_activity.reset(new ExtensionActivity);
+  formatted_activity->extension_id.reset(
+      new std::string(extension_id()));
+  formatted_activity->activity_type = activity_type();
+  formatted_activity->time.reset(new double(time().ToJsTime()));
+  BlockedChromeActivityDetail* details = new BlockedChromeActivityDetail;
+  details->api_call.reset(new std::string(api_call_));
+  details->args.reset(new std::string(args_));
+  details->reason = BlockedChromeActivityDetail::ParseReason(
+      ReasonAsString());
+  details->extra.reset(new std::string(extra_));
+  formatted_activity->blocked_chrome_activity_detail.reset(details);
+  return formatted_activity.Pass();
+}
+
 // static
 bool BlockedAction::InitializeTable(sql::Connection* db) {
   // The original table schema was different than the existing one.
@@ -93,11 +118,11 @@
 
 std::string BlockedAction::ReasonAsString() const {
   if (reason_ == ACCESS_DENIED)
-    return std::string("access denied");
+    return std::string("access_denied");
   else if (reason_ == QUOTA_EXCEEDED)
-    return std::string("quota exceeded");
+    return std::string("quota_exceeded");
   else
-    return std::string("unknown");
+    return std::string("unknown_reason_type");  // To avoid Win header name.
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/activity_log/blocked_actions.h b/chrome/browser/extensions/activity_log/blocked_actions.h
index 930b70a5a..76abe95 100644
--- a/chrome/browser/extensions/activity_log/blocked_actions.h
+++ b/chrome/browser/extensions/activity_log/blocked_actions.h
@@ -43,6 +43,9 @@
   // Record the action in the database.
   virtual void Record(sql::Connection* db) OVERRIDE;
 
+  virtual scoped_ptr<api::activity_log_private::ExtensionActivity>
+      ConvertToExtensionActivity() OVERRIDE;
+
   // Print a BlockedAction as a string for debugging purposes.
   virtual std::string PrintForDebug() OVERRIDE;
 
diff --git a/chrome/browser/extensions/activity_log/dom_actions.cc b/chrome/browser/extensions/activity_log/dom_actions.cc
index d431270..ea2486c 100644
--- a/chrome/browser/extensions/activity_log/dom_actions.cc
+++ b/chrome/browser/extensions/activity_log/dom_actions.cc
@@ -13,6 +13,11 @@
 
 namespace extensions {
 
+using api::activity_log_private::ExtensionActivity;
+using api::activity_log_private::DomActivityDetail;
+using api::activity_log_private::ChromeActivityDetail;
+using api::activity_log_private::BlockedChromeActivityDetail;
+
 const char* DOMAction::kTableName = "activitylog_urls";
 const char* DOMAction::kTableContentFields[] =
     {"url_action_type", "url", "url_title", "api_call", "args", "extra"};
@@ -28,7 +33,7 @@
                      const std::string& api_call,
                      const std::string& args,
                      const std::string& extra)
-    : Action(extension_id, time),
+    : Action(extension_id, time, ExtensionActivity::ACTIVITY_TYPE_DOM),
       verb_(verb),
       url_(url),
       url_title_(url_title),
@@ -38,7 +43,8 @@
 
 DOMAction::DOMAction(const sql::Statement& s)
     : Action(s.ColumnString(0),
-          base::Time::FromInternalValue(s.ColumnInt64(1))),
+             base::Time::FromInternalValue(s.ColumnInt64(1)),
+             ExtensionActivity::ACTIVITY_TYPE_DOM),
       verb_(static_cast<DomActionType::Type>(s.ColumnInt(2))),
       url_(GURL(s.ColumnString(3))),
       url_title_(s.ColumnString16(4)),
@@ -49,6 +55,25 @@
 DOMAction::~DOMAction() {
 }
 
+scoped_ptr<ExtensionActivity> DOMAction::ConvertToExtensionActivity() {
+  scoped_ptr<ExtensionActivity> formatted_activity;
+  formatted_activity.reset(new ExtensionActivity);
+  formatted_activity->extension_id.reset(
+      new std::string(extension_id()));
+  formatted_activity->activity_type = activity_type();
+  formatted_activity->time.reset(new double(time().ToJsTime()));
+  DomActivityDetail* details = new DomActivityDetail;
+  details->dom_activity_type = DomActivityDetail::ParseDomActivityType(
+      VerbAsString());
+  details->url.reset(new std::string(url_.spec()));
+  details->url_title.reset(new std::string(base::UTF16ToUTF8(url_title_)));
+  details->api_call.reset(new std::string(api_call_));
+  details->args.reset(new std::string(args_));
+  details->extra.reset(new std::string(extra_));
+  formatted_activity->dom_activity_detail.reset(details);
+  return formatted_activity.Pass();
+}
+
 // static
 bool DOMAction::InitializeTable(sql::Connection* db) {
   // The original table schema was different than the existing one.
@@ -111,19 +136,19 @@
 std::string DOMAction::VerbAsString() const {
   switch (verb_) {
     case DomActionType::GETTER:
-      return "GETTER";
+      return "getter";
     case DomActionType::SETTER:
-      return "SETTER";
+      return "setter";
     case DomActionType::METHOD:
-      return "METHOD";
+      return "method";
     case DomActionType::INSERTED:
-      return "INSERTED";
+      return "inserted";
     case DomActionType::XHR:
-      return "XHR";
+      return "xhr";
     case DomActionType::WEBREQUEST:
-      return "WEBREQUEST";
+      return "webrequest";
     case DomActionType::MODIFIED:    // legacy
-      return "MODIFIED";
+      return "modified";
     default:
       NOTREACHED();
       return NULL;
diff --git a/chrome/browser/extensions/activity_log/dom_actions.h b/chrome/browser/extensions/activity_log/dom_actions.h
index c430803..4b4825d 100644
--- a/chrome/browser/extensions/activity_log/dom_actions.h
+++ b/chrome/browser/extensions/activity_log/dom_actions.h
@@ -41,6 +41,9 @@
   // Create a new DOMAction from a database row.
   explicit DOMAction(const sql::Statement& s);
 
+  virtual scoped_ptr<api::activity_log_private::ExtensionActivity>
+      ConvertToExtensionActivity() OVERRIDE;
+
   // Record the action in the database.
   virtual void Record(sql::Connection* db) OVERRIDE;
 
diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc
new file mode 100644
index 0000000..7035417
--- /dev/null
+++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.cc
@@ -0,0 +1,96 @@
+// Copyright 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 "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
+
+#include "base/lazy_instance.h"
+#include "base/prefs/pref_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/event_router_forwarder.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/api/activity_log_private.h"
+#include "chrome/common/pref_names.h"
+
+namespace extensions {
+
+using api::activity_log_private::ExtensionActivity;
+
+const char kActivityLogExtensionId[] = "acldcpdepobcjbdanifkmfndkjoilgba";
+const char kActivityLogTestExtensionId[] = "ajabfgledjhbabeoojlabelaifmakodf";
+const char kNewActivityEventName[] = "activityLogPrivate.onExtensionActivity";
+
+static base::LazyInstance<ProfileKeyedAPIFactory<ActivityLogAPI> >
+    g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+ProfileKeyedAPIFactory<ActivityLogAPI>* ActivityLogAPI::GetFactoryInstance() {
+  return &g_factory.Get();
+}
+
+template<>
+void ProfileKeyedAPIFactory<ActivityLogAPI>::DeclareFactoryDependencies() {
+  DependsOn(ExtensionSystemFactory::GetInstance());
+  DependsOn(ActivityLogFactory::GetInstance());
+}
+
+ActivityLogAPI::ActivityLogAPI(Profile* profile)
+    : profile_(profile),
+      initialized_(false) {
+  if (!ExtensionSystem::Get(profile_)->event_router()) {  // Check for testing.
+    LOG(ERROR) << "ExtensionSystem event_router does not exist.";
+    return;
+  }
+  activity_log_ = extensions::ActivityLog::GetInstance(profile_);
+  DCHECK(activity_log_);
+  ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
+      this, kNewActivityEventName);
+  activity_log_->AddObserver(this);
+  initialized_ = true;
+}
+
+ActivityLogAPI::~ActivityLogAPI() {
+}
+
+void ActivityLogAPI::Shutdown() {
+  if (!initialized_) {  // Check for testing.
+    LOG(ERROR) << "ExtensionSystem event_router does not exist.";
+    return;
+  }
+  ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
+  activity_log_->RemoveObserver(this);
+}
+
+// static
+bool ActivityLogAPI::IsExtensionWhitelisted(const std::string& extension_id) {
+  return (extension_id == kActivityLogExtensionId ||
+          extension_id == kActivityLogTestExtensionId);
+}
+
+void ActivityLogAPI::OnListenerAdded(const EventListenerInfo& details) {
+  // TODO(felt): Only observe activity_log_ events when we have a customer.
+}
+
+void ActivityLogAPI::OnListenerRemoved(const EventListenerInfo& details) {
+  // TODO(felt): Only observe activity_log_ events when we have a customer.
+}
+
+void ActivityLogAPI::OnExtensionActivity(scoped_refptr<Action> activity) {
+  scoped_ptr<base::ListValue> value(new base::ListValue());
+  scoped_ptr<ExtensionActivity> activity_arg =
+      activity->ConvertToExtensionActivity();
+  value->Append(activity_arg->ToValue().release());
+  scoped_ptr<Event> event(new Event(kNewActivityEventName, value.Pass()));
+  event->restrict_to_profile = profile_;
+  ExtensionSystem::Get(profile_)->event_router()->BroadcastEvent(event.Pass());
+}
+
+bool ActivityLogPrivateGetExtensionActivitiesFunction::RunImpl() {
+  return true;
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h
new file mode 100644
index 0000000..ea74c1fd
--- /dev/null
+++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h
@@ -0,0 +1,84 @@
+// Copyright 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.
+
+// This extension API provides access to the Activity Log, which is a
+// monitoring framework for extension behavior. Only specific Google-produced
+// extensions should have access to it.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_ACTIVITY_LOG_PRIVATE_ACTIVITY_LOG_PRIVATE_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_ACTIVITY_LOG_PRIVATE_ACTIVITY_LOG_PRIVATE_API_H_
+
+#include "base/synchronization/lock.h"
+#include "chrome/browser/extensions/activity_log/activity_actions.h"
+#include "chrome/browser/extensions/activity_log/activity_log.h"
+#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
+#include "chrome/browser/extensions/api/profile_keyed_api_factory.h"
+#include "chrome/browser/extensions/event_router.h"
+#include "chrome/browser/extensions/extension_function.h"
+
+namespace extensions {
+
+class ActivityLog;
+
+// The ID of the trusted/whitelisted ActivityLog extension.
+extern const char kActivityLogExtensionId[];
+extern const char kActivityLogTestExtensionId[];
+extern const char kNewActivityEventName[];
+
+// Handles interactions between the Activity Log API and implementation.
+class ActivityLogAPI : public ProfileKeyedAPI,
+                       public extensions::ActivityLog::Observer,
+                       public EventRouter::Observer {
+ public:
+  explicit ActivityLogAPI(Profile* profile);
+  virtual ~ActivityLogAPI();
+
+  // ProfileKeyedAPI implementation.
+  static ProfileKeyedAPIFactory<ActivityLogAPI>* GetFactoryInstance();
+
+  virtual void Shutdown() OVERRIDE;
+
+  // Lookup whether the extension ID is whitelisted.
+  static bool IsExtensionWhitelisted(const std::string& extension_id);
+
+ private:
+  friend class ProfileKeyedAPIFactory<ActivityLogAPI>;
+  static const char* service_name() { return "ActivityLogPrivateAPI"; }
+
+  // ActivityLog::Observer
+  // We pass this along to activityLogPrivate.onExtensionActivity.
+  virtual void OnExtensionActivity(scoped_refptr<Action> activity) OVERRIDE;
+
+  // EventRouter::Observer
+  // We only keep track of OnExtensionActivity if we have any listeners.
+  virtual void OnListenerAdded(const EventListenerInfo& details) OVERRIDE;
+  virtual void OnListenerRemoved(const EventListenerInfo& details) OVERRIDE;
+
+  Profile* profile_;
+  ActivityLog* activity_log_;
+  bool initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(ActivityLogAPI);
+};
+
+template<>
+void ProfileKeyedAPIFactory<ActivityLogAPI>::DeclareFactoryDependencies();
+
+// The implementation of activityLogPrivate.getExtensionActivities
+class ActivityLogPrivateGetExtensionActivitiesFunction
+    : public AsyncExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("activityLogPrivate.getExtensionActivities",
+                             ACTIVITYLOGPRIVATE_GETEXTENSIONACTIVITIES)
+
+ protected:
+  virtual ~ActivityLogPrivateGetExtensionActivitiesFunction() {}
+
+  // ExtensionFunction:
+  virtual bool RunImpl() OVERRIDE;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_ACTIVITY_LOG_PRIVATE_ACTIVITY_LOG_PRIVATE_API_H_
diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc
new file mode 100644
index 0000000..6aef6142
--- /dev/null
+++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_api_unittest.cc
@@ -0,0 +1,105 @@
+// Copyright 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 <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kExtensionId[] = "extensionid";
+const char kApiCall[] = "api.call";
+const char kArgs[] = "1, 2";
+const char kExtra[] = "extra";
+
+}  // extensions
+
+namespace extensions {
+
+using api::activity_log_private::BlockedChromeActivityDetail;
+using api::activity_log_private::ChromeActivityDetail;
+using api::activity_log_private::DomActivityDetail;
+using api::activity_log_private::ExtensionActivity;
+
+class ActivityLogApiUnitTest : public testing::Test {
+};
+
+TEST_F(ActivityLogApiUnitTest, ConvertBlockedAction) {
+  scoped_refptr<Action> action(
+      new BlockedAction(kExtensionId,
+                        base::Time::Now(),
+                        kApiCall,
+                        kArgs,
+                        BlockedAction::ACCESS_DENIED,
+                        kExtra));
+  scoped_ptr<ExtensionActivity> result =
+      action->ConvertToExtensionActivity();
+  ASSERT_EQ(ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME,
+            result->activity_type);
+  ASSERT_EQ(kExtensionId, *(result->extension_id.get()));
+  ASSERT_EQ(kApiCall,
+            *(result->blocked_chrome_activity_detail->api_call.get()));
+  ASSERT_EQ(kArgs,
+            *(result->blocked_chrome_activity_detail->args.get()));
+  ASSERT_EQ(BlockedChromeActivityDetail::REASON_ACCESS_DENIED,
+            result->blocked_chrome_activity_detail->reason);
+  ASSERT_EQ(kExtra,
+            *(result->blocked_chrome_activity_detail->extra.get()));
+}
+
+TEST_F(ActivityLogApiUnitTest, ConvertChromeApiAction) {
+  scoped_refptr<Action> action(
+    new APIAction(kExtensionId,
+                  base::Time::Now(),
+                  APIAction::CALL,
+                  kApiCall,
+                  kArgs,
+                  kExtra));
+  scoped_ptr<ExtensionActivity> result =
+      action->ConvertToExtensionActivity();
+  ASSERT_EQ(ExtensionActivity::ACTIVITY_TYPE_CHROME,
+            result->activity_type);
+  ASSERT_EQ(kExtensionId, *(result->extension_id.get()));
+  ASSERT_EQ(ChromeActivityDetail::API_ACTIVITY_TYPE_CALL,
+            result->chrome_activity_detail->api_activity_type);
+  ASSERT_EQ(kApiCall,
+            *(result->chrome_activity_detail->api_call.get()));
+  ASSERT_EQ(kArgs,
+            *(result->chrome_activity_detail->args.get()));
+  ASSERT_EQ(kExtra,
+            *(result->chrome_activity_detail->extra.get()));
+}
+
+TEST_F(ActivityLogApiUnitTest, ConvertDomAction) {
+  scoped_refptr<Action> action(
+      new DOMAction(kExtensionId,
+                    base::Time::Now(),
+                    DomActionType::SETTER,
+                    GURL("http://www.google.com"),
+                    base::ASCIIToUTF16("Title"),
+                    kApiCall,
+                    kArgs,
+                    kExtra));
+  scoped_ptr<ExtensionActivity> result =
+      action->ConvertToExtensionActivity();
+  ASSERT_EQ(ExtensionActivity::ACTIVITY_TYPE_DOM, result->activity_type);
+  ASSERT_EQ(kExtensionId, *(result->extension_id.get()));
+  ASSERT_EQ(DomActivityDetail::DOM_ACTIVITY_TYPE_SETTER,
+            result->dom_activity_detail->dom_activity_type);
+  ASSERT_EQ("http://www.google.com/",
+            *(result->dom_activity_detail->url.get()));
+  ASSERT_EQ("Title", *(result->dom_activity_detail->url_title.get()));
+  ASSERT_EQ(kApiCall,
+            *(result->dom_activity_detail->api_call.get()));
+  ASSERT_EQ(kArgs,
+            *(result->dom_activity_detail->args.get()));
+  ASSERT_EQ(kExtra,
+            *(result->dom_activity_detail->extra.get()));
+}
+
+}  // extensions
+
diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc b/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc
new file mode 100644
index 0000000..7e5d77e
--- /dev/null
+++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc
@@ -0,0 +1,35 @@
+// Copyright 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 <string>
+
+#include "chrome/browser/extensions/activity_log/activity_log.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension_builder.h"
+
+namespace extensions {
+
+class ActivityLogApiTest : public ExtensionApiTest {
+ public:
+  ActivityLogApiTest() {}
+
+  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+    ExtensionApiTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitch(switches::kEnableExtensionActivityLogging);
+    command_line->AppendSwitch(switches::kEnableExtensionActivityLogTesting);
+  }
+};
+
+// The test extension sends a message to its 'friend'. The test completes
+// if it successfully sees the 'friend' receive the message.
+IN_PROC_BROWSER_TEST_F(ActivityLogApiTest, TriggerEvent) {
+  const Extension* friend_extension =
+      LoadExtension(test_data_dir_.AppendASCII("activity_log_private/friend"));
+  ASSERT_TRUE(friend_extension);
+  ASSERT_TRUE(RunExtensionTest("activity_log_private/test"));
+}
+
+}  // namespace extensions
+
diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h
index 3a67231..3b7687a 100644
--- a/chrome/browser/extensions/extension_function_histogram_value.h
+++ b/chrome/browser/extensions/extension_function_histogram_value.h
@@ -546,6 +546,7 @@
   EXPERIMENTAL_SYSTEMINFO_STORAGE_GETALLWATCH,
   EXPERIMENTAL_SYSTEMINFO_STORAGE_REMOVEALLWATCH,
   SYSTEMINFO_MEMORY_GET,
+  ACTIVITYLOGPRIVATE_GETEXTENSIONACTIVITIES,
   ENUM_BOUNDARY // Last entry: Add new entries above.
 };
 
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 41c5ae86..41c3929 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
 #include "chrome/browser/download/download_service_factory.h"
 #include "chrome/browser/extensions/activity_log/activity_log.h"
+#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
 #include "chrome/browser/extensions/api/alarms/alarm_manager.h"
 #include "chrome/browser/extensions/api/audio/audio_api.h"
 #include "chrome/browser/extensions/api/bluetooth/bluetooth_api_factory.h"
@@ -183,6 +184,7 @@
   apps::ShortcutManagerFactory::GetInstance();
   autofill::autocheckout::WhitelistManagerFactory::GetInstance();
   extensions::ActivityLogFactory::GetInstance();
+  extensions::ActivityLogAPI::GetFactoryInstance();
   extensions::AlarmManager::GetFactoryInstance();
   extensions::AudioAPI::GetFactoryInstance();
   extensions::BookmarksAPI::GetFactoryInstance();