1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.h"
6 
7 #include <algorithm>
8 #include <unordered_set>
9 #include <utility>
10 
11 #include "ash/public/cpp/app_list/app_list_config.h"
12 #include "ash/public/cpp/app_list/app_list_features.h"
13 #include "base/metrics/field_trial_params.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/strings/strcat.h"
16 #include "base/strings/string16.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/time/time.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
21 #include "chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.h"
22 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
23 #include "chrome/browser/ui/app_list/search/common/url_icon_source.h"
24 #include "chrome/common/pref_names.h"
25 #include "chromeos/constants/chromeos_features.h"
26 #include "chromeos/constants/chromeos_pref_names.h"
27 #include "components/arc/arc_service_manager.h"
28 #include "components/arc/session/arc_bridge_service.h"
29 #include "components/pref_registry/pref_registry_syncable.h"
30 #include "components/prefs/pref_registry_simple.h"
31 #include "components/prefs/pref_service.h"
32 #include "components/prefs/scoped_user_pref_update.h"
33 #include "extensions/grit/extensions_browser_resources.h"
34 
35 namespace {
36 // Seconds in between refreshes;
37 constexpr base::TimeDelta kRefresh = base::TimeDelta::FromMinutes(30);
38 
39 constexpr char kAppListLatency[] = "Apps.AppListRecommendedResponse.Latency";
40 constexpr char kAppListCounts[] = "Apps.AppListRecommendedResponse.Count";
41 constexpr char kAppListImpressionsBeforeOpen[] =
42     "Apps.AppListRecommendedImpResultCountAfterOpen";
43 
44 // If uninstalled in this time, do not recommend.
45 constexpr base::FeatureParam<int> kUninstallGrace(
46     &app_list_features::kEnableAppReinstallZeroState,
47     "uninstall_time_hours",
48     24 * 90);
49 
50 // If install start triggered within this many days, do not recommend.
51 constexpr base::FeatureParam<int> kInstallStartGrace(
52     &app_list_features::kEnableAppReinstallZeroState,
53     "install_start_hours",
54     24);
55 
56 // If install impression older than this age, reset impressions.
57 constexpr base::FeatureParam<int> kResetImpressionGrace(
58     &app_list_features::kEnableAppReinstallZeroState,
59     "reset_impression_hours",
60     30 * 24);
61 
62 // Count an impression as new if it's more than this much away from the
63 // previous.
64 constexpr base::FeatureParam<int> kNewImpressionTime(
65     &app_list_features::kEnableAppReinstallZeroState,
66     "new_impression_seconds",
67     30);
68 
69 // Maximum number of impressions to show an item.
70 constexpr base::FeatureParam<int> kImpressionLimit(
71     &app_list_features::kEnableAppReinstallZeroState,
72     "impression_count_limit",
73     5);
74 
75 // If a user has meaningfully interacted with this feature within this grace
76 // period, do not show anything. If set to 0, ignored.
77 constexpr base::FeatureParam<int> kInteractionGrace(
78     &app_list_features::kEnableAppReinstallZeroState,
79     "interaction_grace_hours",
80     0);
81 
SetStateInt64(Profile * profile,const std::string & package_name,const std::string & key,const int64_t value)82 void SetStateInt64(Profile* profile,
83                    const std::string& package_name,
84                    const std::string& key,
85                    const int64_t value) {
86   const std::string int64_str = base::NumberToString(value);
87   DictionaryPrefUpdate update(
88       profile->GetPrefs(), app_list::ArcAppReinstallSearchProvider::kAppState);
89   base::DictionaryValue* const dictionary = update.Get();
90   base::Value* package_item =
91       dictionary->FindKeyOfType(package_name, base::Value::Type::DICTIONARY);
92   if (!package_item) {
93     package_item = dictionary->SetKey(
94         package_name, base::Value(base::Value::Type::DICTIONARY));
95   }
96 
97   package_item->SetKey(key, base::Value(int64_str));
98 }
99 
UpdateStateRemoveKey(Profile * profile,const std::string & package_name,const std::string & key)100 void UpdateStateRemoveKey(Profile* profile,
101                           const std::string& package_name,
102                           const std::string& key) {
103   DictionaryPrefUpdate update(
104       profile->GetPrefs(), app_list::ArcAppReinstallSearchProvider::kAppState);
105   base::DictionaryValue* const dictionary = update.Get();
106   base::Value* package_item =
107       dictionary->FindKeyOfType(package_name, base::Value::Type::DICTIONARY);
108   if (!package_item) {
109     return;
110   }
111   package_item->RemoveKey(key);
112 }
113 
UpdateStateTime(Profile * profile,const std::string & package_name,const std::string & key)114 void UpdateStateTime(Profile* profile,
115                      const std::string& package_name,
116                      const std::string& key) {
117   const int64_t timestamp =
118       base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds();
119   SetStateInt64(profile, package_name, key, timestamp);
120 }
121 
GetStateInt64(Profile * profile,const std::string & package_name,const std::string & key,int64_t * value)122 bool GetStateInt64(Profile* profile,
123                    const std::string& package_name,
124                    const std::string& key,
125                    int64_t* value) {
126   const base::DictionaryValue* dictionary = profile->GetPrefs()->GetDictionary(
127       app_list::ArcAppReinstallSearchProvider::kAppState);
128   if (!dictionary)
129     return false;
130   const base::Value* package_item =
131       dictionary->FindKeyOfType(package_name, base::Value::Type::DICTIONARY);
132   if (!package_item)
133     return false;
134   const std::string* value_str = package_item->FindStringKey(key);
135   if (!value_str)
136     return false;
137 
138   if (!base::StringToInt64(*value_str, value)) {
139     LOG(ERROR) << "Failed conversion " << *value_str;
140     return false;
141   }
142 
143   return true;
144 }
145 
GetStateTime(Profile * profile,const std::string & package_name,const std::string & key,base::TimeDelta * time_delta)146 bool GetStateTime(Profile* profile,
147                   const std::string& package_name,
148                   const std::string& key,
149                   base::TimeDelta* time_delta) {
150   int64_t value;
151   if (!GetStateInt64(profile, package_name, key, &value))
152     return false;
153 
154   *time_delta = base::TimeDelta::FromMilliseconds(value);
155   return true;
156 }
157 
GetKnownPackageNames(Profile * profile,std::unordered_set<std::string> * package_names)158 bool GetKnownPackageNames(Profile* profile,
159                           std::unordered_set<std::string>* package_names) {
160   const base::DictionaryValue* dictionary = profile->GetPrefs()->GetDictionary(
161       app_list::ArcAppReinstallSearchProvider::kAppState);
162   for (const auto& it : dictionary->DictItems()) {
163     if (it.second.is_dict()) {
164       package_names->insert(it.first);
165     }
166   }
167   return true;
168 }
169 
RecordUmaResponseParseResult(arc::mojom::AppReinstallState result)170 void RecordUmaResponseParseResult(arc::mojom::AppReinstallState result) {
171   UMA_HISTOGRAM_ENUMERATION("Apps.AppListRecommendedResponse", result);
172 }
173 
174 // Limits icon size to be downloaded with FIFE. The input |icon_dimension| is in
175 // dip and the FIFE requires pixel value. Thus, we need to multiply
176 // |icon_dimension| with the maximum device scale factor to avoid potential
177 // issues.
LimitIconSizeWithFife(const std::string & icon_url,int icon_dimension)178 std::string LimitIconSizeWithFife(const std::string& icon_url,
179                                   int icon_dimension) {
180   DCHECK_GT(icon_dimension, 0);
181   // Maximum device scale factor (DSF).
182   static const int kMaxDeviceScaleFactor = 3;
183   // We append a suffix to icon url
184   return base::StrCat(
185       {icon_url, "=s",
186        base::NumberToString(icon_dimension * kMaxDeviceScaleFactor)});
187 }
188 
189 }  // namespace
190 
191 namespace app_list {
192 
193 // static
194 constexpr char ArcAppReinstallSearchProvider::kInstallTime[];
195 
196 // static
197 constexpr char ArcAppReinstallSearchProvider::kAppState[];
198 
199 // static
200 constexpr char ArcAppReinstallSearchProvider::kImpressionCount[];
201 
202 // static
203 constexpr char ArcAppReinstallSearchProvider::kImpressionTime[];
204 
205 // static
206 constexpr char ArcAppReinstallSearchProvider::kUninstallTime[];
207 
208 // static
209 constexpr char ArcAppReinstallSearchProvider::kOpenTime[];
210 
211 // static
212 constexpr char ArcAppReinstallSearchProvider::kInstallStartTime[];
213 
ArcAppReinstallSearchProvider(Profile * profile,unsigned int max_result_count)214 ArcAppReinstallSearchProvider::ArcAppReinstallSearchProvider(
215     Profile* profile,
216     unsigned int max_result_count)
217     : profile_(profile),
218       max_result_count_(max_result_count),
219       icon_dimension_(ash::AppListConfig::instance().GetPreferredIconDimension(
220           ash::SearchResultDisplayType::kTile)),
221       app_fetch_timer_(std::make_unique<base::RepeatingTimer>()) {
222   DCHECK(profile_);
223   ArcAppListPrefs::Get(profile_)->AddObserver(this);
224   MaybeUpdateFetching();
225 }
226 
~ArcAppReinstallSearchProvider()227 ArcAppReinstallSearchProvider::~ArcAppReinstallSearchProvider() {
228   ArcAppListPrefs::Get(profile_)->RemoveObserver(this);
229 }
230 
BeginRepeatingFetch()231 void ArcAppReinstallSearchProvider::BeginRepeatingFetch() {
232   // If already running, do not re-start.
233   if (app_fetch_timer_->IsRunning())
234     return;
235 
236   app_fetch_timer_->Start(FROM_HERE, kRefresh, this,
237                           &ArcAppReinstallSearchProvider::StartFetch);
238   StartFetch();
239 }
240 
StopRepeatingFetch()241 void ArcAppReinstallSearchProvider::StopRepeatingFetch() {
242   weak_ptr_factory_.InvalidateWeakPtrs();
243   app_fetch_timer_->AbandonAndStop();
244   loaded_value_.clear();
245   icon_urls_.clear();
246   loading_icon_urls_.clear();
247   UpdateResults();
248 }
249 
ResultType()250 ash::AppListSearchResultType ArcAppReinstallSearchProvider::ResultType() {
251   return ash::AppListSearchResultType::kPlayStoreReinstallApp;
252 }
253 
Start(const base::string16 & query)254 void ArcAppReinstallSearchProvider::Start(const base::string16& query) {
255   query_is_empty_ = query.empty();
256   if (!query_is_empty_) {
257     ClearResults();
258     return;
259   }
260 
261   // Always check if suggested content is enabled before searching for
262   // reinstall recommendations.
263   bool should_show_arc_app_reinstall_result = true;
264   PrefService* pref_service = profile_->GetPrefs();
265   if (pref_service &&
266       !pref_service->GetBoolean(chromeos::prefs::kSuggestedContentEnabled))
267     should_show_arc_app_reinstall_result = false;
268   if (!base::FeatureList::IsEnabled(
269           chromeos::features::kSuggestedContentToggle))
270     should_show_arc_app_reinstall_result = false;
271 
272   if (!should_show_arc_app_reinstall_result) {
273     ClearResults();
274     return;
275   }
276 
277   UpdateResults();
278 }
279 
StartFetch()280 void ArcAppReinstallSearchProvider::StartFetch() {
281   arc::mojom::AppInstance* app_instance =
282       arc::ArcServiceManager::Get()
283           ? ARC_GET_INSTANCE_FOR_METHOD(
284                 arc::ArcServiceManager::Get()->arc_bridge_service()->app(),
285                 GetAppReinstallCandidates)
286           : nullptr;
287   if (app_instance == nullptr)
288     return;
289 
290   if (profile_->GetPrefs()->IsManagedPreference(
291           prefs::kAppReinstallRecommendationEnabled) &&
292       !profile_->GetPrefs()->GetBoolean(
293           prefs::kAppReinstallRecommendationEnabled)) {
294     // This user profile is managed, and the app reinstall recommendation is
295     // switched off. This is updated dynamically, usually, so we need to update
296     // the loaded value and return.
297     OnGetAppReinstallCandidates(base::Time::UnixEpoch(),
298                                 arc::mojom::AppReinstallState::REQUEST_SUCCESS,
299                                 {});
300     return;
301   }
302 
303   app_instance->GetAppReinstallCandidates(base::BindOnce(
304       &ArcAppReinstallSearchProvider::OnGetAppReinstallCandidates,
305       weak_ptr_factory_.GetWeakPtr(), base::Time::Now()));
306 }
307 
OnGetAppReinstallCandidates(base::Time start_time,arc::mojom::AppReinstallState state,std::vector<arc::mojom::AppReinstallCandidatePtr> results)308 void ArcAppReinstallSearchProvider::OnGetAppReinstallCandidates(
309     base::Time start_time,
310     arc::mojom::AppReinstallState state,
311     std::vector<arc::mojom::AppReinstallCandidatePtr> results) {
312   RecordUmaResponseParseResult(state);
313 
314   // fake result insertion is indicated by unix epoch start time.
315   if (start_time != base::Time::UnixEpoch()) {
316     UMA_HISTOGRAM_TIMES(kAppListLatency, base::Time::Now() - start_time);
317     UMA_HISTOGRAM_COUNTS_100(kAppListCounts, results.size());
318   }
319   if (state != arc::mojom::AppReinstallState::REQUEST_SUCCESS) {
320     LOG(ERROR) << "Failed to get reinstall candidates: " << state;
321     return;
322   }
323   loaded_value_.clear();
324 
325   for (const auto& candidate : results) {
326     // only keep candidates with icons.
327     if (candidate->icon_url != base::nullopt) {
328       loaded_value_.push_back(candidate.Clone());
329     }
330   }
331 
332   // Update the dictionary to reset old impression counts.
333   const base::TimeDelta now = base::Time::Now().ToDeltaSinceWindowsEpoch();
334   // Remove stale impressions from state.
335   std::unordered_set<std::string> package_names;
336   GetKnownPackageNames(profile_, &package_names);
337   for (const std::string& package_name : package_names) {
338     base::TimeDelta latest_impression;
339     if (!GetStateTime(profile_, package_name, kImpressionTime,
340                       &latest_impression)) {
341       continue;
342     }
343     if (now - latest_impression >
344         base::TimeDelta::FromHours(kResetImpressionGrace.Get())) {
345       SetStateInt64(profile_, package_name, kImpressionCount, 0);
346       UpdateStateRemoveKey(profile_, package_name, kImpressionTime);
347     }
348   }
349 
350   UpdateResults();
351 }
352 
UpdateResults()353 void ArcAppReinstallSearchProvider::UpdateResults() {
354   // We clear results if there are none from the server, or the user has entered
355   // a non-zero query.
356   if (loaded_value_.empty() || !query_is_empty_) {
357     if (loaded_value_.empty())
358       icon_urls_.clear();
359     ClearResults();
360     return;
361   }
362 
363   ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
364   DCHECK(prefs);
365 
366   std::vector<std::unique_ptr<ChromeSearchResult>> new_results;
367   std::unordered_set<std::string> used_icon_urls;
368 
369   // Lock over the whole list.
370   if (ShouldShowAnything()) {
371     for (size_t i = 0, processed = 0;
372          i < loaded_value_.size() && processed < max_result_count_; ++i) {
373       // Any packages that are installing or installed and not in sync with the
374       // server are removed with IsUnknownPackage.
375       if (!prefs->IsUnknownPackage(loaded_value_[i]->package_name))
376         continue;
377       // Should we filter this ?
378       if (!ShouldShowPackage(loaded_value_[i]->package_name)) {
379         continue;
380       }
381       processed++;
382 
383       // From this point, we believe that this item should be in the result
384       // list. We try to find this icon, and if it is not available, we load it.
385       const std::string& icon_url = loaded_value_[i]->icon_url.value();
386       // All the icons we are showing.
387       used_icon_urls.insert(icon_url);
388 
389       const auto icon_it = icon_urls_.find(icon_url);
390       const auto loading_icon_it = loading_icon_urls_.find(icon_url);
391       if (icon_it == icon_urls_.end() &&
392           loading_icon_it == loading_icon_urls_.end()) {
393         // this icon is not loaded, nor is it in the loading set. Add it.
394         loading_icon_urls_[icon_url] = gfx::ImageSkia(
395             std::make_unique<UrlIconSource>(
396                 base::BindRepeating(
397                     &ArcAppReinstallSearchProvider::OnIconLoaded,
398                     weak_ptr_factory_.GetWeakPtr(), icon_url),
399                 profile_,
400                 GURL(LimitIconSizeWithFife(icon_url, icon_dimension_)),
401                 icon_dimension_, IDR_APP_DEFAULT_ICON),
402             gfx::Size(icon_dimension_, icon_dimension_));
403         loading_icon_urls_[icon_url].GetRepresentation(1.0f);
404       } else if (icon_it != icon_urls_.end()) {
405         // Icon is loaded, add it to the results.
406         new_results.emplace_back(std::make_unique<ArcAppReinstallAppResult>(
407             loaded_value_[i], icon_it->second, this));
408       }
409     }
410   }
411 
412   // Remove unused icons.
413   std::unordered_set<std::string> unused_icon_urls;
414   for (const auto& it : icon_urls_) {
415     if (used_icon_urls.find(it.first) == used_icon_urls.end()) {
416       // This url is used, remove.
417       unused_icon_urls.insert(it.first);
418     }
419   }
420 
421   for (const std::string& url : unused_icon_urls) {
422     icon_urls_.erase(url);
423     loading_icon_urls_.erase(url);
424   }
425 
426   // Now we are ready with new_results. do we actually need to replace things on
427   // screen?
428   if (!ResultsIdentical(results(), new_results)) {
429     SwapResults(&new_results);
430   }
431 }
432 
MaybeUpdateFetching()433 void ArcAppReinstallSearchProvider::MaybeUpdateFetching() {
434   ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_);
435   std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
436       prefs->GetApp(arc::kPlayStoreAppId);
437   if (app_info && app_info->ready)
438     BeginRepeatingFetch();
439   else
440     StopRepeatingFetch();
441 }
442 
OnAppRegistered(const std::string & app_id,const ArcAppListPrefs::AppInfo & app_info)443 void ArcAppReinstallSearchProvider::OnAppRegistered(
444     const std::string& app_id,
445     const ArcAppListPrefs::AppInfo& app_info) {
446   OnAppStatesChanged(app_id, app_info);
447 }
448 
OnAppStatesChanged(const std::string & app_id,const ArcAppListPrefs::AppInfo & app_info)449 void ArcAppReinstallSearchProvider::OnAppStatesChanged(
450     const std::string& app_id,
451     const ArcAppListPrefs::AppInfo& app_info) {
452   if (app_id == arc::kPlayStoreAppId)
453     MaybeUpdateFetching();
454 }
455 
OnAppRemoved(const std::string & app_id)456 void ArcAppReinstallSearchProvider::OnAppRemoved(const std::string& app_id) {
457   if (app_id == arc::kPlayStoreAppId)
458     MaybeUpdateFetching();
459 }
460 
OnInstallationStarted(const std::string & package_name)461 void ArcAppReinstallSearchProvider::OnInstallationStarted(
462     const std::string& package_name) {
463   UpdateStateTime(profile_, package_name, kInstallStartTime);
464   UpdateResults();
465 }
466 
OnInstallationFinished(const std::string & package_name,bool success)467 void ArcAppReinstallSearchProvider::OnInstallationFinished(
468     const std::string& package_name,
469     bool success) {
470   if (success) {
471     UpdateStateTime(profile_, package_name, kInstallTime);
472   }
473   UpdateResults();
474 }
475 
OnPackageInstalled(const arc::mojom::ArcPackageInfo & package_info)476 void ArcAppReinstallSearchProvider::OnPackageInstalled(
477     const arc::mojom::ArcPackageInfo& package_info) {
478   UpdateResults();
479 }
480 
OnPackageRemoved(const std::string & package_name,bool uninstalled)481 void ArcAppReinstallSearchProvider::OnPackageRemoved(
482     const std::string& package_name,
483     bool uninstalled) {
484   // If we uninstalled this, update the timestamp before updating results.
485   // Otherwise, it's just an app no longer available.
486   if (uninstalled) {
487     UpdateStateTime(profile_, package_name, kUninstallTime);
488   }
489   UpdateResults();
490 }
491 
SetTimerForTesting(std::unique_ptr<base::RepeatingTimer> timer)492 void ArcAppReinstallSearchProvider::SetTimerForTesting(
493     std::unique_ptr<base::RepeatingTimer> timer) {
494   app_fetch_timer_ = std::move(timer);
495 }
496 
OnOpened(const std::string & package_name)497 void ArcAppReinstallSearchProvider::OnOpened(const std::string& package_name) {
498   UpdateStateTime(profile_, package_name, kOpenTime);
499   int64_t impression_count;
500   if (GetStateInt64(profile_, package_name, kImpressionCount,
501                     &impression_count)) {
502     UMA_HISTOGRAM_COUNTS_100(kAppListImpressionsBeforeOpen, impression_count);
503   }
504   UpdateResults();
505 }
506 
OnVisibilityChanged(const std::string & package_name,bool visibility)507 void ArcAppReinstallSearchProvider::OnVisibilityChanged(
508     const std::string& package_name,
509     bool visibility) {
510   if (!visibility) {
511     // do not update state when showing, update when we hide.
512     return;
513   }
514 
515   // If never shown before, or shown more than |kNewImpressionTime| ago,
516   // increment the count here.
517   const base::TimeDelta now = base::Time::Now().ToDeltaSinceWindowsEpoch();
518   base::TimeDelta latest_impression;
519   int64_t impression_count;
520   if (!GetStateInt64(profile_, package_name, kImpressionCount,
521                      &impression_count)) {
522     impression_count = 0;
523   }
524   UMA_HISTOGRAM_COUNTS_100("Arc.AppListRecommendedImp.AllImpression", 1);
525   // Get impression count and time. If neither is set, set them.
526   // If they're set, update if appropriate.
527   if (!GetStateTime(profile_, package_name, kImpressionTime,
528                     &latest_impression) ||
529       impression_count == 0 ||
530       (now - latest_impression >
531        base::TimeDelta::FromSeconds(kNewImpressionTime.Get()))) {
532     UpdateStateTime(profile_, package_name, kImpressionTime);
533     SetStateInt64(profile_, package_name, kImpressionCount,
534                   impression_count + 1);
535     UMA_HISTOGRAM_COUNTS_100("Arc.AppListRecommendedImp.CountedImpression", 1);
536     UpdateResults();
537   }
538 }
539 
RegisterProfilePrefs(PrefRegistrySimple * registry)540 void ArcAppReinstallSearchProvider::RegisterProfilePrefs(
541     PrefRegistrySimple* registry) {
542   registry->RegisterDictionaryPref(kAppState);
543 }
544 
545 // For icon load callback, in OnGetAppReinstallCandidates
OnIconLoaded(const std::string & icon_url)546 void ArcAppReinstallSearchProvider::OnIconLoaded(const std::string& icon_url) {
547   auto skia_ptr = loading_icon_urls_.find(icon_url);
548   DCHECK(skia_ptr != loading_icon_urls_.end());
549   if (skia_ptr == loading_icon_urls_.end()) {
550     return;
551   }
552   const std::vector<gfx::ImageSkiaRep> image_reps =
553       skia_ptr->second.image_reps();
554   for (const gfx::ImageSkiaRep& rep : image_reps)
555     skia_ptr->second.RemoveRepresentation(rep.scale());
556   DCHECK_LE(skia_ptr->second.width(), icon_dimension_);
557 
558   // ImageSkia is now ready to serve, move to the done list and update the
559   // screen.
560   icon_urls_[icon_url] = skia_ptr->second;
561   loading_icon_urls_.erase(icon_url);
562   UpdateResults();
563 }
564 
ShouldShowPackage(const std::string & package_id) const565 bool ArcAppReinstallSearchProvider::ShouldShowPackage(
566     const std::string& package_id) const {
567   base::TimeDelta timestamp;
568   const base::TimeDelta now = base::Time::Now().ToDeltaSinceWindowsEpoch();
569   if (GetStateTime(profile_, package_id, kUninstallTime, &timestamp)) {
570     const auto delta = now - timestamp;
571     if (delta < base::TimeDelta::FromHours(kUninstallGrace.Get())) {
572       // We uninstalled this recently, don't show.
573       return false;
574     }
575   }
576   if (GetStateTime(profile_, package_id, kInstallStartTime, &timestamp)) {
577     const auto delta = now - timestamp;
578     if (delta < base::TimeDelta::FromHours(kInstallStartGrace.Get())) {
579       // We started install on this recently, don't show.
580       return false;
581     }
582   }
583   int64_t value;
584   if (GetStateInt64(profile_, package_id, kImpressionCount, &value)) {
585     if (value > kImpressionLimit.Get()) {
586       // Shown too many times, ignore.
587       return false;
588     }
589   }
590   return true;
591 }
592 
ShouldShowAnything() const593 bool ArcAppReinstallSearchProvider::ShouldShowAnything() const {
594   if (!kInteractionGrace.Get()) {
595     return true;
596   }
597   const base::TimeDelta grace_period =
598       base::TimeDelta::FromHours(kInteractionGrace.Get());
599   const base::TimeDelta now = base::Time::Now().ToDeltaSinceWindowsEpoch();
600   std::unordered_set<std::string> package_names;
601   GetKnownPackageNames(profile_, &package_names);
602 
603   for (const std::string& package_name : package_names) {
604     base::TimeDelta install_time;
605     if (GetStateTime(profile_, package_name, kInstallTime, &install_time)) {
606       if (now - install_time < grace_period) {
607         // installed in grace, do not show anything.
608         return false;
609       }
610     }
611 
612     base::TimeDelta result_open;
613     if (GetStateTime(profile_, package_name, kOpenTime, &result_open)) {
614       if (now - result_open < grace_period) {
615         // Shown in grace, do not show anything.
616         return false;
617       }
618     }
619 
620     int64_t impression_count;
621     if (GetStateInt64(profile_, package_name, kImpressionCount,
622                       &impression_count)) {
623       if (impression_count >= kImpressionLimit.Get()) {
624         base::TimeDelta impression_time;
625         if (GetStateTime(profile_, package_name, kImpressionTime,
626                          &impression_time)) {
627           if (now - impression_time < grace_period) {
628             // We showed a too-many-shown result recently, within grace, don't
629             // show anything.
630             return false;
631           }
632         }
633       }
634     }
635   }
636   return true;
637 }
638 
ResultsIdentical(const std::vector<std::unique_ptr<ChromeSearchResult>> & old_results,const std::vector<std::unique_ptr<ChromeSearchResult>> & new_results)639 bool ArcAppReinstallSearchProvider::ResultsIdentical(
640     const std::vector<std::unique_ptr<ChromeSearchResult>>& old_results,
641     const std::vector<std::unique_ptr<ChromeSearchResult>>& new_results) {
642   if (old_results.size() != new_results.size()) {
643     return false;
644   }
645   for (size_t i = 0; i < old_results.size(); ++i) {
646     const ChromeSearchResult& old_result = *(old_results[i]);
647     const ChromeSearchResult& new_result = *(new_results[i]);
648     if (!old_result.icon().BackedBySameObjectAs(new_result.icon())) {
649       return false;
650     }
651     if (old_result.title() != new_result.title()) {
652       return false;
653     }
654     if (old_result.id() != new_result.id()) {
655       return false;
656     }
657     if (old_result.relevance() != new_result.relevance()) {
658       return false;
659     }
660     if (old_result.rating() != new_result.rating()) {
661       return false;
662     }
663   }
664   return true;
665 }
666 
667 }  // namespace app_list
668