File: promise_app_service.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (379 lines) | stat: -rw-r--r-- 14,880 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/apps/app_service/promise_apps/promise_app_service.h"

#include <memory>
#include <optional>

#include "base/check.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/package_id_util.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_almanac_connector.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_icon_cache.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_metrics.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_utils.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_wrapper.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_list/arc/arc_package_install_priority_handler.h"
#include "chrome/browser/image_fetcher/image_decoder_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/experiences/arc/arc_features.h"
#include "components/image_fetcher/core/image_fetcher_impl.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "components/services/app_service/public/cpp/package_id.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "url/gurl.h"

namespace {

const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("promise_app_service_download_icon",
                                        R"(
    semantics {
      sender: "Promise App Service"
      description:
        "Queries a Google server to fetch the icon of an app that is being "
        "installed or is pending installation on the device."
      trigger:
        "A request can be sent when an app starts installing or is pending "
        "installation."
      destination: GOOGLE_OWNED_SERVICE
      internal {
        contacts {
          email: "chromeos-apps-foundation-team@google.com"
        }
      }
      user_data {
        type: SENSITIVE_URL
      }
      data: "URL of the image to be fetched."
      last_reviewed: "2023-05-16"
    }
    policy {
      cookies_allowed: NO
      setting:
        "This request is enabled by app sync without passphrase. You can"
        "disable this request in the 'Sync and Google services' section"
        "in Settings by either: 1. Going into the 'Manage What You Sync'"
        "settings page and turning off Apps sync; OR 2. In the 'Encryption"
        "Options' settings page, select the option to use a sync passphrase."
      policy_exception_justification:
        "This feature is required to deliver core user experiences and "
        "cannot be disabled by policy."
    }
  )");

apps::PromiseAppType GetPromiseAppType(apps::PackageType promise_app_type,
                                       apps::AppType installed_app_type) {
  if (promise_app_type == apps::PackageType::kArc &&
      installed_app_type == apps::AppType::kArc) {
    return apps::PromiseAppType::kArc;
  }
  if (promise_app_type == apps::PackageType::kArc &&
      installed_app_type == apps::AppType::kWeb) {
    return apps::PromiseAppType::kTwa;
  }
  return apps::PromiseAppType::kUnknown;
}

}  // namespace

namespace apps {
PromiseAppService::PromiseAppService(Profile* profile,
                                     AppRegistryCache& app_registry_cache)
    : profile_(profile),
      promise_app_registry_cache_(
          std::make_unique<apps::PromiseAppRegistryCache>()),
      promise_app_almanac_connector_(
          std::make_unique<PromiseAppAlmanacConnector>(profile)),
      promise_app_icon_cache_(std::make_unique<apps::PromiseAppIconCache>()),
      image_fetcher_(std::make_unique<image_fetcher::ImageFetcherImpl>(
          std::make_unique<ImageDecoderImpl>(),
          profile->GetURLLoaderFactory())),
      app_registry_cache_(&app_registry_cache) {
  app_registry_cache_observation_.Observe(&app_registry_cache);
}

PromiseAppService::~PromiseAppService() = default;

PromiseAppRegistryCache* PromiseAppService::PromiseAppRegistryCache() {
  return promise_app_registry_cache_.get();
}

PromiseAppIconCache* PromiseAppService::PromiseAppIconCache() {
  return promise_app_icon_cache_.get();
}

void PromiseAppService::OnPromiseApp(PromiseAppPtr delta) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const PackageId package_id = delta->package_id;
  bool is_new_promise_app =
      !promise_app_registry_cache_->HasPromiseApp(package_id);

  // If the app is in the AppRegistryCache, then it already has an item in the
  // Launcher/ Shelf and we don't need to create a new promise app item to
  // represent it. This scenario happens when we start installing a default ARC
  // app (which is a stubbed app in App Registry Cache to show an icon in the
  // Launcher/ Shelf but uses legacy ARC default apps implementation).
  // TODO(b/286981938): Remove this check after refactoring to allow the Promise
  // App Service to manage ARC default app icons.
  if (is_new_promise_app && IsRegisteredInAppRegistryCache(package_id)) {
    return;
  }

  promise_app_registry_cache_->OnPromiseApp(std::move(delta));

  // If the promise app is newly removed, clear out the icons.
  if (!promise_app_registry_cache_->HasPromiseApp(package_id)) {
    promise_app_icon_cache_->RemoveIconsForPackageId(package_id);
  }

  // If this is a new promise app, send an Almanac request to fetch more
  // details.
  if (is_new_promise_app && !skip_almanac_for_testing_) {
    promise_app_almanac_connector_->GetPromiseAppInfo(
        package_id,
        base::BindOnce(&PromiseAppService::OnGetPromiseAppInfoCompleted,
                       weak_ptr_factory_.GetWeakPtr(), package_id));
  }
}

void PromiseAppService::LoadIcon(const PackageId& package_id,
                                 int32_t size_hint_in_dip,
                                 apps::IconEffects icon_effects,
                                 apps::LoadIconCallback callback) {
  promise_app_icon_cache_->GetIconAndApplyEffects(
      package_id, size_hint_in_dip, icon_effects, std::move(callback));
}

void PromiseAppService::OnAppUpdate(const apps::AppUpdate& update) {
  // Check that the update is for a new installation.
  if (!update.ReadinessChanged() ||
      update.Readiness() != apps::Readiness::kReady ||
      apps_util::IsInstalled(update.PriorReadiness())) {
    return;
  }

  std::optional<PackageId> package_id =
      apps_util::GetPackageIdForApp(profile_.get(), update);
  if (!package_id.has_value()) {
    return;
  }

  // Check that the update corresponds to a registered promise app.
  if (!promise_app_registry_cache_->HasPromiseApp(package_id.value())) {
    return;
  }

  // Record metrics for app type, noting that the app type may differ between
  // the promise app and the installed app.
  RecordPromiseAppType(
      GetPromiseAppType(package_id->package_type(), update.AppType()));

  // Delete the promise app.
  PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id.value());
  promise_app->status = PromiseStatus::kSuccess;
  promise_app->installed_app_id = update.AppId();
  OnPromiseApp(std::move(promise_app));
}

void PromiseAppService::OnAppRegistryCacheWillBeDestroyed(
    apps::AppRegistryCache* cache) {
  app_registry_cache_observation_.Reset();
  app_registry_cache_ = nullptr;
}

void PromiseAppService::SetSkipAlmanacForTesting(bool skip_almanac) {
  skip_almanac_for_testing_ = skip_almanac;
}

void PromiseAppService::SetSkipApiKeyCheckForTesting(bool skip_api_key_check) {
  promise_app_almanac_connector_->SetSkipApiKeyCheckForTesting(  // IN-TEST
      skip_api_key_check);
}

void PromiseAppService::UpdateInstallPriority(const std::string& id) {
  const auto* promise_app =
      promise_app_registry_cache_->GetPromiseAppForStringPackageId(id);
  CHECK(promise_app);

  // Currently, updating install priority is only supported for ARC promise
  // apps.
  if (promise_app->package_id.package_type() != PackageType::kArc) {
    return;
  }

  // Feature flag that enables interacing with promise icon.
  if (!base::FeatureList::IsEnabled(arc::kSyncInstallPriority)) {
    return;
  }

  // We can only increase install priority for packages that are
  // queued/ pending. Promise apps that are already actively installing are
  // already treated as the highest priority installation and their installation
  // progress cannot be accelerated any further.
  if (promise_app->status != PromiseStatus::kPending) {
    return;
  }

  ArcAppListPrefs* arc_app_list_prefs = ArcAppListPrefs::Get(profile_);
  CHECK(arc_app_list_prefs);

  arc_app_list_prefs->GetInstallPriorityHandler()->PromotePackageInstall(
      promise_app->package_id.identifier());
}

void PromiseAppService::OnGetPromiseAppInfoCompleted(
    const PackageId& package_id,
    std::optional<PromiseAppWrapper> promise_app_info) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // If the promise app doesn't exist in the registry, drop the update. The app
  // installation may have completed before the Almanac returned a response.
  if (!promise_app_registry_cache_->HasPromiseApp(package_id)) {
    LOG(ERROR) << "Cannot update promise app " << package_id.ToString()
               << " as it does not exist in PromiseAppRegistry";
    return;
  }

  // If Almanac doesn't provide any meaningful response, continue to show the
  // promise app item. When an icon is requested, the PromiseAppIconCache will
  // fallback to returning a placeholder icon.
  if (!promise_app_info.has_value() ||
      !promise_app_info->GetName().has_value() ||
      promise_app_info->GetIcons().size() == 0) {
    RecordPromiseAppIconType(PromiseAppIconType::kPlaceholderIcon);
    SetPromiseAppReadyToShow(package_id);
    return;
  }

  PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id);
  promise_app->name = promise_app_info->GetName().value();
  promise_app_registry_cache_->OnPromiseApp(std::move(promise_app));

  pending_download_count_[package_id] = promise_app_info->GetIcons().size();

  for (auto icon : promise_app_info->GetIcons()) {
    image_fetcher_->FetchImage(
        icon.GetUrl(),
        base::BindOnce(&PromiseAppService::OnIconDownloaded,
                       weak_ptr_factory_.GetWeakPtr(), package_id),
        image_fetcher::ImageFetcherParams(kTrafficAnnotation,
                                          "Promise App Service Icon Fetcher"));
  }
}

void PromiseAppService::OnIconDownloaded(
    const PackageId& package_id,
    const gfx::Image& image,
    const image_fetcher::RequestMetadata& metadata) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // If we weren't expecting an icon to be downloaded for this package ID, don't
  // process the result.
  if (!pending_download_count_.contains(package_id)) {
    LOG(ERROR) << "Will not save icon for unexpected package ID: "
               << package_id.ToString();
    return;
  }
  if (pending_download_count_[package_id] == 0) {
    LOG(ERROR) << "Will not save icon for unexpected package ID: "
               << package_id.ToString();
    pending_download_count_.erase(package_id);
    return;
  }

  // Save valid icons to the icon cache.
  if (!image.IsEmpty()) {
    PromiseAppIconPtr promise_app_icon = std::make_unique<PromiseAppIcon>();
    promise_app_icon->icon = image.AsBitmap();
    promise_app_icon->width_in_pixels = promise_app_icon->icon.width();
    promise_app_icon_cache_->SaveIcon(package_id, std::move(promise_app_icon));
  }

  // If there are still icons to be downloaded, we should wait for those
  // downloads to finish before updating the promise app. Otherwise, stop
  // tracking pending downloads for this package ID.
  pending_download_count_[package_id] -= 1;
  if (pending_download_count_[package_id] > 0) {
    return;
  }
  pending_download_count_.erase(package_id);
  RecordPromiseAppIconType(
      promise_app_icon_cache_->DoesPackageIdHaveIcons(package_id)
          ? PromiseAppIconType::kRealIcon
          : PromiseAppIconType::kPlaceholderIcon);

  // Update the promise app so it can show to the user.
  SetPromiseAppReadyToShow(package_id);
}

bool PromiseAppService::IsRegisteredInAppRegistryCache(
    const PackageId& package_id) {
  if (!app_registry_cache_) {
    return false;
  }
  bool is_registered = false;
  app_registry_cache_->ForEachApp(
      [&package_id, &is_registered](const AppUpdate& update) {
        // TODO(b/297296711): Update check for TWAs, which can have differing
        // package IDs.
        if (ConvertPackageTypeToAppType(package_id.package_type()) !=
            update.AppType()) {
          return;
        }
        if (update.PublisherId() != package_id.identifier()) {
          return;
        }
        if (!apps_util::IsInstalled(update.Readiness())) {
          // It's possible for an app to be in the AppRegistryCache despite
          // being uninstalled. Do not consider this as a registered
          // installed app.
          return;
        }
        is_registered = true;
        return;
      });
  return is_registered;
}

void PromiseAppService::SetPromiseAppReadyToShow(const PackageId& package_id) {
  PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id);
  promise_app->should_show = true;
  promise_app_registry_cache_->OnPromiseApp(std::move(promise_app));
}

void PromiseAppService::OnApkWebAppInstallationFinished(
    const std::string& package_name) {
  PackageId package_id(PackageType::kArc, package_name);

  // Successful APK web app installations are already handled during a call to
  // observers via AppRegistryCache::OnAppUpdate which happens before this
  // method is called.
  if (!promise_app_registry_cache_->HasPromiseApp(package_id)) {
    return;
  }

  // We get to this point if the APK web installation failed. In this case, we
  // should remove the promise app and consider it a cancellation.
  PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id);
  promise_app->status = PromiseStatus::kCancelled;
  OnPromiseApp(std::move(promise_app));
}

}  // namespace apps