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 "apps/app_lifetime_monitor_factory.h"
      8 #include "base/command_line.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "chrome/browser/apps/ephemeral_app_service_factory.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/extensions/extension_service.h"
     13 #include "chrome/browser/extensions/extension_util.h"
     14 #include "chrome/browser/profiles/profile.h"
     15 #include "chrome/common/chrome_switches.h"
     16 #include "content/public/browser/notification_service.h"
     17 #include "content/public/browser/notification_source.h"
     18 #include "content/public/browser/notification_types.h"
     19 #include "extensions/browser/extension_prefs.h"
     20 #include "extensions/browser/extension_registry.h"
     21 #include "extensions/browser/extension_system.h"
     22 #include "extensions/browser/extension_util.h"
     23 #include "extensions/browser/notification_types.h"
     24 #include "extensions/browser/uninstall_reason.h"
     25 #include "extensions/common/extension.h"
     26 #include "extensions/common/extension_set.h"
     27 
     28 using extensions::Extension;
     29 using extensions::ExtensionPrefs;
     30 using extensions::ExtensionRegistry;
     31 using extensions::ExtensionSet;
     32 using extensions::ExtensionSystem;
     33 
     34 namespace {
     35 
     36 // The number of seconds after startup before performing garbage collection
     37 // of ephemeral apps.
     38 const int kGarbageCollectAppsStartupDelay = 60;
     39 
     40 // The number of seconds after an ephemeral app has been installed before
     41 // performing garbage collection.
     42 const int kGarbageCollectAppsInstallDelay = 15;
     43 
     44 // When the number of ephemeral apps reaches this count, trigger garbage
     45 // collection to trim off the least-recently used apps in excess of
     46 // kMaxEphemeralAppsCount.
     47 const int kGarbageCollectAppsTriggerCount = 35;
     48 
     49 // The number of seconds after an app has stopped running before it will be
     50 // disabled.
     51 const int kDefaultDisableAppDelay = 1;
     52 
     53 // The number of seconds after startup before disabling inactive ephemeral apps.
     54 const int kDisableAppsOnStartupDelay = 5;
     55 
     56 }  // namespace
     57 
     58 const int EphemeralAppService::kAppInactiveThreshold = 10;
     59 const int EphemeralAppService::kAppKeepThreshold = 1;
     60 const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
     61 
     62 // static
     63 EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
     64   return EphemeralAppServiceFactory::GetForProfile(profile);
     65 }
     66 
     67 EphemeralAppService::EphemeralAppService(Profile* profile)
     68     : profile_(profile),
     69       extension_registry_observer_(this),
     70       app_lifetime_monitor_observer_(this),
     71       ephemeral_app_count_(-1),
     72       disable_idle_app_delay_(kDefaultDisableAppDelay),
     73       weak_ptr_factory_(this) {
     74   registrar_.Add(this,
     75                  extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
     76                  content::Source<Profile>(profile_));
     77 }
     78 
     79 EphemeralAppService::~EphemeralAppService() {
     80 }
     81 
     82 void EphemeralAppService::ClearCachedApps() {
     83   // Cancel any pending garbage collects.
     84   garbage_collect_apps_timer_.Stop();
     85 
     86   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
     87   DCHECK(registry);
     88   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
     89   DCHECK(prefs);
     90   ExtensionService* service =
     91       ExtensionSystem::Get(profile_)->extension_service();
     92   DCHECK(service);
     93 
     94   scoped_ptr<ExtensionSet> extensions =
     95       registry->GenerateInstalledExtensionsSet();
     96 
     97   for (ExtensionSet::const_iterator it = extensions->begin();
     98        it != extensions->end();
     99        ++it) {
    100     std::string extension_id = (*it)->id();
    101     if (!prefs->IsEphemeralApp(extension_id))
    102       continue;
    103 
    104     // Do not remove apps that are running.
    105     if (!extensions::util::IsExtensionIdle(extension_id, profile_))
    106       continue;
    107 
    108     DCHECK(registry->GetExtensionById(extension_id,
    109                                       ExtensionRegistry::EVERYTHING));
    110     service->UninstallExtension(
    111         extension_id,
    112         extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
    113         base::Bind(&base::DoNothing),
    114         NULL);
    115   }
    116 }
    117 
    118 void EphemeralAppService::Observe(
    119     int type,
    120     const content::NotificationSource& source,
    121     const content::NotificationDetails& details) {
    122   switch (type) {
    123     case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
    124       Init();
    125       break;
    126     }
    127     default:
    128       NOTREACHED();
    129   }
    130 }
    131 
    132 void EphemeralAppService::OnExtensionWillBeInstalled(
    133     content::BrowserContext* browser_context,
    134     const extensions::Extension* extension,
    135     bool is_update,
    136     bool from_ephemeral,
    137     const std::string& old_name) {
    138   if (from_ephemeral) {
    139     // An ephemeral app was just promoted to a regular installed app.
    140     --ephemeral_app_count_;
    141     DCHECK_GE(ephemeral_app_count_, 0);
    142     HandleEphemeralAppPromoted(extension);
    143   } else if (!is_update &&
    144              extensions::util::IsEphemeralApp(extension->id(), profile_)) {
    145     // A new ephemeral app was launched.
    146     ++ephemeral_app_count_;
    147     if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
    148       TriggerGarbageCollect(
    149           base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
    150     }
    151   }
    152 }
    153 
    154 void EphemeralAppService::OnExtensionUninstalled(
    155     content::BrowserContext* browser_context,
    156     const extensions::Extension* extension,
    157     extensions::UninstallReason reason) {
    158   if (extensions::util::IsEphemeralApp(extension->id(), profile_)) {
    159     --ephemeral_app_count_;
    160     DCHECK_GE(ephemeral_app_count_, 0);
    161   }
    162 }
    163 
    164 void EphemeralAppService::OnAppStop(Profile* profile,
    165                                     const std::string& app_id) {
    166   if (!extensions::util::IsEphemeralApp(app_id, profile_))
    167     return;
    168 
    169   base::MessageLoop::current()->PostDelayedTask(
    170       FROM_HERE,
    171       base::Bind(&EphemeralAppService::DisableEphemeralApp,
    172                  weak_ptr_factory_.GetWeakPtr(),
    173                  app_id),
    174       base::TimeDelta::FromSeconds(disable_idle_app_delay_));
    175 }
    176 
    177 void EphemeralAppService::OnChromeTerminating() {
    178   garbage_collect_apps_timer_.Stop();
    179 
    180   extension_registry_observer_.RemoveAll();
    181   app_lifetime_monitor_observer_.RemoveAll();
    182 }
    183 
    184 void EphemeralAppService::Init() {
    185   InitEphemeralAppCount();
    186 
    187   // Start observing.
    188   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
    189   app_lifetime_monitor_observer_.Add(
    190       apps::AppLifetimeMonitorFactory::GetForProfile(profile_));
    191 
    192   // Execute startup clean up tasks (except during tests).
    193   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType))
    194     return;
    195 
    196   TriggerGarbageCollect(
    197       base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
    198 
    199   base::MessageLoop::current()->PostDelayedTask(
    200       FROM_HERE,
    201       base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup,
    202                  weak_ptr_factory_.GetWeakPtr()),
    203       base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay));
    204 }
    205 
    206 void EphemeralAppService::InitEphemeralAppCount() {
    207   scoped_ptr<ExtensionSet> extensions =
    208       ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
    209   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
    210   DCHECK(prefs);
    211 
    212   ephemeral_app_count_ = 0;
    213   for (ExtensionSet::const_iterator it = extensions->begin();
    214        it != extensions->end(); ++it) {
    215     const Extension* extension = it->get();
    216     if (prefs->IsEphemeralApp(extension->id()))
    217       ++ephemeral_app_count_;
    218   }
    219 }
    220 
    221 void EphemeralAppService::DisableEphemeralApp(const std::string& app_id) {
    222   if (!extensions::util::IsEphemeralApp(app_id, profile_) ||
    223       !extensions::util::IsExtensionIdle(app_id, profile_)) {
    224     return;
    225   }
    226 
    227   // After an ephemeral app has stopped running, unload it from extension
    228   // system and disable it to prevent all background activity.
    229   ExtensionService* service =
    230       ExtensionSystem::Get(profile_)->extension_service();
    231   DCHECK(service);
    232   service->DisableExtension(app_id, Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
    233 }
    234 
    235 void EphemeralAppService::DisableEphemeralAppsOnStartup() {
    236   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
    237   DCHECK(prefs);
    238   ExtensionService* service =
    239       ExtensionSystem::Get(profile_)->extension_service();
    240   DCHECK(service);
    241 
    242   // Ensure that all inactive ephemeral apps are disabled to prevent all
    243   // background activity. This is done on startup to catch any apps that escaped
    244   // being disabled on shutdown.
    245   scoped_ptr<ExtensionSet> extensions =
    246       ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
    247   for (ExtensionSet::const_iterator it = extensions->begin();
    248        it != extensions->end();
    249        ++it) {
    250     const Extension* extension = it->get();
    251     if (!prefs->IsEphemeralApp(extension->id()))
    252       continue;
    253 
    254     // Only V2 apps are installed ephemerally. Remove other ephemeral app types
    255     // that were cached before this policy was introduced.
    256     if (!extension->is_platform_app()) {
    257       service->UninstallExtension(
    258           extension->id(),
    259           extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
    260           base::Bind(&base::DoNothing),
    261           NULL);
    262       continue;
    263     }
    264 
    265     if (!prefs->HasDisableReason(extension->id(),
    266                                  Extension::DISABLE_INACTIVE_EPHEMERAL_APP) &&
    267         !prefs->IsExtensionRunning(extension->id()) &&
    268         extensions::util::IsExtensionIdle(extension->id(), profile_)) {
    269       service->DisableExtension(extension->id(),
    270                                 Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
    271     }
    272   }
    273 }
    274 
    275 void EphemeralAppService::HandleEphemeralAppPromoted(const Extension* app) {
    276   // When ephemeral apps are promoted to regular install apps, remove the
    277   // DISABLE_INACTIVE_EPHEMERAL_APP reason and enable the app if there are no
    278   // other reasons.
    279   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
    280   DCHECK(prefs);
    281 
    282   int disable_reasons = prefs->GetDisableReasons(app->id());
    283   if (disable_reasons & Extension::DISABLE_INACTIVE_EPHEMERAL_APP) {
    284     prefs->RemoveDisableReason(app->id(),
    285                                Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
    286     if (disable_reasons == Extension::DISABLE_INACTIVE_EPHEMERAL_APP)
    287       prefs->SetExtensionState(app->id(), Extension::ENABLED);
    288   }
    289 }
    290 
    291 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
    292   if (!garbage_collect_apps_timer_.IsRunning()) {
    293     garbage_collect_apps_timer_.Start(
    294         FROM_HERE,
    295         delay,
    296         this,
    297         &EphemeralAppService::GarbageCollectApps);
    298   }
    299 }
    300 
    301 void EphemeralAppService::GarbageCollectApps() {
    302   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
    303   DCHECK(registry);
    304   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
    305   DCHECK(prefs);
    306 
    307   scoped_ptr<ExtensionSet> extensions =
    308       registry->GenerateInstalledExtensionsSet();
    309 
    310   int app_count = 0;
    311   LaunchTimeAppMap app_launch_times;
    312   std::set<std::string> remove_app_ids;
    313 
    314   // Populate a list of ephemeral apps, ordered by their last launch time.
    315   for (ExtensionSet::const_iterator it = extensions->begin();
    316        it != extensions->end(); ++it) {
    317     const Extension* extension = it->get();
    318     if (!prefs->IsEphemeralApp(extension->id()))
    319       continue;
    320 
    321     ++app_count;
    322 
    323     // Do not remove ephemeral apps that are running.
    324     if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
    325       continue;
    326 
    327     base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());
    328 
    329     // If the last launch time is invalid, this may be because it was just
    330     // installed. So use the install time. If this is also null for some reason,
    331     // the app will be removed.
    332     if (last_launch_time.is_null())
    333       last_launch_time = prefs->GetInstallTime(extension->id());
    334 
    335     app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
    336   }
    337 
    338   ExtensionService* service =
    339       ExtensionSystem::Get(profile_)->extension_service();
    340   DCHECK(service);
    341   // Execute the eviction policies and remove apps marked for deletion.
    342   if (!app_launch_times.empty()) {
    343     GetAppsToRemove(app_count, app_launch_times, &remove_app_ids);
    344 
    345     for (std::set<std::string>::const_iterator id = remove_app_ids.begin();
    346          id != remove_app_ids.end(); ++id) {
    347       // Protect against cascading uninstalls.
    348       if (!registry->GetExtensionById(*id, ExtensionRegistry::EVERYTHING))
    349         continue;
    350 
    351       service->UninstallExtension(
    352           *id,
    353           extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
    354           base::Bind(&base::DoNothing),
    355           NULL);
    356     }
    357   }
    358 }
    359 
    360 // static
    361 void EphemeralAppService::GetAppsToRemove(
    362     int app_count,
    363     const LaunchTimeAppMap& app_launch_times,
    364     std::set<std::string>* remove_app_ids) {
    365   base::Time time_now = base::Time::Now();
    366   const base::Time inactive_threshold =
    367       time_now - base::TimeDelta::FromDays(kAppInactiveThreshold);
    368   const base::Time keep_threshold =
    369       time_now - base::TimeDelta::FromDays(kAppKeepThreshold);
    370 
    371   // Visit the apps in order of least recently to most recently launched.
    372   for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin();
    373        it != app_launch_times.end(); ++it) {
    374     // Cannot remove apps that have been launched recently. So break when we
    375     // reach the new apps.
    376     if (it->first > keep_threshold)
    377         break;
    378 
    379     // Remove ephemeral apps that have been inactive for a while or if the cache
    380     // is larger than the desired size.
    381     if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) {
    382       remove_app_ids->insert(it->second);
    383       --app_count;
    384     } else {
    385       break;
    386     }
    387   }
    388 }
    389