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, ×tamp)) {
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, ×tamp)) {
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