1 // Copyright 2014 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.
5 #include "chrome/browser/apps/drive/drive_app_provider.h"
10 #include "base/bind_helpers.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/stl_util.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "chrome/browser/apps/drive/drive_app_converter.h"
17 #include "chrome/browser/apps/drive/drive_app_mapping.h"
18 #include "chrome/browser/apps/drive/drive_app_uninstall_sync_service.h"
19 #include "chrome/browser/apps/drive/drive_service_bridge.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
23 #include "components/drive/drive_app_registry.h"
24 #include "extensions/browser/extension_registry.h"
25 #include "extensions/browser/extension_registry_factory.h"
26 #include "extensions/browser/extension_system.h"
27 #include "extensions/browser/uninstall_reason.h"
28 #include "extensions/common/extension.h"
30 using extensions::Extension
;
31 using extensions::ExtensionRegistry
;
35 void IgnoreUninstallResult(google_apis::DriveApiErrorCode
) {
40 DriveAppProvider::DriveAppProvider(
42 DriveAppUninstallSyncService
* uninstall_sync_service
)
44 uninstall_sync_service_(uninstall_sync_service
),
45 service_bridge_(DriveServiceBridge::Create(profile
).Pass()),
46 mapping_(new DriveAppMapping(profile
->GetPrefs())),
47 drive_app_registry_updated_(false),
48 weak_ptr_factory_(this) {
49 service_bridge_
->GetAppRegistry()->AddObserver(this);
50 ExtensionRegistry::Get(profile_
)->AddObserver(this);
53 DriveAppProvider::~DriveAppProvider() {
54 ExtensionRegistry::Get(profile_
)->RemoveObserver(this);
55 service_bridge_
->GetAppRegistry()->RemoveObserver(this);
59 void DriveAppProvider::AppendDependsOnFactories(
60 std::set
<BrowserContextKeyedServiceFactory
*>* factories
) {
61 factories
->insert(extensions::ExtensionRegistryFactory::GetInstance());
62 DriveServiceBridge::AppendDependsOnFactories(factories
);
65 void DriveAppProvider::SetDriveServiceBridgeForTest(
66 scoped_ptr
<DriveServiceBridge
> test_bridge
) {
67 service_bridge_
->GetAppRegistry()->RemoveObserver(this);
68 service_bridge_
= test_bridge
.Pass();
69 service_bridge_
->GetAppRegistry()->AddObserver(this);
72 void DriveAppProvider::AddUninstalledDriveAppFromSync(
73 const std::string
& drive_app_id
) {
74 mapping_
->AddUninstalledDriveApp(drive_app_id
);
76 // Decouple the operation because this function could be called during
77 // sync processing and UpdateDriveApps could trigger another sync change.
78 // See http://crbug.com/429205
79 base::ThreadTaskRunnerHandle::Get()->PostTask(
80 FROM_HERE
, base::Bind(&DriveAppProvider::UpdateDriveApps
,
81 weak_ptr_factory_
.GetWeakPtr()));
84 void DriveAppProvider::RemoveUninstalledDriveAppFromSync(
85 const std::string
& drive_app_id
) {
86 mapping_
->RemoveUninstalledDriveApp(drive_app_id
);
88 // Decouple the operation because this function could be called during
89 // sync processing and UpdateDriveApps could trigger another sync change.
90 // See http://crbug.com/429205
91 base::ThreadTaskRunnerHandle::Get()->PostTask(
92 FROM_HERE
, base::Bind(&DriveAppProvider::UpdateDriveApps
,
93 weak_ptr_factory_
.GetWeakPtr()));
96 void DriveAppProvider::UpdateMappingAndExtensionSystem(
97 const std::string
& drive_app_id
,
98 const Extension
* new_app
,
99 bool is_new_app_generated
) {
100 const std::string
& new_chrome_app_id
= new_app
->id();
102 const std::string existing_chrome_app_id
=
103 mapping_
->GetChromeApp(drive_app_id
);
104 if (existing_chrome_app_id
== new_chrome_app_id
)
107 const bool is_existing_app_generated
=
108 mapping_
->IsChromeAppGenerated(existing_chrome_app_id
);
109 mapping_
->Add(drive_app_id
, new_chrome_app_id
, is_new_app_generated
);
111 const Extension
* existing_app
=
112 ExtensionRegistry::Get(profile_
)->GetExtensionById(
113 existing_chrome_app_id
, ExtensionRegistry::EVERYTHING
);
114 if (existing_app
&& is_existing_app_generated
) {
115 extensions::ExtensionSystem::Get(profile_
)
116 ->extension_service()
117 ->UninstallExtension(existing_chrome_app_id
,
118 extensions::UNINSTALL_REASON_SYNC
,
119 base::Bind(&base::DoNothing
),
124 void DriveAppProvider::ProcessDeferredOnExtensionInstalled(
125 const std::string drive_app_id
,
126 const std::string chrome_app_id
) {
127 const Extension
* app
= ExtensionRegistry::Get(profile_
)->GetExtensionById(
128 chrome_app_id
, ExtensionRegistry::EVERYTHING
);
132 // Remove uninstall tracking for user installed app.
133 mapping_
->RemoveUninstalledDriveApp(drive_app_id
);
134 uninstall_sync_service_
->UntrackUninstalledDriveApp(drive_app_id
);
136 UpdateMappingAndExtensionSystem(drive_app_id
, app
, false);
139 void DriveAppProvider::SchedulePendingConverters() {
140 if (pending_converters_
.empty())
143 if (!pending_converters_
.front()->IsStarted())
144 pending_converters_
.front()->Start();
147 void DriveAppProvider::OnLocalAppConverted(const DriveAppConverter
* converter
,
149 DCHECK_EQ(pending_converters_
.front(), converter
);
152 const bool was_generated
=
153 mapping_
->GetDriveApp(converter
->extension()->id()) ==
154 converter
->drive_app_info().app_id
&&
155 mapping_
->IsChromeAppGenerated(converter
->extension()->id());
156 UpdateMappingAndExtensionSystem(
157 converter
->drive_app_info().app_id
,
158 converter
->extension(),
159 converter
->is_new_install() || was_generated
);
161 LOG(WARNING
) << "Failed to convert drive app to web app, "
162 << "drive app id= " << converter
->drive_app_info().app_id
163 << ", name=" << converter
->drive_app_info().app_name
;
166 pending_converters_
.erase(pending_converters_
.begin());
167 SchedulePendingConverters();
170 bool DriveAppProvider::IsMappedUrlAppUpToDate(
171 const drive::DriveAppInfo
& drive_app
) const {
172 const std::string
& url_app_id
= mapping_
->GetChromeApp(drive_app
.app_id
);
173 if (url_app_id
.empty())
176 const Extension
* url_app
= ExtensionRegistry::Get(profile_
)->GetExtensionById(
177 url_app_id
, ExtensionRegistry::EVERYTHING
);
180 DCHECK(url_app
->is_hosted_app() && url_app
->from_bookmark());
182 return drive_app
.app_name
== url_app
->name() &&
183 drive_app
.create_url
==
184 extensions::AppLaunchInfo::GetLaunchWebURL(url_app
);
187 void DriveAppProvider::AddOrUpdateDriveApp(
188 const drive::DriveAppInfo
& drive_app
) {
189 const Extension
* chrome_app
=
190 ExtensionRegistry::Get(profile_
)->GetExtensionById(
191 drive_app
.product_id
, ExtensionRegistry::EVERYTHING
);
193 UpdateMappingAndExtensionSystem(drive_app
.app_id
, chrome_app
, false);
197 if (IsMappedUrlAppUpToDate(drive_app
))
200 ScopedVector
<DriveAppConverter
>::iterator it
= pending_converters_
.begin();
201 while (it
!= pending_converters_
.end()) {
202 if (!(*it
)->IsStarted() &&
203 (*it
)->drive_app_info().app_id
== drive_app
.app_id
) {
204 it
= pending_converters_
.erase(it
);
210 pending_converters_
.push_back(
211 new DriveAppConverter(profile_
,
213 base::Bind(&DriveAppProvider::OnLocalAppConverted
,
214 base::Unretained(this))));
217 void DriveAppProvider::ProcessRemovedDriveApp(const std::string
& drive_app_id
) {
218 const std::string chrome_app_id
= mapping_
->GetChromeApp(drive_app_id
);
219 const bool is_generated
= mapping_
->IsChromeAppGenerated(chrome_app_id
);
220 mapping_
->Remove(drive_app_id
);
222 if (chrome_app_id
.empty() || !is_generated
)
225 const Extension
* existing_app
=
226 ExtensionRegistry::Get(profile_
)
227 ->GetExtensionById(chrome_app_id
, ExtensionRegistry::EVERYTHING
);
231 extensions::ExtensionSystem::Get(profile_
)
232 ->extension_service()
233 ->UninstallExtension(chrome_app_id
,
234 extensions::UNINSTALL_REASON_SYNC
,
235 base::Bind(&base::DoNothing
),
239 void DriveAppProvider::UpdateDriveApps() {
240 if (!drive_app_registry_updated_
)
243 service_bridge_
->GetAppRegistry()->GetAppList(&drive_apps_
);
246 for (size_t i
= 0; i
< drive_apps_
.size(); ++i
) {
247 const std::string
& drive_app_id
= drive_apps_
[i
].app_id
;
248 if (!mapping_
->IsUninstalledDriveApp(drive_app_id
) &&
249 drive_apps_
[i
].create_url
.is_valid()) {
250 current_ids
.insert(drive_app_id
);
254 const IdSet existing_ids
= mapping_
->GetDriveAppIds();
255 const IdSet ids_to_remove
=
256 base::STLSetDifference
<IdSet
>(existing_ids
, current_ids
);
257 for (IdSet::const_iterator it
= ids_to_remove
.begin();
258 it
!= ids_to_remove
.end();
260 ProcessRemovedDriveApp(*it
);
263 for (size_t i
= 0; i
< drive_apps_
.size(); ++i
) {
264 if (!mapping_
->IsUninstalledDriveApp(drive_apps_
[i
].app_id
) &&
265 drive_apps_
[i
].create_url
.is_valid()) {
266 AddOrUpdateDriveApp(drive_apps_
[i
]);
269 SchedulePendingConverters();
272 void DriveAppProvider::OnDriveAppRegistryUpdated() {
273 drive_app_registry_updated_
= true;
277 void DriveAppProvider::OnExtensionInstalled(
278 content::BrowserContext
* browser_context
,
279 const Extension
* extension
,
281 // Bail if the |extension| is installed from a converter. The post install
282 // processing will be handled in OnLocalAppConverted.
283 if (!pending_converters_
.empty() &&
284 pending_converters_
.front()->IsInstalling(extension
->id())) {
288 // Only user installed app reaches here. If it is mapped, make sure it is not
289 // tagged as generated.
290 const std::string drive_app_id
= mapping_
->GetDriveApp(extension
->id());
291 if (!drive_app_id
.empty() &&
292 mapping_
->IsChromeAppGenerated(extension
->id())) {
293 mapping_
->Add(drive_app_id
, extension
->id(), false);
297 for (size_t i
= 0; i
< drive_apps_
.size(); ++i
) {
298 if (drive_apps_
[i
].product_id
== extension
->id()) {
299 // Defer the processing because it touches the extensions system and
300 // it is better to let the current task finish to avoid unexpected
301 // incomplete status.
302 base::ThreadTaskRunnerHandle::Get()->PostTask(
304 base::Bind(&DriveAppProvider::ProcessDeferredOnExtensionInstalled
,
305 weak_ptr_factory_
.GetWeakPtr(), drive_apps_
[i
].app_id
,
312 void DriveAppProvider::OnExtensionUninstalled(
313 content::BrowserContext
* browser_context
,
314 const Extension
* extension
,
315 extensions::UninstallReason reason
) {
316 std::string drive_app_id
= mapping_
->GetDriveApp(extension
->id());
317 if (drive_app_id
.empty())
320 for (size_t i
= 0; i
< drive_apps_
.size(); ++i
) {
321 if (drive_apps_
[i
].app_id
!= drive_app_id
)
324 if (drive_apps_
[i
].is_removable
) {
325 service_bridge_
->GetAppRegistry()->UninstallApp(
326 drive_app_id
, base::Bind(&IgnoreUninstallResult
));
328 mapping_
->AddUninstalledDriveApp(drive_app_id
);
329 uninstall_sync_service_
->TrackUninstalledDriveApp(drive_app_id
);