Home | History | Annotate | Download | only in apps
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/apps/ephemeral_app_service.h"
      6 
      7 #include "base/command_line.h"
      8 #include "chrome/browser/apps/ephemeral_app_service_factory.h"
      9 #include "chrome/browser/chrome_notification_types.h"
     10 #include "chrome/browser/extensions/extension_service.h"
     11 #include "chrome/browser/extensions/extension_util.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/common/chrome_switches.h"
     14 #include "content/public/browser/notification_service.h"
     15 #include "content/public/browser/notification_source.h"
     16 #include "content/public/browser/notification_types.h"
     17 #include "extensions/browser/extension_prefs.h"
     18 #include "extensions/browser/extension_registry.h"
     19 #include "extensions/browser/extension_system.h"
     20 #include "extensions/browser/extension_util.h"
     21 #include "extensions/common/extension.h"
     22 #include "extensions/common/extension_set.h"
     23 
     24 using extensions::Extension;
     25 using extensions::ExtensionPrefs;
     26 using extensions::ExtensionSet;
     27 using extensions::ExtensionSystem;
     28 
     29 namespace {
     30 
     31 // The number of seconds after startup before performing garbage collection
     32 // of ephemeral apps.
     33 const int kGarbageCollectAppsStartupDelay = 60;
     34 
     35 // The number of seconds after an ephemeral app has been installed before
     36 // performing garbage collection.
     37 const int kGarbageCollectAppsInstallDelay = 15;
     38 
     39 // When the number of ephemeral apps reaches this count, trigger garbage
     40 // collection to trim off the least-recently used apps in excess of
     41 // kMaxEphemeralAppsCount.
     42 const int kGarbageCollectAppsTriggerCount = 35;
     43 
     44 }  // namespace
     45 
     46 const int EphemeralAppService::kAppInactiveThreshold = 10;
     47 const int EphemeralAppService::kAppKeepThreshold = 1;
     48 const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
     49 
     50 // static
     51 EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
     52   return EphemeralAppServiceFactory::GetForProfile(profile);
     53 }
     54 
     55 EphemeralAppService::EphemeralAppService(Profile* profile)
     56     : profile_(profile),
     57       extension_registry_observer_(this),
     58       ephemeral_app_count_(-1) {
     59   if (!CommandLine::ForCurrentProcess()->HasSwitch(
     60             switches::kEnableEphemeralApps))
     61     return;
     62 
     63   extension_registry_observer_.Add(
     64       extensions::ExtensionRegistry::Get(profile_));
     65   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
     66                  content::Source<Profile>(profile_));
     67   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
     68                  content::Source<Profile>(profile_));
     69 }
     70 
     71 EphemeralAppService::~EphemeralAppService() {
     72 }
     73 
     74 void EphemeralAppService::Observe(
     75     int type,
     76     const content::NotificationSource& source,
     77     const content::NotificationDetails& details) {
     78   switch (type) {
     79     case chrome::NOTIFICATION_EXTENSIONS_READY: {
     80       Init();
     81       break;
     82     }
     83     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
     84       // Ideally we need to know when the extension system is shutting down.
     85       garbage_collect_apps_timer_.Stop();
     86       break;
     87     }
     88     default:
     89       NOTREACHED();
     90   }
     91 }
     92 
     93 void EphemeralAppService::OnExtensionWillBeInstalled(
     94     content::BrowserContext* browser_context,
     95     const extensions::Extension* extension,
     96     bool is_update,
     97     bool from_ephemeral,
     98     const std::string& old_name) {
     99   if (from_ephemeral) {
    100     // An ephemeral app was just promoted to a regular installed app.
    101     --ephemeral_app_count_;
    102     DCHECK_GE(ephemeral_app_count_, 0);
    103   } else if (!is_update &&
    104              extensions::util::IsEphemeralApp(extension->id(), profile_)) {
    105     ++ephemeral_app_count_;
    106     if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
    107       TriggerGarbageCollect(
    108           base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
    109     }
    110   }
    111 }
    112 
    113 void EphemeralAppService::OnExtensionUninstalled(
    114     content::BrowserContext* browser_context,
    115     const extensions::Extension* extension) {
    116   if (extensions::util::IsEphemeralApp(extension->id(), profile_)) {
    117     --ephemeral_app_count_;
    118     DCHECK_GE(ephemeral_app_count_, 0);
    119   }
    120 }
    121 
    122 void EphemeralAppService::Init() {
    123   InitEphemeralAppCount();
    124   TriggerGarbageCollect(
    125       base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
    126 }
    127 
    128 void EphemeralAppService::InitEphemeralAppCount() {
    129   scoped_ptr<ExtensionSet> extensions =
    130       extensions::ExtensionRegistry::Get(profile_)
    131           ->GenerateInstalledExtensionsSet();
    132   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
    133   DCHECK(prefs);
    134 
    135   ephemeral_app_count_ = 0;
    136   for (ExtensionSet::const_iterator it = extensions->begin();
    137        it != extensions->end(); ++it) {
    138     const Extension* extension = *it;
    139     if (prefs->IsEphemeralApp(extension->id()))
    140       ++ephemeral_app_count_;
    141   }
    142 }
    143 
    144 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
    145   if (!garbage_collect_apps_timer_.IsRunning()) {
    146     garbage_collect_apps_timer_.Start(
    147         FROM_HERE,
    148         delay,
    149         this,
    150         &EphemeralAppService::GarbageCollectApps);
    151   }
    152 }
    153 
    154 void EphemeralAppService::GarbageCollectApps() {
    155   scoped_ptr<ExtensionSet> extensions =
    156       extensions::ExtensionRegistry::Get(profile_)
    157           ->GenerateInstalledExtensionsSet();
    158   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
    159   DCHECK(prefs);
    160 
    161   int app_count = 0;
    162   LaunchTimeAppMap app_launch_times;
    163   std::set<std::string> remove_app_ids;
    164 
    165   // Populate a list of ephemeral apps, ordered by their last launch time.
    166   for (ExtensionSet::const_iterator it = extensions->begin();
    167        it != extensions->end(); ++it) {
    168     const Extension* extension = *it;
    169     if (!prefs->IsEphemeralApp(extension->id()))
    170       continue;
    171 
    172     ++app_count;
    173 
    174     // Do not remove ephemeral apps that are running.
    175     if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
    176       continue;
    177 
    178     base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());
    179 
    180     // If the last launch time is invalid, this may be because it was just
    181     // installed. So use the install time. If this is also null for some reason,
    182     // the app will be removed.
    183     if (last_launch_time.is_null())
    184       last_launch_time = prefs->GetInstallTime(extension->id());
    185 
    186     app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
    187   }
    188 
    189   ExtensionService* service =
    190       ExtensionSystem::Get(profile_)->extension_service();
    191   DCHECK(service);
    192   // Execute the replacement policies and remove apps marked for deletion.
    193   if (!app_launch_times.empty()) {
    194     GetAppsToRemove(app_count, app_launch_times, &remove_app_ids);
    195     for (std::set<std::string>::const_iterator id = remove_app_ids.begin();
    196          id != remove_app_ids.end(); ++id) {
    197       if (service->UninstallExtension(*id, false, NULL))
    198         --app_count;
    199     }
    200   }
    201 
    202   ephemeral_app_count_ = app_count;
    203 }
    204 
    205 // static
    206 void EphemeralAppService::GetAppsToRemove(
    207     int app_count,
    208     const LaunchTimeAppMap& app_launch_times,
    209     std::set<std::string>* remove_app_ids) {
    210   base::Time time_now = base::Time::Now();
    211   const base::Time inactive_threshold =
    212       time_now - base::TimeDelta::FromDays(kAppInactiveThreshold);
    213   const base::Time keep_threshold =
    214       time_now - base::TimeDelta::FromDays(kAppKeepThreshold);
    215 
    216   // Visit the apps in order of least recently to most recently launched.
    217   for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin();
    218        it != app_launch_times.end(); ++it) {
    219     // Cannot remove apps that have been launched recently. So break when we
    220     // reach the new apps.
    221     if (it->first > keep_threshold)
    222         break;
    223 
    224     // Remove ephemeral apps that have been inactive for a while or if the cache
    225     // is larger than the desired size.
    226     if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) {
    227       remove_app_ids->insert(it->second);
    228       --app_count;
    229     } else {
    230       break;
    231     }
    232   }
    233 }
    234