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