File: remote_apps_manager.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 (457 lines) | stat: -rw-r--r-- 16,758 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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
// Copyright 2020 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/ash/remote_apps/remote_apps_manager.h"

#include <utility>

#include "ash/app_list/app_list_controller_impl.h"
#include "ash/public/cpp/app_menu_constants.h"
#include "ash/public/cpp/image_downloader.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "cc/paint/paint_flags.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/menu_util.h"
#include "chrome/browser/ash/app_list/app_list_model_updater.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ash/app_list/app_list_util.h"
#include "chrome/browser/ash/app_list/chrome_app_list_item.h"
#include "chrome/browser/ash/app_list/chrome_app_list_model_updater.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/remote_apps/remote_apps_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/apps/platform_apps/api/enterprise_remote_apps.h"
#include "chrome/grit/generated_resources.h"
#include "components/account_id/account_id.h"
#include "components/services/app_service/public/cpp/menu.h"
#include "components/user_manager/user.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_event_histogram_value.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"

namespace ash {

namespace {

constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("remote_apps_image_downloader", R"(
        semantics {
          sender: "Remote Apps Manager"
          description: "Fetches icons for Remote Apps."
          trigger:
            "Triggered when a Remote App is added to the ChromeOS launcher. "
            "Remote Apps can only be added by allowlisted extensions "
            "installed by enterprise policy."
          data: "No user data."
          destination: OTHER
          destination_other: "Icon URL of the Remote App"
        }
        policy {
          cookies_allowed: NO
          setting: "This request cannot be disabled."
          policy_exception_justification:
            "This request is only performed by allowlisted extensions "
            "installed by enterprise policy."
        }
      )");

class ImageDownloaderImpl : public RemoteAppsManager::ImageDownloader {
 public:
  explicit ImageDownloaderImpl(const Profile* profile) : profile_(profile) {}
  ImageDownloaderImpl(const ImageDownloaderImpl&) = delete;
  ImageDownloaderImpl& operator=(const ImageDownloaderImpl&) = delete;
  ~ImageDownloaderImpl() override = default;

  void Download(const GURL& url, DownloadCallback callback) override {
    ash::ImageDownloader* image_downloader = ash::ImageDownloader::Get();
    DCHECK(image_downloader);
    auto* const user = ProfileHelper::Get()->GetUserByProfile(profile_);
    DCHECK(user);
    const AccountId& account_id = user->GetAccountId();
    image_downloader->Download(url, kTrafficAnnotation, account_id,
                               std::move(callback));
  }

 private:
  const raw_ptr<const Profile> profile_;
};

// Placeholder icon which shows the first letter of the app's name on top of a
// gray circle.
class RemoteAppsPlaceholderIcon : public gfx::CanvasImageSource {
 public:
  RemoteAppsPlaceholderIcon(const std::string& name, int32_t size)
      : gfx::CanvasImageSource(gfx::Size(size, size)) {
    std::u16string sanitized_name = base::UTF8ToUTF16(std::string(name));
    base::i18n::UnadjustStringForLocaleDirection(&sanitized_name);
    letter_ = sanitized_name.substr(0, 1);

    if (size <= 16)
      font_style_ = ui::ResourceBundle::SmallFont;
    else if (size <= 32)
      font_style_ = ui::ResourceBundle::MediumFont;
    else
      font_style_ = ui::ResourceBundle::LargeFont;
  }
  RemoteAppsPlaceholderIcon(const RemoteAppsPlaceholderIcon&) = delete;
  RemoteAppsPlaceholderIcon& operator=(const RemoteAppsPlaceholderIcon&) =
      delete;
  ~RemoteAppsPlaceholderIcon() override = default;

 private:
  // gfx::CanvasImageSource:
  void Draw(gfx::Canvas* canvas) override {
    const gfx::Size& icon_size = size();
    float width = static_cast<float>(icon_size.width());
    float height = static_cast<float>(icon_size.height());

    // Draw gray circle.
    cc::PaintFlags flags;
    flags.setAntiAlias(true);
    flags.setColor(SK_ColorGRAY);
    flags.setStyle(cc::PaintFlags::kFill_Style);
    canvas->DrawCircle(gfx::PointF(width / 2, height / 2), width / 2, flags);

    // Draw the letter on top.
    canvas->DrawStringRectWithFlags(
        letter_,
        ui::ResourceBundle::GetSharedInstance().GetFontList(font_style_),
        SK_ColorWHITE, gfx::Rect(icon_size.width(), icon_size.height()),
        gfx::Canvas::TEXT_ALIGN_CENTER);
  }

  // The first letter of the app's name.
  std::u16string letter_;
  ui::ResourceBundle::FontStyle font_style_ = ui::ResourceBundle::MediumFont;
};

}  // namespace

RemoteAppsManager::RemoteAppsManager(Profile* profile)
    : profile_(profile),
      event_router_(extensions::EventRouter::Get(profile)),
      remote_apps_(std::make_unique<apps::RemoteApps>(
          apps::AppServiceProxyFactory::GetForProfile(profile_),
          this)),
      model_(std::make_unique<RemoteAppsModel>()),
      image_downloader_(std::make_unique<ImageDownloaderImpl>(profile)) {
  remote_apps_->Initialize();
  app_list_syncable_service_ =
      app_list::AppListSyncableServiceFactory::GetForProfile(profile_);
  model_updater_ = app_list_syncable_service_->GetModelUpdater();
  app_list_model_updater_observation_.Observe(model_updater_.get());

  // |AppListSyncableService| manages the Chrome side AppList and has to be
  // initialized before apps can be added.
  if (app_list_syncable_service_->IsInitialized()) {
    Initialize();
  } else {
    app_list_syncable_service_observation_.Observe(
        app_list_syncable_service_.get());
  }
}

RemoteAppsManager::~RemoteAppsManager() = default;

void RemoteAppsManager::Initialize() {
  DCHECK(app_list_syncable_service_->IsInitialized());
  is_initialized_ = true;
}

void RemoteAppsManager::AddApp(const std::string& source_id,
                               const std::string& name,
                               const std::string& folder_id,
                               const GURL& icon_url,
                               bool add_to_front,
                               AddAppCallback callback) {
  if (!is_initialized_) {
    std::move(callback).Run(std::string(), RemoteAppsError::kNotReady);
    return;
  }

  if (!folder_id.empty() && !model_->HasFolder(folder_id)) {
    std::move(callback).Run(std::string(),
                            RemoteAppsError::kFolderIdDoesNotExist);
    return;
  }

  if (!folder_id.empty()) {
    // Disable |add_to_front| if app has a parent folder.
    add_to_front = false;

    // Ensure that the parent folder exists before adding the app.
    MaybeAddFolder(folder_id);
  }

  const RemoteAppsModel::AppInfo& info =
      model_->AddApp(name, icon_url, folder_id, add_to_front);
  add_app_callback_map_.insert({info.id, std::move(callback)});
  remote_apps_->AddApp(info);
  app_id_to_source_id_map_.insert(
      std::pair<std::string, std::string>(info.id, source_id));
}

void RemoteAppsManager::MaybeAddFolder(const std::string& folder_id) {
  // If the specified folder already exists, nothing to do.
  if (model_updater_->FindFolderItem(folder_id))
    return;

  DCHECK(!model_updater_->FindItem(folder_id));

  // The folder to be added.
  auto remote_folder =
      std::make_unique<ChromeAppListItem>(profile_, folder_id, model_updater_);

  const app_list::AppListSyncableService::SyncItem* sync_item =
      app_list_syncable_service_->GetSyncItem(folder_id);
  if (sync_item) {
    // If the specified folder's sync data exists, fill `remote_folder` with
    // the sync data.
    DCHECK_EQ(sync_pb::AppListSpecifics::TYPE_FOLDER, sync_item->item_type);
    remote_folder->SetMetadata(
        app_list::GenerateItemMetadataFromSyncItem(*sync_item));
    remote_folder->SetIsSystemFolder(true);
    remote_folder->SetIsEphemeral(true);
    app_list_syncable_service_->AddItem(std::move(remote_folder));
    return;
  }

  // Handle the case that the specified folder's sync data does not exist.
  DCHECK(model_->HasFolder(folder_id));
  const RemoteAppsModel::FolderInfo& info = model_->GetFolderInfo(folder_id);
  remote_folder->SetChromeName(info.name);
  remote_folder->SetIsSystemFolder(true);
  remote_folder->SetIsEphemeral(true);
  remote_folder->SetChromeIsFolder(true);
  syncer::StringOrdinal position =
      info.add_to_front ? model_updater_->GetPositionBeforeFirstItem()
                        : remote_folder->CalculateDefaultPositionIfApplicable();
  remote_folder->SetChromePosition(position);

  app_list_syncable_service_->AddItem(std::move(remote_folder));
}

const RemoteAppsModel::AppInfo* RemoteAppsManager::GetAppInfo(
    const std::string& app_id) const {
  if (!model_->HasApp(app_id))
    return nullptr;

  return &model_->GetAppInfo(app_id);
}

RemoteAppsError RemoteAppsManager::DeleteApp(const std::string& id) {
  // Check if app was added but |HandleOnAppAdded| has not been called.
  if (!model_->HasApp(id) ||
      add_app_callback_map_.find(id) != add_app_callback_map_.end())
    return RemoteAppsError::kAppIdDoesNotExist;

  model_->DeleteApp(id);
  remote_apps_->DeleteApp(id);
  app_id_to_source_id_map_.erase(id);
  return RemoteAppsError::kNone;
}

void RemoteAppsManager::SortLauncherWithRemoteAppsFirst() {
  static_cast<ChromeAppListModelUpdater*>(model_updater_)
      ->RequestAppListSort(AppListSortOrder::kAlphabeticalEphemeralAppFirst);
}

RemoteAppsError RemoteAppsManager::SetPinnedApps(
    const std::vector<std::string>& app_ids) {
  if (app_ids.size() > 1) {
    return RemoteAppsError::kPinningMultipleAppsNotSupported;
  }

  // Providing an empty app id will reset the pinned app.
  std::string app_id = app_ids.empty() ? "" : app_ids[0];
  bool success =
      Shell::Get()->app_list_controller()->SetHomeButtonQuickApp(app_id);
  return success ? RemoteAppsError::kNone : RemoteAppsError::kFailedToPinAnApp;
}

std::string RemoteAppsManager::AddFolder(const std::string& folder_name,
                                         bool add_to_front) {
  const RemoteAppsModel::FolderInfo& folder_info =
      model_->AddFolder(folder_name, add_to_front);
  return folder_info.id;
}

RemoteAppsError RemoteAppsManager::DeleteFolder(const std::string& folder_id) {
  if (!model_->HasFolder(folder_id))
    return RemoteAppsError::kFolderIdDoesNotExist;

  // Move all items out of the folder. Empty folders are automatically deleted.
  RemoteAppsModel::FolderInfo& folder_info = model_->GetFolderInfo(folder_id);
  for (const auto& app : folder_info.items)
    model_updater_->SetItemFolderId(app, std::string());
  model_->DeleteFolder(folder_id);
  return RemoteAppsError::kNone;
}

bool RemoteAppsManager::ShouldAddToFront(const std::string& id) const {
  if (model_->HasApp(id))
    return model_->GetAppInfo(id).add_to_front;

  if (model_->HasFolder(id))
    return model_->GetFolderInfo(id).add_to_front;

  return false;
}

void RemoteAppsManager::BindFactoryInterface(
    mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteAppsFactory>
        pending_remote_apps_factory) {
  factory_receivers_.Add(this, std::move(pending_remote_apps_factory));
}

void RemoteAppsManager::BindLacrosBridgeInterface(
    mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteAppsLacrosBridge>
        pending_remote_apps_lacros_bridge) {
  bridge_receivers_.Add(this, std::move(pending_remote_apps_lacros_bridge));
}

void RemoteAppsManager::Shutdown() {}

void RemoteAppsManager::BindRemoteAppsAndAppLaunchObserver(
    const std::string& source_id,
    mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteApps>
        pending_remote_apps,
    mojo::PendingRemote<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
        pending_observer) {
  remote_apps_impl_.BindRemoteAppsAndAppLaunchObserver(
      source_id, std::move(pending_remote_apps), std::move(pending_observer));
}

void RemoteAppsManager::BindRemoteAppsAndAppLaunchObserverForLacros(
    mojo::PendingReceiver<chromeos::remote_apps::mojom::RemoteApps>
        pending_remote_apps,
    mojo::PendingRemote<chromeos::remote_apps::mojom::RemoteAppLaunchObserver>
        pending_observer) {
  remote_apps_impl_.BindRemoteAppsAndAppLaunchObserver(
      std::nullopt, std::move(pending_remote_apps),
      std::move(pending_observer));
}

const std::map<std::string, RemoteAppsModel::AppInfo>&
RemoteAppsManager::GetApps() {
  return model_->GetAllAppInfo();
}

void RemoteAppsManager::LaunchApp(const std::string& app_id) {
  auto it = app_id_to_source_id_map_.find(app_id);
  if (it == app_id_to_source_id_map_.end())
    return;
  std::string source_id = it->second;

  std::unique_ptr<extensions::Event> event = std::make_unique<
      extensions::Event>(
      extensions::events::ENTERPRISE_REMOTE_APPS_ON_REMOTE_APP_LAUNCHED,
      chrome_apps::api::enterprise_remote_apps::OnRemoteAppLaunched::kEventName,
      chrome_apps::api::enterprise_remote_apps::OnRemoteAppLaunched::Create(
          app_id));

  event_router_->DispatchEventToExtension(source_id, std::move(event));

  remote_apps_impl_.OnAppLaunched(source_id, app_id);
}

gfx::ImageSkia RemoteAppsManager::GetIcon(const std::string& id) {
  if (!model_->HasApp(id))
    return gfx::ImageSkia();

  return model_->GetAppInfo(id).icon;
}

gfx::ImageSkia RemoteAppsManager::GetPlaceholderIcon(const std::string& id,
                                                     int32_t size_hint_in_dip) {
  if (!model_->HasApp(id))
    return gfx::ImageSkia();

  gfx::ImageSkia icon(std::make_unique<RemoteAppsPlaceholderIcon>(
                          model_->GetAppInfo(id).name, size_hint_in_dip),
                      gfx::Size(size_hint_in_dip, size_hint_in_dip));
  icon.EnsureRepsForSupportedScales();
  return icon;
}

apps::MenuItems RemoteAppsManager::GetMenuModel(const std::string& id) {
  apps::MenuItems menu_items;
  // TODO(b/236785623): Temporary string for menu item.
  apps::AddCommandItem(ash::LAUNCH_NEW, IDS_APP_CONTEXT_MENU_ACTIVATE_ARC,
                       menu_items);
  return menu_items;
}

void RemoteAppsManager::OnSyncModelUpdated() {
  DCHECK(!is_initialized_);
  Initialize();
  app_list_syncable_service_observation_.Reset();
}

void RemoteAppsManager::OnAppListItemAdded(ChromeAppListItem* item) {
  if (item->is_folder())
    return;

  // Make a copy of id as item->metadata can be invalidated.
  HandleOnAppAdded(std::string(item->id()));
}

void RemoteAppsManager::SetImageDownloaderForTesting(
    std::unique_ptr<ImageDownloader> image_downloader) {
  image_downloader_ = std::move(image_downloader);
}

RemoteAppsModel* RemoteAppsManager::GetModelForTesting() {
  return model_.get();
}

void RemoteAppsManager::SetIsInitializedForTesting(bool is_initialized) {
  is_initialized_ = is_initialized;
}

void RemoteAppsManager::HandleOnAppAdded(const std::string& id) {
  if (!model_->HasApp(id))
    return;
  RemoteAppsModel::AppInfo& app_info = model_->GetAppInfo(id);
  StartIconDownload(id, app_info.icon_url);

  auto it = add_app_callback_map_.find(id);
  DCHECK(it != add_app_callback_map_.end())
      << "Missing callback for id: " << id;
  std::move(it->second).Run(id, RemoteAppsError::kNone);
  add_app_callback_map_.erase(it);
}

void RemoteAppsManager::StartIconDownload(const std::string& id,
                                          const GURL& icon_url) {
  image_downloader_->Download(
      icon_url, base::BindOnce(&RemoteAppsManager::OnIconDownloaded,
                               weak_factory_.GetWeakPtr(), id));
}

void RemoteAppsManager::OnIconDownloaded(const std::string& id,
                                         const gfx::ImageSkia& icon) {
  // App may have been deleted.
  if (!model_->HasApp(id))
    return;

  RemoteAppsModel::AppInfo& app_info = model_->GetAppInfo(id);
  app_info.icon = icon;
  remote_apps_->UpdateAppIcon(id);
}

}  // namespace ash