chromium/chrome/browser/ash/extensions/gfx_utils.cc

// Copyright 2016 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/extensions/gfx_utils.h"

#include <vector>

#include "base/containers/flat_map.h"
#include "base/lazy_instance.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/chromeos_app_icon_resources.h"
#include "components/prefs/pref_service.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"

namespace extensions {

namespace {

// The badge map between |arc_package_name| and |extension_id|. Note the mapping
// from |extension_id| to |arc_package_name| is unique, but the mapping from
// |arc_package_name| to |extension_id| is not.
const struct {
  const char* arc_package_name;
  const char* extension_id;
} kDualBadgeMap[] = {
    // Google Keep
    {"com.google.android.keep", "hmjkmjkepdijhoojdojkdfohbdgmmhki"},
    {"com.google.android.keep", "dondgdlndnpianbklfnehgdhkickdjck"},
    // GMail
    {"com.google.android.gm", extension_misc::kGmailAppId},
    {"com.google.android.gm", "bjdhhokmhgelphffoafoejjmlfblpdha"},
    // Google Drive
    {"com.google.android.apps.docs", extension_misc::kGoogleDriveAppId},
    {"com.google.android.apps.docs", "mdhnphfgagkpdhndljccoackjjhghlif"},
    // Google Maps
    {"com.google.android.apps.maps", "lneaknkopdijkpnocmklfnjbeapigfbh"},
    // Calculator
    {"com.google.android.calculator", "joodangkbfjnajiiifokapkpmhfnpleo"},
    // Google Calender
    {"com.google.android.calendar", "ejjicmeblgpmajnghnpcppodonldlgfn"},
    {"com.google.android.calendar", "fpgfohogebplgnamlafljlcidjedbdeb"},
    // Google Docs
    {"com.google.android.apps.docs.editors.docs",
     extension_misc::kGoogleDocsAppId},
    {"com.google.android.apps.docs.editors.docs",
     "npnjdccdffhdndcbeappiamcehbhjibf"},
    // Google Slides
    {"com.google.android.apps.docs.editors.slides",
     extension_misc::kGoogleSlidesAppId},
    {"com.google.android.apps.docs.editors.slides",
     "hdmobeajeoanbanmdlabnbnlopepchip"},
    // Google Sheets
    {"com.google.android.apps.docs.editors.sheets",
     extension_misc::kGoogleSheetsAppId},
    {"com.google.android.apps.docs.editors.sheets",
     "nifkmgcdokhkjghdlgflonppnefddien"},
    // YouTube
    {"com.google.android.youtube", extension_misc::kYoutubeAppId},
    {"com.google.android.youtube", "pbdihpaifchmclcmkfdgffnnpfbobefh"},
    // Google Play Books
    {"com.google.android.apps.books", extension_misc::kGooglePlayBooksAppId},
    // Google+
    {"com.google.android.apps.plus", "dlppkpafhbajpcmmoheippocdidnckmm"},
    {"com.google.android.apps.plus", "fgjnkhlabjcaajddbaenilcmpcidahll"},
    // Google Play Movies & TV
    {"com.google.android.videos", extension_misc::kGooglePlayMoviesAppId},
    {"com.google.android.videos", "amfoiggnkefambnaaphodjdmdooiinna"},
    // Google Play Music
    {"com.google.android.music", extension_misc::kGooglePlayMusicAppId},
    {"com.google.android.music", "onbhgdmifjebcabplolilidlpgeknifi"},
    // Google Now
    {"com.google.android.launcher", "mnfadmojomeniojkkikjpgjaegolkbpb"},
    // Google Photos
    {"com.google.android.apps.photos", "hcglmfcclpfgljeaiahehebeoaiicbko"},
    {"com.google.android.apps.photos", "efjnaogkjbogokcnohkmnjdojkikgobo"},
    {"com.google.android.apps.photos", "ifpkhncdnjfipfjlhfidljjffdgklanh"},
    // Google Classroom
    {"com.google.android.apps.classroom", "mfhehppjhmmnlfbbopchdfldgimhfhfk"},
    // Google Hangouts
    {"com.google.android.talk", "knipolnnllmklapflnccelgolnpehhpl"},
    {"com.google.android.talk", "cgmlfbhkckbedohgdepgbkflommbfkep"},
    {"com.google.android.talk", "gldgpnmcpaogjlojhhpebkbbanacoglc"},
    // Google Play Music
    {"com.google.android.music", "fahmaaghhglfmonjliepjlchgpgfmobi"},
    // Google News
    {"com.google.android.apps.genie.geniewidget",
     "dllkocilcinkggkchnjgegijklcililc"},
    // Used in unit tests.
    {"fake.package.name1", "emfkafnhnpcmabnnkckkchdilgeoekbo"},
};

// This class maintains the maps between the extension id and its equivalent
// ARC package name.
class AppDualBadgeMap {
 public:
  using ArcAppToExtensionsMap =
      base::flat_map<std::string, std::vector<std::string>>;
  using ExtensionToArcAppMap = base::flat_map<std::string, std::string>;

  AppDualBadgeMap() {
    for (auto dual_badge : kDualBadgeMap) {
      arc_app_to_extensions_map_[dual_badge.arc_package_name].push_back(
          dual_badge.extension_id);
      extension_to_arc_app_map_[dual_badge.extension_id] =
          dual_badge.arc_package_name;
    }
  }

  AppDualBadgeMap(const AppDualBadgeMap&) = delete;
  AppDualBadgeMap& operator=(const AppDualBadgeMap&) = delete;

  std::vector<std::string> GetExtensionIdsForArcPackageName(
      std::string arc_package_name) {
    const auto iter = arc_app_to_extensions_map_.find(arc_package_name);
    if (iter == arc_app_to_extensions_map_.end())
      return std::vector<std::string>();
    return iter->second;
  }

  std::string GetArcPackageNameFromExtensionId(std::string extension_id) {
    const auto iter = extension_to_arc_app_map_.find(extension_id);
    if (iter == extension_to_arc_app_map_.end())
      return std::string();
    return iter->second;
  }

 private:
  ArcAppToExtensionsMap arc_app_to_extensions_map_;
  ExtensionToArcAppMap extension_to_arc_app_map_;
};

base::LazyInstance<AppDualBadgeMap>::DestructorAtExit g_dual_badge_map =
    LAZY_INSTANCE_INITIALIZER;

}  // namespace

namespace util {

bool HasEquivalentInstalledArcApp(content::BrowserContext* context,
                                  const std::string& extension_id) {
  std::unordered_set<std::string> arc_apps;
  return GetEquivalentInstalledArcApps(context, extension_id, &arc_apps);
}

bool GetEquivalentInstalledArcApps(content::BrowserContext* context,
                                   const std::string& extension_id,
                                   std::unordered_set<std::string>* arc_apps) {
  const std::string arc_package_name =
      g_dual_badge_map.Get().GetArcPackageNameFromExtensionId(extension_id);
  if (arc_package_name.empty())
    return false;

  const ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(context);
  if (!prefs)
    return false;

  // TODO(hidehiko): The icon is per launcher, so we should have more precise
  // check here.
  DCHECK(arc_apps);
  prefs->GetAppsForPackage(arc_package_name).swap(*arc_apps);
  return !arc_apps->empty();
}

const std::vector<std::string> GetEquivalentInstalledAppIds(
    const std::string& arc_package_name) {
  return g_dual_badge_map.Get().GetExtensionIdsForArcPackageName(
      arc_package_name);
}

const std::vector<std::string> GetEquivalentInstalledExtensions(
    content::BrowserContext* context,
    const std::string& arc_package_name) {
  const ExtensionRegistry* registry = ExtensionRegistry::Get(context);
  if (!registry)
    return std::vector<std::string>();

  std::vector<std::string> extension_ids =
      g_dual_badge_map.Get().GetExtensionIdsForArcPackageName(arc_package_name);
  if (extension_ids.empty())
    return std::vector<std::string>();

  std::erase_if(extension_ids, [registry](std::string extension_id) {
    return !registry->GetInstalledExtension(extension_id);
  });
  return extension_ids;
}

bool ShouldApplyChromeBadge(content::BrowserContext* context,
                            const std::string& extension_id) {
  DCHECK(context);

  Profile* profile = Profile::FromBrowserContext(context);
  // Only apply Chrome badge for the primary profile.
  if (!ash::ProfileHelper::IsPrimaryProfile(profile) ||
      !multi_user_util::IsProfileFromActiveUser(profile)) {
    return false;
  }

  const ExtensionRegistry* registry = ExtensionRegistry::Get(context);
  if (!registry || !registry->GetInstalledExtension(extension_id))
    return false;

  if (!HasEquivalentInstalledArcApp(context, extension_id))
    return false;

  return true;
}

bool ShouldApplyChromeBadgeToWebApp(content::BrowserContext* context,
                                    const std::string& web_app_id) {
  DCHECK(context);

  Profile* profile = Profile::FromBrowserContext(context);
  // Only apply Chrome badge for the primary profile.
  if (!ash::ProfileHelper::IsPrimaryProfile(profile) ||
      !multi_user_util::IsProfileFromActiveUser(profile)) {
    return false;
  }

  if (!HasEquivalentInstalledArcApp(context, web_app_id))
    return false;

  return true;
}

void ApplyBadge(gfx::ImageSkia* icon_out, ChromeAppIcon::Badge badge_type) {
  DCHECK(icon_out);
  DCHECK_NE(ChromeAppIcon::Badge::kNone, badge_type);

  int badge_res = 0;
  switch (badge_type) {
    case ChromeAppIcon::Badge::kChrome:
      badge_res = IDR_CHROME_ICON_BADGE;
      break;
    case ChromeAppIcon::Badge::kBlocked:
      badge_res = IDR_BLOCK_ICON_BADGE;
      break;
    case ChromeAppIcon::Badge::kPaused:
      badge_res = IDR_HOURGLASS_ICON_BADGE;
      break;
    default:
      NOTREACHED_IN_MIGRATION();
  }

  const gfx::ImageSkia* badge_image =
      ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(badge_res);
  DCHECK(badge_image);

  gfx::ImageSkia resized_badge_image = *badge_image;
  if (badge_image->size() != icon_out->size()) {
    resized_badge_image = gfx::ImageSkiaOperations::CreateResizedImage(
        *badge_image, skia::ImageOperations::RESIZE_BEST, icon_out->size());
  }
  *icon_out = gfx::ImageSkiaOperations::CreateSuperimposedImage(
      *icon_out, resized_badge_image);
}

}  // namespace util
}  // namespace extensions