personalization: Handle dedup key for Google Photos wallpapers.

Problem: Google Photos photos are uniquely identified by id but the same
photo in multiple albums will have a unique id per album. This makes it
impossible for us to determine whether a given photo from one album
refers to the same photo in another.

Solution: Google Photos photos are mapped to a dedup key that is the
same for a given photo across multiple albums. This makes it possible to
determine whether a given photo from one album refers to the same photo
in another.

Note: Backwards compatibility is important to consider due to cross
device sync. In this CL, a new attribute is synced for dedup key. Old
clients will not deserialize the new attribute and will therefore
continue to fall back to matching by id. If an old client sets a Google
Photos wallpaper, the new client will not deserialize a dedup key and
so will fall back to matching by id.

Before: http://shortn/_Q04RVVnAcK
After: http://shortn/_cR7BpNWHEy

Bug: b:232990402
Change-Id: I19db4415bd4b12f2bede5662a2e6f624264998fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3653296
Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org>
Reviewed-by: Sam McNally <sammc@chromium.org>
Commit-Queue: David Black <dmblack@google.com>
Cr-Commit-Position: refs/heads/main@{#1005120}
diff --git a/ash/public/cpp/wallpaper/google_photos_wallpaper_params.cc b/ash/public/cpp/wallpaper/google_photos_wallpaper_params.cc
index a9e661a..1dfe584 100644
--- a/ash/public/cpp/wallpaper/google_photos_wallpaper_params.cc
+++ b/ash/public/cpp/wallpaper/google_photos_wallpaper_params.cc
@@ -4,6 +4,8 @@
 
 #include "ash/public/cpp/wallpaper/google_photos_wallpaper_params.h"
 
+#include "base/check.h"
+
 namespace ash {
 
 GooglePhotosWallpaperParams::GooglePhotosWallpaperParams(
@@ -11,12 +13,19 @@
     const std::string& id,
     bool daily_refresh_enabled,
     WallpaperLayout layout,
-    bool preview_mode)
+    bool preview_mode,
+    absl::optional<std::string> dedup_key)
     : account_id(account_id),
       id(id),
       daily_refresh_enabled(daily_refresh_enabled),
       layout(layout),
-      preview_mode(preview_mode) {}
+      preview_mode(preview_mode),
+      dedup_key(std::move(dedup_key)) {
+  // The `dedup_key` is only used to differentiate between photos (not albums)
+  // and so is not applicable when daily refresh is enabled.
+  if (daily_refresh_enabled)
+    DCHECK(!this->dedup_key.has_value());
+}
 
 GooglePhotosWallpaperParams::GooglePhotosWallpaperParams(
     const GooglePhotosWallpaperParams& other) = default;
@@ -30,10 +39,11 @@
                          const GooglePhotosWallpaperParams& params) {
   os << "GooglePhotosWallPaperParams:" << std::endl;
   os << "  Account Id: " << params.account_id << std::endl;
-  os << "  Photo Id: " << params.id << std::endl;
+  os << "  Id: " << params.id << std::endl;
   os << "  Daily Refresh: " << params.daily_refresh_enabled << std::endl;
   os << "  Layout: " << params.layout << std::endl;
   os << "  Preview Mode: " << params.preview_mode << std::endl;
+  os << "  Dedup Key: " << params.dedup_key.value_or("") << std::endl;
   return os;
 }
 
diff --git a/ash/public/cpp/wallpaper/google_photos_wallpaper_params.h b/ash/public/cpp/wallpaper/google_photos_wallpaper_params.h
index 02a5c9c58..bc21be63 100644
--- a/ash/public/cpp/wallpaper/google_photos_wallpaper_params.h
+++ b/ash/public/cpp/wallpaper/google_photos_wallpaper_params.h
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/ash_public_export.h"
 #include "ash/public/cpp/wallpaper/wallpaper_types.h"
 #include "components/account_id/account_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
@@ -18,7 +19,8 @@
                               const std::string& id,
                               bool daily_refresh_enabled,
                               WallpaperLayout layout,
-                              bool preview_mode);
+                              bool preview_mode,
+                              absl::optional<std::string> dedup_key);
 
   GooglePhotosWallpaperParams(const GooglePhotosWallpaperParams& other);
 
@@ -42,6 +44,12 @@
   // If true, show the wallpaper immediately, but don't change the user
   // wallpaper info until `ConfirmPreviewWallpaper()` is called.
   bool preview_mode;
+
+  // A string which uniquely identifies a Google Photos photo across albums.
+  // Note that the same photo appearing in multiple albums will have a unique
+  // `id` for each album in which it appears, but the `dedup_key` is shared
+  // across albums.
+  absl::optional<std::string> dedup_key;
 };
 
 ASH_PUBLIC_EXPORT std::ostream& operator<<(
diff --git a/ash/public/cpp/wallpaper/wallpaper_info.cc b/ash/public/cpp/wallpaper/wallpaper_info.cc
index 38f0b61..8dc0ac025 100644
--- a/ash/public/cpp/wallpaper/wallpaper_info.cc
+++ b/ash/public/cpp/wallpaper/wallpaper_info.cc
@@ -39,6 +39,7 @@
   } else {
     type = WallpaperType::kOnceGooglePhotos;
     location = google_photos_wallpaper_params.id;
+    dedup_key = google_photos_wallpaper_params.dedup_key;
   }
 }
 
diff --git a/ash/public/cpp/wallpaper/wallpaper_info.h b/ash/public/cpp/wallpaper/wallpaper_info.h
index e5066c9..650cd91 100644
--- a/ash/public/cpp/wallpaper/wallpaper_info.h
+++ b/ash/public/cpp/wallpaper/wallpaper_info.h
@@ -50,6 +50,10 @@
   WallpaperType type;
   base::Time date;
 
+  // These fields are applicable if |type| == WallpaperType::kOnceGooglePhotos
+  // or WallpaperType::kDailyGooglePhotos.
+  absl::optional<std::string> dedup_key;
+
   // These fields are applicable if |type| == WallpaperType::kOnline or
   // WallpaperType::kDaily.
   absl::optional<uint64_t> asset_id;
diff --git a/ash/wallpaper/test_wallpaper_controller_client.cc b/ash/wallpaper/test_wallpaper_controller_client.cc
index ad2ad04e..1bfc3fa8 100644
--- a/ash/wallpaper/test_wallpaper_controller_client.cc
+++ b/ash/wallpaper/test_wallpaper_controller_client.cc
@@ -114,14 +114,15 @@
     FetchGooglePhotosPhotoCallback callback) {
   base::Time time;
   base::Time::Exploded exploded_time{2011, 6, 3, 15, 12, 0, 0, 0};
-  DCHECK(base::Time::FromUTCExploded(exploded_time, &time));
+  if (!base::Time::FromUTCExploded(exploded_time, &time))
+    NOTREACHED();
   if (fetch_google_photos_photo_fails_ || google_photo_has_been_deleted_) {
     std::move(callback).Run(nullptr,
                             /*success=*/google_photo_has_been_deleted_);
   } else {
     std::move(callback).Run(
         personalization_app::mojom::GooglePhotosPhoto::New(
-            id, "test_name", base::TimeFormatFriendlyDate(time),
+            id, "dedup_key", "test_name", base::TimeFormatFriendlyDate(time),
             GURL("https://google.com/picture.png"), "home"),
         /*success=*/true);
   }
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 4e852a9..5575574 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -588,12 +588,15 @@
       WallpaperControllerImpl::kNewWallpaperAssetIdNodeName);
   const std::string* collection_id = info_dict.FindStringPath(
       WallpaperControllerImpl::kNewWallpaperCollectionIdNodeName);
+  const std::string* dedup_key = info_dict.FindStringPath(
+      WallpaperControllerImpl::kNewWallpaperDedupKeyNodeName);
   const std::string* unit_id_str = info_dict.FindStringPath(
       WallpaperControllerImpl::kNewWallpaperUnitIdNodeName);
   const base::Value* variant_list = info_dict.FindListPath(
       WallpaperControllerImpl::kNewWallpaperVariantListNodeName);
 
   info->collection_id = collection_id ? *collection_id : std::string();
+  info->dedup_key = dedup_key ? absl::make_optional(*dedup_key) : absl::nullopt;
 
   if (asset_id_str) {
     uint64_t asset_id;
@@ -717,6 +720,11 @@
   wallpaper_info_dict.SetStringPath(
       WallpaperControllerImpl::kNewWallpaperDateNodeName,
       base::NumberToString(info.date.ToInternalValue()));
+  if (info.dedup_key) {
+    wallpaper_info_dict.SetStringPath(
+        WallpaperControllerImpl::kNewWallpaperDedupKeyNodeName,
+        info.dedup_key.value());
+  }
   wallpaper_info_dict.SetStringPath(
       WallpaperControllerImpl::kNewWallpaperLocationNodeName, info.location);
   wallpaper_info_dict.SetIntPath(
@@ -803,6 +811,8 @@
 const char WallpaperControllerImpl::kNewWallpaperCollectionIdNodeName[] =
     "collection_id";
 const char WallpaperControllerImpl::kNewWallpaperDateNodeName[] = "date";
+const char WallpaperControllerImpl::kNewWallpaperDedupKeyNodeName[] =
+    "dedup_key";
 const char WallpaperControllerImpl::kNewWallpaperLayoutNodeName[] = "layout";
 const char WallpaperControllerImpl::kNewWallpaperLocationNodeName[] = "file";
 const char WallpaperControllerImpl::kNewWallpaperTypeNodeName[] = "type";
@@ -2367,7 +2377,7 @@
 }
 
 void WallpaperControllerImpl::OnGooglePhotosPhotoFetched(
-    const GooglePhotosWallpaperParams& params,
+    GooglePhotosWallpaperParams params,
     SetWallpaperCallback callback,
     ash::personalization_app::mojom::GooglePhotosPhotoPtr photo,
     bool success) {
@@ -2396,6 +2406,8 @@
     return;
   }
 
+  params.dedup_key = photo->dedup_key;
+
   auto cached_path =
       GetUserGooglePhotosWallpaperDir(params.account_id).Append(params.id);
   sequenced_task_runner_->PostTaskAndReplyWithResult(
@@ -2436,7 +2448,7 @@
   ImageDownloader::DownloadCallback download_callback = base::BindOnce(
       &WallpaperControllerImpl::OnDailyGooglePhotosWallpaperDownloaded,
       set_wallpaper_weak_factory_.GetWeakPtr(), account_id, photo->id, album_id,
-      std::move(callback));
+      photo->dedup_key, std::move(callback));
   wallpaper_controller_client_->FetchGooglePhotosAccessToken(
       account_id, base::BindOnce(&DownloadGooglePhotosImage, photo->url,
                                  account_id, std::move(download_callback)));
@@ -2446,6 +2458,7 @@
     const AccountId& account_id,
     const std::string& photo_id,
     const std::string& album_id,
+    absl::optional<std::string> dedup_key,
     RefreshWallpaperCallback callback,
     const gfx::ImageSkia& image) {
   DCHECK(callback);
@@ -2461,8 +2474,9 @@
   WallpaperInfo wallpaper_info(
       {account_id, album_id, /*daily_refresh_enabled=*/true,
        ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-       /*preview_mode=*/false});
+       /*preview_mode=*/false, /*dedup_key=*/absl::nullopt});
   wallpaper_info.location = photo_id;
+  wallpaper_info.dedup_key = dedup_key;
 
   if (!SetUserWallpaperInfo(account_id, wallpaper_info)) {
     LOG(ERROR) << "Setting user wallpaper info fails. This should never happen "
@@ -3498,17 +3512,18 @@
     if (GetUserWallpaperInfo(account_id, &old_info) &&
         old_info.collection_id != info.collection_id) {
       SetGooglePhotosWallpaper(
-          GooglePhotosWallpaperParams(account_id, info.collection_id,
-                                      daily_refresh_enabled, info.layout,
-                                      /*preview_mode=*/false),
+          GooglePhotosWallpaperParams(
+              account_id, info.collection_id,
+              /*daily_refresh_enabled=*/true, info.layout,
+              /*preview_mode=*/false, /*dedup_key=*/absl::nullopt),
           base::DoNothing());
     }
   } else {
-    SetGooglePhotosWallpaper(
-        GooglePhotosWallpaperParams(account_id, info.location,
-                                    daily_refresh_enabled, info.layout,
-                                    /*preview_mode=*/false),
-        base::DoNothing());
+    SetGooglePhotosWallpaper(GooglePhotosWallpaperParams(
+                                 account_id, info.location,
+                                 /*daily_refresh_enabled=*/false, info.layout,
+                                 /*preview_mode=*/false, info.dedup_key),
+                             base::DoNothing());
   }
 }
 
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h
index 52cbe71..d6a2b6e 100644
--- a/ash/wallpaper/wallpaper_controller_impl.h
+++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -102,6 +102,7 @@
   static const char kNewWallpaperAssetIdNodeName[];
   static const char kNewWallpaperCollectionIdNodeName[];
   static const char kNewWallpaperDateNodeName[];
+  static const char kNewWallpaperDedupKeyNodeName[];
   static const char kNewWallpaperLayoutNodeName[];
   static const char kNewWallpaperLocationNodeName[];
   static const char kNewWallpaperTypeNodeName[];
@@ -478,7 +479,7 @@
   // Used as the callback of fetching the data for a Google Photos photo from
   // the unique id.
   void OnGooglePhotosPhotoFetched(
-      const GooglePhotosWallpaperParams& params,
+      GooglePhotosWallpaperParams params,
       SetWallpaperCallback callback,
       ash::personalization_app::mojom::GooglePhotosPhotoPtr photo,
       bool success);
@@ -490,11 +491,13 @@
       ash::personalization_app::mojom::GooglePhotosPhotoPtr photo,
       bool success);
 
-  void OnDailyGooglePhotosWallpaperDownloaded(const AccountId& account_id,
-                                              const std::string& photo_id,
-                                              const std::string& album_id,
-                                              RefreshWallpaperCallback callback,
-                                              const gfx::ImageSkia& image);
+  void OnDailyGooglePhotosWallpaperDownloaded(
+      const AccountId& account_id,
+      const std::string& photo_id,
+      const std::string& album_id,
+      absl::optional<std::string> dedup_key,
+      RefreshWallpaperCallback callback,
+      const gfx::ImageSkia& image);
 
   void GetGooglePhotosWallpaperFromCacheOrDownload(
       const GooglePhotosWallpaperParams& params,
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index 898ce34..8be5204 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -270,6 +270,11 @@
         WallpaperControllerImpl::kNewWallpaperAssetIdNodeName,
         base::NumberToString(info.asset_id.value()));
   }
+  if (info.dedup_key.has_value()) {
+    wallpaper_info_dict.SetStringKey(
+        WallpaperControllerImpl::kNewWallpaperDedupKeyNodeName,
+        info.dedup_key.value());
+  }
   if (info.unit_id.has_value()) {
     wallpaper_info_dict.SetStringKey(
         WallpaperControllerImpl::kNewWallpaperUnitIdNodeName,
@@ -348,6 +353,8 @@
     info.collection_id = "placeholder collection";
     info.location = "https://example.com/example.jpeg";
   }
+  if (type == WallpaperType::kOnceGooglePhotos)
+    info.dedup_key = "dedup_key";
   return info;
 }
 
@@ -2227,7 +2234,7 @@
     controller_->SetGooglePhotosWallpaper(
         GooglePhotosWallpaperParams(account_id_1, "id",
                                     /*daily_refresh_enabled=*/false, layout,
-                                    /*preview_mode=*/false),
+                                    /*preview_mode=*/false, "dedup_key"),
         base::DoNothing());
     RunAllTasksUntilIdle();
     EXPECT_EQ(1, GetWallpaperCount());
@@ -2239,7 +2246,7 @@
     EXPECT_EQ(wallpaper_info,
               WallpaperInfo(GooglePhotosWallpaperParams(
                   account_id_1, "id", /*daily_refresh_enabled=*/false, layout,
-                  /*preview_mode=*/false)));
+                  /*preview_mode=*/false, "dedup_key")));
 
     // Now change to a different layout. Verify that the layout is updated for
     // both the current wallpaper and the saved wallpaper info.
@@ -2253,7 +2260,7 @@
     EXPECT_EQ(wallpaper_info,
               WallpaperInfo(GooglePhotosWallpaperParams(
                   account_id_1, "id", /*daily_refresh_enabled=*/false,
-                  new_layout, /*preview_mode=*/false)));
+                  new_layout, /*preview_mode=*/false, "dedup_key")));
   }
 
   // Now set an online wallpaper. Verify that it's set successfully and the
@@ -3400,6 +3407,17 @@
                              account_id_1, info);
 }
 
+TEST_P(WallpaperControllerTest, SetWallpaperInfoLocalFromGooglePhotos) {
+  WallpaperInfo info(
+      GooglePhotosWallpaperParams{account_id_1, kFakeGooglePhotosPhotoId,
+                                  /*daily_refresh_enabled=*/false,
+                                  WallpaperLayout::WALLPAPER_LAYOUT_STRETCH,
+                                  /*preview_mode=*/false, "dedup_key"});
+  EXPECT_TRUE(controller_->SetUserWallpaperInfo(account_id_1, info));
+  AssertWallpaperInfoInPrefs(GetLocalPrefService(), prefs::kUserWallpaperInfo,
+                             account_id_1, info);
+}
+
 // Test cases that only run with |kWallpaperWebUI| turned on.
 class WallpaperControllerWallpaperWebUiTest
     : public WallpaperControllerTestBase {
@@ -3487,6 +3505,17 @@
                              prefs::kSyncableWallpaperInfo, account_id_1, info);
 }
 
+TEST_F(WallpaperControllerWallpaperWebUiTest,
+       SetWallpaperInfoSyncedFromGooglePhotos) {
+  base::test::ScopedFeatureList scoped_features;
+  scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
+
+  WallpaperInfo info = InfoWithType(WallpaperType::kOnceGooglePhotos);
+  EXPECT_TRUE(controller_->SetUserWallpaperInfo(account_id_1, info));
+  AssertWallpaperInfoInPrefs(GetProfilePrefService(account_id_1),
+                             prefs::kSyncableWallpaperInfo, account_id_1, info);
+}
+
 TEST_F(WallpaperControllerWallpaperWebUiTest, SetWallpaperInfoSyncDisabled) {
   base::test::ScopedFeatureList scoped_features;
   scoped_features.InitAndEnableFeature(features::kWallpaperWebUI);
@@ -4437,7 +4466,7 @@
   GooglePhotosWallpaperParams params(account_id_1, kFakeGooglePhotosPhotoId,
                                      /*daily_refresh_enabled=*/false,
                                      WallpaperLayout::WALLPAPER_LAYOUT_STRETCH,
-                                     /*preview_mode=*/false);
+                                     /*preview_mode=*/false, "dedup_key");
 
   controller_->SetGooglePhotosWallpaper(params, base::DoNothing());
   if (feature_enabled)
@@ -4485,7 +4514,7 @@
   base::test::TestFuture<bool> google_photos_future;
   controller_->SetGooglePhotosWallpaper(
       {account_id_1, kFakeGooglePhotosPhotoId, false,
-       WallpaperLayout::WALLPAPER_LAYOUT_STRETCH, false},
+       WallpaperLayout::WALLPAPER_LAYOUT_STRETCH, false, "dedup_key"},
       google_photos_future.GetCallback());
   EXPECT_FALSE(google_photos_future.Get());
   EXPECT_NE(controller_->GetWallpaperType(), WallpaperType::kOnceGooglePhotos);
@@ -4565,7 +4594,7 @@
   controller_->SetGooglePhotosWallpaper(
       {account_id_1, kFakeGooglePhotosPhotoId, /*daily_refresh_enabled=*/false,
        WALLPAPER_LAYOUT_STRETCH,
-       /*preview_mode=*/false},
+       /*preview_mode=*/false, "dedup_key"},
       google_photos_future.GetCallback());
   EXPECT_EQ(GooglePhotosEnabled(), google_photos_future.Get());
   RunAllTasksUntilIdle();
@@ -4591,7 +4620,7 @@
   controller_->SetGooglePhotosWallpaper(
       {account_id_1, kFakeGooglePhotosPhotoId, /*daily_refresh_enabled=*/false,
        WALLPAPER_LAYOUT_STRETCH,
-       /*preview_mode=*/false},
+       /*preview_mode=*/false, "dedup_key"},
       google_photos_future.GetCallback());
   EXPECT_EQ(GooglePhotosEnabled(), google_photos_future.Get());
   RunAllTasksUntilIdle();
@@ -4615,7 +4644,7 @@
   GooglePhotosWallpaperParams params({account_id_1, kFakeGooglePhotosPhotoId,
                                       /*daily_refresh_enabled=*/false,
                                       WALLPAPER_LAYOUT_STRETCH,
-                                      /*preview_mode=*/false});
+                                      /*preview_mode=*/false, "dedup_key"});
   controller_->SetGooglePhotosWallpaper(params,
                                         google_photos_future.GetCallback());
   EXPECT_EQ(GooglePhotosEnabled(), google_photos_future.Get());
@@ -4665,7 +4694,7 @@
   base::test::TestFuture<bool> google_photos_future;
   controller_->SetGooglePhotosWallpaper(
       {account_id_1, photo_id, /*daily_refresh_enabled=*/false, layout,
-       /*preview_mode=*/true},
+       /*preview_mode=*/true, "dedup_key"},
       google_photos_future.GetCallback());
   EXPECT_EQ(google_photos_future.Get(), GooglePhotosEnabled());
   RunAllTasksUntilIdle();
@@ -4731,7 +4760,7 @@
   base::test::TestFuture<bool> google_photos_future;
   controller_->SetGooglePhotosWallpaper(
       {account_id_1, photo_id, /*daily_refresh_enabled=*/false,
-       WALLPAPER_LAYOUT_STRETCH, /*preview_mode=*/true},
+       WALLPAPER_LAYOUT_STRETCH, /*preview_mode=*/true, "dedup_key"},
       google_photos_future.GetCallback());
   EXPECT_EQ(google_photos_future.Get(), GooglePhotosEnabled());
   RunAllTasksUntilIdle();
@@ -4790,7 +4819,7 @@
   base::test::TestFuture<bool> google_photos_future;
   controller_->SetGooglePhotosWallpaper(
       {account_id_1, photo_id, /*daily_refresh_enabled=*/false, layout,
-       /*preview_mode=*/true},
+       /*preview_mode=*/true, "dedup_key"},
       google_photos_future.GetCallback());
   EXPECT_EQ(google_photos_future.Get(), GooglePhotosEnabled());
   RunAllTasksUntilIdle();
@@ -4853,10 +4882,10 @@
 
   SimulateUserLogin(account_id_1);
 
-  GooglePhotosWallpaperParams params(account_id_1, kFakeGooglePhotosAlbumId,
-                                     /*daily_refresh_enabled=*/true,
-                                     WALLPAPER_LAYOUT_CENTER_CROPPED,
-                                     /*preview_mode=*/false);
+  GooglePhotosWallpaperParams params(
+      account_id_1, kFakeGooglePhotosAlbumId,
+      /*daily_refresh_enabled=*/true, WALLPAPER_LAYOUT_CENTER_CROPPED,
+      /*preview_mode=*/false, /*dedup_key=*/absl::nullopt);
   WallpaperInfo info(params);
   controller_->SetUserWallpaperInfo(account_id_1, info);
 
@@ -4877,10 +4906,10 @@
        DailyRefreshTimerStartsForDailyGooglePhotos) {
   SimulateUserLogin(account_id_1);
 
-  GooglePhotosWallpaperParams params(account_id_1, kFakeGooglePhotosAlbumId,
-                                     /*daily_refresh_enabled=*/true,
-                                     WALLPAPER_LAYOUT_CENTER_CROPPED,
-                                     /*preview_mode=*/false);
+  GooglePhotosWallpaperParams params(
+      account_id_1, kFakeGooglePhotosAlbumId,
+      /*daily_refresh_enabled=*/true, WALLPAPER_LAYOUT_CENTER_CROPPED,
+      /*preview_mode=*/false, /*dedup_key=*/absl::nullopt);
   WallpaperInfo info(params);
   controller_->SetUserWallpaperInfo(account_id_1, info);
 
@@ -4905,10 +4934,10 @@
        DailyRefreshRetryTimerStartsOnFailedFetch) {
   SimulateUserLogin(account_id_1);
 
-  GooglePhotosWallpaperParams params(account_id_1, kFakeGooglePhotosAlbumId,
-                                     /*daily_refresh_enabled=*/true,
-                                     WALLPAPER_LAYOUT_CENTER_CROPPED,
-                                     /*preview_mode=*/false);
+  GooglePhotosWallpaperParams params(
+      account_id_1, kFakeGooglePhotosAlbumId,
+      /*daily_refresh_enabled=*/true, WALLPAPER_LAYOUT_CENTER_CROPPED,
+      /*preview_mode=*/false, /*dedup_key=*/absl::nullopt);
   WallpaperInfo info(params);
   controller_->SetUserWallpaperInfo(account_id_1, info);
 
@@ -4937,7 +4966,8 @@
 
   GooglePhotosWallpaperParams daily_google_photos_params(
       account_id_1, kFakeGooglePhotosAlbumId, /*daily_refresh_enabled=*/true,
-      WALLPAPER_LAYOUT_CENTER_CROPPED, /*preview_mode=*/false);
+      WALLPAPER_LAYOUT_CENTER_CROPPED, /*preview_mode=*/false,
+      /*dedup_key=*/absl::nullopt);
   OnlineWallpaperParams online_params(
       account_id_1, kAssetId, GURL(kDummyUrl),
       TestWallpaperControllerClient::kDummyCollectionId,
@@ -4966,9 +4996,9 @@
 
   base::test::TestFuture<bool> google_photos_future;
   controller_->SetGooglePhotosWallpaper(
-      {account_id_1, kFakeGooglePhotosAlbumId, /*daily_refres_enabled=*/true,
+      {account_id_1, kFakeGooglePhotosAlbumId, /*daily_refresh_enabled=*/true,
        WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-       /*preview_mode=*/false},
+       /*preview_mode=*/false, /*dedup_key=*/absl::nullopt},
       google_photos_future.GetCallback());
   EXPECT_EQ(GooglePhotosEnabled(), google_photos_future.Get());
   RunAllTasksUntilIdle();
@@ -5004,7 +5034,7 @@
   controller_->SetGooglePhotosWallpaper(
       {account_id_1, kFakeGooglePhotosAlbumId, /*daily_refresh_enabled=*/true,
        WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-       /*preview_mode=*/false},
+       /*preview_mode=*/false, /*dedup_key=*/absl::nullopt},
       google_photos_future.GetCallback());
   EXPECT_EQ(GooglePhotosEnabled(), google_photos_future.Get());
   RunAllTasksUntilIdle();
diff --git a/ash/webui/personalization_app/mojom/personalization_app.mojom b/ash/webui/personalization_app/mojom/personalization_app.mojom
index e3e55ab..2f63419 100644
--- a/ash/webui/personalization_app/mojom/personalization_app.mojom
+++ b/ash/webui/personalization_app/mojom/personalization_app.mojom
@@ -132,6 +132,12 @@
     // Unique identifier for this photo.
     string id;
 
+    // A string which uniquely identifies a Google Photos photo across albums.
+    // Note that the same photo appearing in multiple albums will have a unique
+    // |id| for each album in which it appears, but the |dedup_key| is shared
+    // across albums.
+    string? dedup_key;
+
     // Display name of the photo, derived from the photo's server-side filename.
     string name;
 
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_app.ts b/ash/webui/personalization_app/resources/trusted/personalization_app.ts
index 4ac463b4..7bfe71a6 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_app.ts
+++ b/ash/webui/personalization_app/resources/trusted/personalization_app.ts
@@ -90,7 +90,7 @@
 export {UserPreview} from './user/user_preview_element.js';
 export {UserSubpage} from './user/user_subpage_element.js';
 export {GetUserMediaProxy, getWebcamUtils, setWebcamUtilsForTesting} from './user/webcam_utils_proxy.js';
-export {getImageKey, isDefaultImage, isFilePath, isGooglePhotosPhoto, isWallpaperImage} from './utils.js';
+export {isDefaultImage, isFilePath, isGooglePhotosPhoto, isWallpaperImage} from './utils.js';
 export {GooglePhotosAlbums} from './wallpaper/google_photos_albums_element.js';
 export {GooglePhotosCollection} from './wallpaper/google_photos_collection_element.js';
 export {GooglePhotosPhotosByAlbumId} from './wallpaper/google_photos_photos_by_album_id_element.js';
diff --git a/ash/webui/personalization_app/resources/trusted/utils.ts b/ash/webui/personalization_app/resources/trusted/utils.ts
index bef0e156..d40f621 100644
--- a/ash/webui/personalization_app/resources/trusted/utils.ts
+++ b/ash/webui/personalization_app/resources/trusted/utils.ts
@@ -33,21 +33,23 @@
   return !!obj && typeof obj.id === 'string';
 }
 
-/** Returns the unique identifier for |image|. */
-export function getImageKey(image: DisplayableImage): string|
-    DefaultImageSymbol {
+/** Returns whether |image| is a match for the specified |key|. */
+export function isImageAMatchForKey(
+    image: DisplayableImage, key: string|DefaultImageSymbol): boolean {
   if (isWallpaperImage(image)) {
-    return image.assetId.toString();
+    return key === image.assetId.toString();
   }
   if (isDefaultImage(image)) {
-    return kDefaultImageSymbol;
+    return key === kDefaultImageSymbol;
   }
   if (isFilePath(image)) {
     // TODO(b/229420564): Update key extraction for local images.
-    return image.path.substr(image.path.lastIndexOf('/') + 1);
+    return key === image.path.substr(image.path.lastIndexOf('/') + 1);
   }
   assert(isGooglePhotosPhoto(image));
-  return image.id;
+  // NOTE: Old clients may not support |dedupKey| when setting Google Photos
+  // wallpaper, so use |id| in such cases for backwards compatibility.
+  return (image.dedupKey && key === image.dedupKey) || key === image.id;
 }
 
 /**
@@ -64,7 +66,7 @@
     // |CurrentWallpaper.key| cannot include javascript symbols.
     return selected.type === WallpaperType.kDefault;
   }
-  return getImageKey(image) === selected.key;
+  return isImageAMatchForKey(image, selected.key);
 }
 
 /**
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
index 56f0a5e..9819a34 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_by_album_id_element.ts
@@ -22,7 +22,7 @@
 import {dismissErrorAction, setErrorAction} from '../personalization_actions.js';
 import {CurrentWallpaper, GooglePhotosAlbum, GooglePhotosPhoto, WallpaperProviderInterface, WallpaperType} from '../personalization_app.mojom-webui.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
-import {isGooglePhotosPhoto} from '../utils.js';
+import {isGooglePhotosPhoto, isImageAMatchForKey, isImageEqualToSelected} from '../utils.js';
 
 import {recordWallpaperGooglePhotosSourceUMA, WallpaperGooglePhotosSource} from './google_photos_metrics_logger.js';
 import {getTemplate} from './google_photos_photos_by_album_id_element.html.js';
@@ -317,13 +317,18 @@
     if (!photo || (!currentSelected && !pendingSelected)) {
       return false;
     }
+    // NOTE: Old clients may not support |dedupKey| when setting Google Photos
+    // wallpaper, so use |id| in such cases for backwards compatibility.
     if (isGooglePhotosPhoto(pendingSelected) &&
-        pendingSelected!.id === photo.id) {
+        ((pendingSelected!.dedupKey &&
+          isImageAMatchForKey(photo, pendingSelected!.dedupKey)) ||
+         isImageAMatchForKey(photo, pendingSelected!.id))) {
       return true;
     }
     if (!pendingSelected && !!currentSelected &&
-        currentSelected.type === WallpaperType.kOnceGooglePhotos &&
-        currentSelected.key === photo.id) {
+        (currentSelected.type === WallpaperType.kOnceGooglePhotos ||
+         currentSelected.type === WallpaperType.kDailyGooglePhotos) &&
+        isImageEqualToSelected(photo, currentSelected)) {
       return true;
     }
     return false;
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
index 0b27f43..6abba1f 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_photos_element.ts
@@ -21,7 +21,7 @@
 import {dismissErrorAction, setErrorAction} from '../personalization_actions.js';
 import {CurrentWallpaper, GooglePhotosPhoto, WallpaperProviderInterface, WallpaperType} from '../personalization_app.mojom-webui.js';
 import {WithPersonalizationStore} from '../personalization_store.js';
-import {isGooglePhotosPhoto} from '../utils.js';
+import {isGooglePhotosPhoto, isImageAMatchForKey, isImageEqualToSelected} from '../utils.js';
 
 import {recordWallpaperGooglePhotosSourceUMA, WallpaperGooglePhotosSource} from './google_photos_metrics_logger.js';
 import {getTemplate} from './google_photos_photos_element.html.js';
@@ -480,13 +480,18 @@
     if (!photo || (!currentSelected && !pendingSelected)) {
       return false;
     }
+    // NOTE: Old clients may not support |dedupKey| when setting Google Photos
+    // wallpaper, so use |id| in such cases for backwards compatibility.
     if (isGooglePhotosPhoto(pendingSelected) &&
-        pendingSelected!.id === photo.id) {
+        ((pendingSelected!.dedupKey &&
+          isImageAMatchForKey(photo, pendingSelected!.dedupKey)) ||
+         isImageAMatchForKey(photo, pendingSelected!.id))) {
       return true;
     }
     if (!pendingSelected && !!currentSelected &&
-        currentSelected.type === WallpaperType.kOnceGooglePhotos &&
-        currentSelected.key === photo.id) {
+        (currentSelected.type === WallpaperType.kOnceGooglePhotos ||
+         currentSelected.type === WallpaperType.kDailyGooglePhotos) &&
+        isImageEqualToSelected(photo, currentSelected)) {
       return true;
     }
     return false;
diff --git a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
index 2bacbb0..157788f0 100644
--- a/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
+++ b/chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.cc
@@ -220,6 +220,7 @@
     return;
 
   const auto* id = photo->FindStringByDottedPath("itemId.mediaKey");
+  const auto* dedup_key = photo->FindString("dedupKey");
   const auto* filename = photo->FindString("filename");
   const auto* timestamp_string = photo->FindString("creationTimestamp");
   const auto* url = photo->FindStringByDottedPath("photo.servingUrl");
@@ -245,7 +246,8 @@
   // A photo from Google Photos may or may not have a location set.
   parsed_response->photos->push_back(
       ash::personalization_app::mojom::GooglePhotosPhoto::New(
-          *id, name, date, GURL(*url),
+          *id, dedup_key ? absl::make_optional(*dedup_key) : absl::nullopt,
+          name, date, GURL(*url),
           location ? absl::make_optional(*location) : absl::nullopt));
 }
 
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc
index d78acbf3..cd7b9b9 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.cc
@@ -393,15 +393,8 @@
 
       return;
     }
-    case ash::WallpaperType::kOnceGooglePhotos:
-      client->FetchGooglePhotosPhoto(
-          GetAccountId(profile_), info.location,
-          base::BindOnce(&PersonalizationAppWallpaperProviderImpl::
-                             SendGooglePhotosAttribution,
-                         weak_ptr_factory_.GetWeakPtr(), info,
-                         wallpaper_data_url));
-      return;
     case ash::WallpaperType::kDailyGooglePhotos:
+    case ash::WallpaperType::kOnceGooglePhotos:
       client->FetchGooglePhotosPhoto(
           GetAccountId(profile_), info.location,
           base::BindOnce(&PersonalizationAppWallpaperProviderImpl::
@@ -535,7 +528,8 @@
   client->SetGooglePhotosWallpaper(
       ash::GooglePhotosWallpaperParams(GetAccountId(profile_), id,
                                        /*daily_refresh_enabled=*/false, layout,
-                                       preview_mode),
+                                       preview_mode,
+                                       /*dedup_key=*/absl::nullopt),
       base::BindOnce(&PersonalizationAppWallpaperProviderImpl::
                          OnGooglePhotosWallpaperSelected,
                      backend_weak_ptr_factory_.GetWeakPtr()));
@@ -564,7 +558,8 @@
           GetAccountId(profile_), id,
           /*daily_refresh=*/true,
           ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-          /*preview_mode=*/false),
+          /*preview_mode=*/false,
+          /*dedup_key=*/absl::nullopt),
       base::BindOnce(
           &PersonalizationAppWallpaperProviderImpl::OnGooglePhotosAlbumSelected,
           backend_weak_ptr_factory_.GetWeakPtr()));
@@ -857,9 +852,11 @@
   if (!photo.is_null()) {
     attribution.push_back(photo->name);
   }
+  // NOTE: Old clients may not support |dedup_key| when setting Google Photos
+  // wallpaper, so use |location| in such cases for backwards compatibility.
   NotifyWallpaperChanged(ash::personalization_app::mojom::CurrentWallpaper::New(
       wallpaper_data_url, attribution, info.layout, info.type,
-      /*key=*/info.location));
+      /*key=*/info.dedup_key.value_or(info.location)));
 }
 
 void PersonalizationAppWallpaperProviderImpl::SetMinimizedWindowStateForPreview(
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
index 7b531a7..50bbb02 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc
@@ -81,6 +81,7 @@
     "      \"itemId\": {"
     "         \"mediaKey\": \"photoId\""
     "      },"
+    "      \"dedupKey\": \"dedupKey\","
     "      \"filename\": \"photoName.png\","
     "      \"creationTimestamp\": \"2021-12-31T07:07:07.000Z\","
     "      \"photo\": {"
@@ -100,6 +101,7 @@
     "      \"itemId\": {"
     "         \"mediaKey\": \"photoId\""
     "      },"
+    "      \"dedupKey\": \"dedupKey\","
     "      \"filename\": \"photoName.png\","
     "      \"creationTimestamp\": \"2021-12-31T07:07:07.000Z\","
     "      \"photo\": {"
@@ -877,7 +879,7 @@
   // Parse a response with a valid photo and a resume token.
   auto valid_photos_vector = std::vector<GooglePhotosPhotoPtr>();
   valid_photos_vector.push_back(GooglePhotosPhoto::New(
-      "photoId", "photoName", u"Friday, December 31, 2021",
+      "photoId", "dedupKey", "photoName", u"Friday, December 31, 2021",
       GURL("https://www.google.com/"), "home"));
   auto response = JsonToDict(kGooglePhotosPhotosFullResponse);
   auto result = FetchGooglePhotosPhotosResponse::New(
@@ -900,18 +902,35 @@
   EXPECT_EQ(google_photos_photos_fetcher->GetResultCount(result),
             absl::make_optional<size_t>(valid_photos_vector.size()));
 
+  // Parse a response with a valid photo and no dedup key.
+  auto valid_photos_vector_without_dedup_key =
+      std::vector<GooglePhotosPhotoPtr>();
+  valid_photos_vector_without_dedup_key.push_back(GooglePhotosPhoto::New(
+      "photoId", absl::nullopt, "photoName", u"Friday, December 31, 2021",
+      GURL("https://www.google.com/"), "home"));
+  response.RemoveByDottedPath("item.dedupKey");
+  result = FetchGooglePhotosPhotosResponse::New(
+      mojo::Clone(valid_photos_vector_without_dedup_key), absl::nullopt);
+  EXPECT_EQ(result, google_photos_photos_fetcher->ParseResponse(&response));
+  EXPECT_EQ(google_photos_photos_fetcher->GetResultCount(result),
+            absl::make_optional<size_t>(
+                valid_photos_vector_without_dedup_key.size()));
+
   // Parse a response with a valid photo and no location.
   auto valid_photos_vector_without_location =
       std::vector<GooglePhotosPhotoPtr>();
   valid_photos_vector_without_location.push_back(GooglePhotosPhoto::New(
-      "photoId", "photoName", u"Friday, December 31, 2021",
+      "photoId", absl::nullopt, "photoName", u"Friday, December 31, 2021",
       GURL("https://www.google.com/"), absl::nullopt));
   auto* name_list = response.FindListByDottedPath("item.locationFeature.name");
   EXPECT_FALSE(name_list->empty());
   name_list->clear();
-  EXPECT_EQ(FetchGooglePhotosPhotosResponse::New(
-                std::move(valid_photos_vector_without_location), absl::nullopt),
-            google_photos_photos_fetcher->ParseResponse(&response));
+  result = FetchGooglePhotosPhotosResponse::New(
+      mojo::Clone(valid_photos_vector_without_location), absl::nullopt);
+  EXPECT_EQ(result, google_photos_photos_fetcher->ParseResponse(&response));
+  EXPECT_EQ(
+      google_photos_photos_fetcher->GetResultCount(result),
+      absl::make_optional<size_t>(valid_photos_vector_without_location.size()));
 }
 
 TEST_P(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
@@ -933,7 +952,7 @@
                 {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
                  photo_id, /*daily_refresh_enabled=*/false,
                  ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-                 /*preview_mode=*/false}),
+                 /*preview_mode=*/false, "dedup_key"}),
             test_wallpaper_controller()->wallpaper_info().value_or(
                 ash::WallpaperInfo()));
 
@@ -954,7 +973,7 @@
                 {AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId),
                  photo_id, /*daily_refresh_enabled=*/false,
                  ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
-                 /*preview_mode=*/false}) ==
+                 /*preview_mode=*/false, "dedup_key"}) ==
                 test_wallpaper_controller()->wallpaper_info().value_or(
                     ash::WallpaperInfo()));
 }
diff --git a/chrome/test/data/webui/chromeos/personalization_app/google_photos_collection_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/google_photos_collection_element_test.ts
index 51c2c5c..c1622c1 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/google_photos_collection_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/google_photos_collection_element_test.ts
@@ -44,6 +44,7 @@
     wallpaperProvider.setGooglePhotosAlbums(undefined);
     wallpaperProvider.setGooglePhotosPhotos([{
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+      dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
       name: 'foo',
       date: {data: []},
       url: {url: 'foo.com'},
@@ -148,6 +149,7 @@
     wallpaperProvider.setGooglePhotosAlbums(albums);
     wallpaperProvider.setGooglePhotosPhotos([{
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+      dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
       name: 'foo',
       date: {data: []},
       url: {url: 'foo.com'},
diff --git a/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_by_album_id_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_by_album_id_element_test.ts
index 5844530..4344f25 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_by_album_id_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_by_album_id_element_test.ts
@@ -131,6 +131,7 @@
       [album.id]: [
         {
           id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+          dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
           name: 'foo',
           date: {data: []},
           url: {url: 'foo.com'},
@@ -138,6 +139,7 @@
         },
         {
           id: '0ec40478-9712-42e1-b5bf-3e75870ca042',
+          dedupKey: '2cb1b955-0b7e-4f59-b9d0-802227aeeb28',
           name: 'bar',
           date: {data: []},
           url: {url: 'bar.com'},
@@ -147,6 +149,7 @@
       [otherAlbum.id]: [
         {
           id: '0a268a37-877a-4936-81d4-38cc84b0f596',
+          dedupKey: 'd99eedfa-43e5-4bca-8882-b881222b8db9',
           name: 'baz',
           date: {data: []},
           url: {url: 'baz.com'},
@@ -254,6 +257,7 @@
 
     const photo: GooglePhotosPhoto = {
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+      dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
       name: 'foo',
       date: {data: []},
       url: {url: 'foo.com'},
@@ -262,15 +266,26 @@
 
     const anotherPhoto: GooglePhotosPhoto = {
       id: '0ec40478-9712-42e1-b5bf-3e75870ca042',
+      dedupKey: '2cb1b955-0b7e-4f59-b9d0-802227aeeb28',
       name: 'bar',
       date: {data: []},
       url: {url: 'bar.com'},
       location: 'home2'
     };
 
+    const yetAnotherPhoto: GooglePhotosPhoto = {
+      id: '0a268a37-877a-4936-81d4-38cc84b0f596',
+      dedupKey: photo.dedupKey,
+      name: 'baz',
+      date: {data: []},
+      url: {url: 'baz.com'},
+      location: 'home3'
+    };
+
     // Set values returned by |wallpaperProvider|.
     wallpaperProvider.setGooglePhotosAlbums([album]);
-    wallpaperProvider.setGooglePhotosPhotos([photo, anotherPhoto]);
+    wallpaperProvider.setGooglePhotosPhotos(
+        [photo, anotherPhoto, yetAnotherPhoto]);
     wallpaperProvider.setGooglePhotosPhotosByAlbumId(
         album.id, [photo, anotherPhoto]);
 
@@ -283,6 +298,7 @@
     album.preview.url += '=s512';
     photo.url.url += '=s512';
     anotherPhoto.url.url += '=s512';
+    yetAnotherPhoto.url.url += '=s512';
 
     // Initialize |googlePhotosPhotosByAlbumIdElement|.
     googlePhotosPhotosByAlbumIdElement =
@@ -349,6 +365,31 @@
     assertEquals(photoEls[0]!.selected, false);
     assertEquals(photoEls[1]!.selected, true);
 
+    // Start a pending selection for |yetAnotherPhoto|.
+    personalizationStore.data.wallpaper.pendingSelected = yetAnotherPhoto;
+    personalizationStore.notifyObservers();
+    await waitAfterNextRender(googlePhotosPhotosByAlbumIdElement);
+
+    // Verify selected states.
+    assertEquals(photoEls[0]!.selected, true);
+    assertEquals(photoEls[1]!.selected, false);
+
+    // Complete the pending selection.
+    personalizationStore.data.wallpaper.pendingSelected = null;
+    personalizationStore.data.wallpaper.currentSelected = {
+      url: yetAnotherPhoto.url,
+      attribution: [],
+      layout: WallpaperLayout.kCenter,
+      type: WallpaperType.kOnceGooglePhotos,
+      key: yetAnotherPhoto.dedupKey
+    };
+    personalizationStore.notifyObservers();
+    await waitAfterNextRender(googlePhotosPhotosByAlbumIdElement);
+
+    // Verify selected states.
+    assertEquals(photoEls[0]!.selected, true);
+    assertEquals(photoEls[1]!.selected, false);
+
     // Start a pending selection for a |FilePath| backed wallpaper.
     personalizationStore.data.wallpaper.pendingSelected = {path: '//foo'};
     personalizationStore.notifyObservers();
@@ -383,6 +424,7 @@
     const photos: GooglePhotosPhoto[] =
         Array.from({length: photosCount}, (_, i) => ({
                                             id: `id-${i}`,
+                                            dedupKey: `dedupKey-${i}`,
                                             name: `name-${i}`,
                                             date: {data: []},
                                             url: {url: `url-${i}`},
@@ -480,6 +522,7 @@
         album.id, Array.from({length: photosCount / 2}).map(() => {
           return {
             id: `id-${nextPhotoId}`,
+            dedupKey: `dedupKey-${nextPhotoId}`,
             name: `name-${nextPhotoId}`,
             date: {data: []},
             url: {url: `url-${nextPhotoId}`},
@@ -519,6 +562,7 @@
         album.id, Array.from({length: photosCount / 2}).map(() => {
           return {
             id: `id-${nextPhotoId}`,
+            dedupKey: `dedupKey-${nextPhotoId}`,
             name: `name-${nextPhotoId}`,
             date: {data: []},
             url: {url: `url-${nextPhotoId}`},
@@ -622,6 +666,7 @@
 
     const photo: GooglePhotosPhoto = {
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+      dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
       name: 'foo',
       date: {data: []},
       url: {url: 'foo.com'},
diff --git a/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts
index 2d8c9e7..3d42369 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/google_photos_photos_element_test.ts
@@ -150,6 +150,7 @@
       // First row.
       {
         id: '1',
+        dedupKey: '1',
         name: '1',
         date: toString16('First row'),
         url: {url: '1'},
@@ -158,6 +159,7 @@
       // Second row.
       {
         id: '2',
+        dedupKey: '2',
         name: '2',
         date: toString16('Second row'),
         url: {url: '2'},
@@ -165,6 +167,7 @@
       },
       {
         id: '3',
+        dedupKey: '3',
         name: '3',
         date: toString16('Second row'),
         url: {url: '3'},
@@ -173,6 +176,7 @@
       // Third row.
       {
         id: '4',
+        dedupKey: '4',
         name: '4',
         date: toString16('Third row'),
         url: {url: '4'},
@@ -303,6 +307,7 @@
       // Section of photos without location.
       {
         id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+        dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
         name: 'foo',
         date: toString16('Wednesday, February 16, 2022'),
         url: {url: 'foo.com'},
@@ -311,6 +316,7 @@
       // Section of photos with one location.
       {
         id: '0ec40478-9712-42e1-b5bf-3e75870ca042',
+        dedupKey: '2cb1b955-0b7e-4f59-b9d0-802227aeeb28',
         name: 'bar',
         date: toString16('Friday, November 12, 2021'),
         url: {url: 'bar.com'},
@@ -318,6 +324,7 @@
       },
       {
         id: '0a268a37-877a-4936-81d4-38cc84b0f596',
+        dedupKey: 'd99eedfa-43e5-4bca-8882-b881222b8db9',
         name: 'baz',
         date: toString16('Friday, November 12, 2021'),
         url: {url: 'baz.com'},
@@ -326,6 +333,7 @@
       // Section of photos with different locations.
       {
         id: '0a5231as-97a2-42e1-bdbf-3e75870ca042',
+        dedupKey: 'ef8795ae-e6c8-4580-8184-0bcad20fd013',
         name: 'bare',
         date: toString16('Friday, July 16, 2021'),
         url: {url: 'bare.com'},
@@ -333,6 +341,7 @@
       },
       {
         id: '0a268a11-877a-4936-81d4-38cc8s9dn396',
+        dedupKey: 'c8817402-822f-4ee8-9716-1f4b36c3263f',
         name: 'baze',
         date: toString16('Friday, July 16, 2021'),
         url: {url: 'baze.com'},
@@ -417,6 +426,7 @@
   test('displays photo selected', async () => {
     const photo: GooglePhotosPhoto = {
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+      dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
       name: 'foo',
       date: {data: []},
       url: {url: 'foo.com'},
@@ -425,14 +435,25 @@
 
     const anotherPhoto: GooglePhotosPhoto = {
       id: '0ec40478-9712-42e1-b5bf-3e75870ca042',
+      dedupKey: '2cb1b955-0b7e-4f59-b9d0-802227aeeb28',
       name: 'bar',
       date: {data: []},
       url: {url: 'bar.com'},
       location: 'home2'
     };
 
+    const yetAnotherPhoto: GooglePhotosPhoto = {
+      id: '0a268a37-877a-4936-81d4-38cc84b0f596',
+      dedupKey: anotherPhoto.dedupKey,
+      name: 'baz',
+      date: {data: []},
+      url: {url: 'baz.com'},
+      location: 'home3'
+    };
+
     // Set values returned by |wallpaperProvider|.
-    wallpaperProvider.setGooglePhotosPhotos([photo, anotherPhoto]);
+    wallpaperProvider.setGooglePhotosPhotos(
+        [photo, anotherPhoto, yetAnotherPhoto]);
 
     // Initialize Google Photos data in the |personalizationStore|.
     await initializeGooglePhotosData(wallpaperProvider, personalizationStore);
@@ -441,6 +462,7 @@
     // The wallpaper controller is expected to impose max resolution.
     photo.url.url += '=s512';
     anotherPhoto.url.url += '=s512';
+    yetAnotherPhoto.url.url += '=s512';
 
     // Initialize |googlePhotosPhotosElement|.
     googlePhotosPhotosElement =
@@ -450,11 +472,12 @@
     // Verify that the expected photos are rendered.
     const photoSelector = 'wallpaper-grid-item:not([hidden]).photo';
     const photoEls = querySelectorAll(photoSelector) as WallpaperGridItem[];
-    assertEquals(photoEls.length, 2);
+    assertEquals(photoEls.length, 3);
 
     // Verify selected states.
     assertEquals(photoEls[0]!.selected, false);
     assertEquals(photoEls[1]!.selected, false);
+    assertEquals(photoEls[2]!.selected, false);
 
     // Start a pending selection for |photo|.
     personalizationStore.data.wallpaper.pendingSelected = photo;
@@ -464,6 +487,7 @@
     // Verify selected states.
     assertEquals(photoEls[0]!.selected, true);
     assertEquals(photoEls[1]!.selected, false);
+    assertEquals(photoEls[2]!.selected, false);
 
     // Complete the pending selection.
     personalizationStore.data.wallpaper.pendingSelected = null;
@@ -480,6 +504,7 @@
     // Verify selected states.
     assertEquals(photoEls[0]!.selected, true);
     assertEquals(photoEls[1]!.selected, false);
+    assertEquals(photoEls[2]!.selected, false);
 
     // Start a pending selection for |anotherPhoto|.
     personalizationStore.data.wallpaper.pendingSelected = anotherPhoto;
@@ -497,7 +522,7 @@
       attribution: [],
       layout: WallpaperLayout.kCenter,
       type: WallpaperType.kOnceGooglePhotos,
-      key: anotherPhoto.id
+      key: anotherPhoto.dedupKey
     };
     personalizationStore.notifyObservers();
     await waitAfterNextRender(googlePhotosPhotosElement);
@@ -505,6 +530,7 @@
     // Verify selected states.
     assertEquals(photoEls[0]!.selected, false);
     assertEquals(photoEls[1]!.selected, true);
+    assertEquals(photoEls[2]!.selected, true);
 
     // Start a pending selection for a |FilePath| backed wallpaper.
     personalizationStore.data.wallpaper.pendingSelected = {path: '//foo'};
@@ -514,6 +540,7 @@
     // Verify selected states.
     assertEquals(photoEls[0]!.selected, false);
     assertEquals(photoEls[1]!.selected, false);
+    assertEquals(photoEls[2]!.selected, false);
 
     // Complete the pending selection.
     personalizationStore.data.wallpaper.pendingSelected = null;
@@ -530,6 +557,7 @@
     // Verify selected states.
     assertEquals(photoEls[0]!.selected, false);
     assertEquals(photoEls[1]!.selected, false);
+    assertEquals(photoEls[2]!.selected, false);
   });
 
   test('displays placeholders until photos are present', async () => {
@@ -538,6 +566,7 @@
     const photos: GooglePhotosPhoto[] =
         Array.from({length: photosCount}, (_, i) => ({
                                             id: `id-${i}`,
+                                            dedupKey: `dedupKey-${i}`,
                                             name: `name-${i}`,
                                             date: {data: []},
                                             url: {url: `url-${i}`},
@@ -598,6 +627,7 @@
         Array.from({length: photosCount / 2}).map(() => {
           return {
             id: `id-${nextPhotoId}`,
+            dedupKey: `dedupKey-${nextPhotoId}`,
             name: `name-${nextPhotoId}`,
             date: {data: []},
             url: {url: `url-${nextPhotoId}`},
@@ -625,6 +655,7 @@
         Array.from({length: photosCount / 2}).map(() => {
           return {
             id: `id-${nextPhotoId}`,
+            dedupKey: `dedupKey-${nextPhotoId}`,
             name: `name-${nextPhotoId}`,
             date: {data: []},
             url: {url: `url-${nextPhotoId}`},
@@ -746,6 +777,7 @@
   test('selects photo', async () => {
     const photo: GooglePhotosPhoto = {
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+      dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
       name: 'foo',
       date: {data: []},
       url: {url: 'foo.com'},
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
index 5f99f10..30a73ef 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_controller_test.ts
@@ -5,7 +5,7 @@
 import 'chrome://personalization/strings.m.js';
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
-import {cancelPreviewWallpaper, DefaultImageSymbol, DisplayableImage, fetchCollections, fetchGooglePhotosAlbum, fetchGooglePhotosAlbums, fetchLocalData, getDefaultImageThumbnail, getImageKey, getLocalImages, GooglePhotosAlbum, GooglePhotosEnablementState, GooglePhotosPhoto, initializeBackdropData, initializeGooglePhotosData, isDefaultImage, isFilePath, isGooglePhotosPhoto, isWallpaperImage, kDefaultImageSymbol, selectWallpaper, WallpaperType} from 'chrome://personalization/trusted/personalization_app.js';
+import {cancelPreviewWallpaper, DefaultImageSymbol, DisplayableImage, fetchCollections, fetchGooglePhotosAlbum, fetchGooglePhotosAlbums, fetchLocalData, getDefaultImageThumbnail, getLocalImages, GooglePhotosAlbum, GooglePhotosEnablementState, GooglePhotosPhoto, initializeBackdropData, initializeGooglePhotosData, isDefaultImage, isFilePath, isGooglePhotosPhoto, isWallpaperImage, kDefaultImageSymbol, selectWallpaper, WallpaperType} from 'chrome://personalization/trusted/personalization_app.js';
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
@@ -119,6 +119,7 @@
 
     const photos: GooglePhotosPhoto[] = [{
       id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+      dedupKey: '2d0d1595-14af-4471-b2db-b9c8eae3a491',
       name: 'foo',
       date: {data: []},
       url: {url: 'foo.com'},
@@ -788,6 +789,22 @@
     wallpaperProvider.isInTabletModeResponse = false;
   });
 
+  function getImageKey(image: DisplayableImage): string|undefined {
+    if (isDefaultImage(image)) {
+      return undefined;
+    }
+    if (isGooglePhotosPhoto(image)) {
+      return image.dedupKey ? image.dedupKey : image.id;
+    }
+    if (isWallpaperImage(image)) {
+      return image.assetId.toString();
+    }
+    if (isFilePath(image)) {
+      return image.path.substr(image.path.lastIndexOf('/') + 1);
+    }
+    assertNotReached('unknown wallpaper type');
+  }
+
   function getImageType(image: DisplayableImage): WallpaperType {
     if (isDefaultImage(image)) {
       return WallpaperType.kDefault;
@@ -866,20 +883,23 @@
     await testReselectWallpaper(image);
   });
 
-  test('re-selects Google Photos wallpaper', async () => {
-    const image: GooglePhotosPhoto = {
-      id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
-      name: 'foo',
-      date: {data: []},
-      url: {url: 'foo.com'},
-      location: 'home'
-    };
-    // Reset the history of actions and prior states, but keep the current
-    // state.
-    personalizationStore.reset(personalizationStore.data);
-
-    await testReselectWallpaper(image);
-  });
+  // Check with both |dedupKey| absent and present for backwards compatibility
+  // with older clients that do not support the latter.
+  [undefined, '2d0d1595-14af-4471-b2db-b9c8eae3a491'].forEach(
+      dedupKey => test('re-selects Google Photos wallpaper', async () => {
+        const image: GooglePhotosPhoto = {
+          id: '9bd1d7a3-f995-4445-be47-53c5b58ce1cb',
+          dedupKey: dedupKey,
+          name: 'foo',
+          date: {data: []},
+          url: {url: 'foo.com'},
+          location: 'home'
+        };
+        // Reset the history of actions and prior states, but keep the current
+        // state.
+        personalizationStore.reset(personalizationStore.data);
+        await testReselectWallpaper(image);
+      }));
 
   test('re-selects default image', async () => {
     // Reset the history of actions and prior states, but keep the current