blob: 4133636d7493d17c4c18507d866cd6e063c41e56 [file] [log] [blame]
// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/ntp/notification_promo_whats_new.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <vector>
#include "base/json/json_reader.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_service.h"
#include "ios/chrome/browser/experimental_flags.h"
#include "ios/chrome/browser/notification_promo.h"
#include "ios/chrome/browser/pref_names.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
struct PromoStringToIdsMapEntry {
const char* promo_text_str;
int message_id;
};
// A mapping from a string to a l10n message id.
const PromoStringToIdsMapEntry kPromoStringToIdsMap[] = {
{"appRatingPromo", IDS_IOS_APP_RATING_PROMO_STRING},
{"moveToDockTip", IDS_IOS_MOVE_TO_DOCK_TIP},
};
// Returns a localized version of |promo_text| if it has an entry in the
// |kPromoStringToIdsMap|. If there is no entry, an empty string is returned.
std::string GetLocalizedPromoText(const std::string& promo_text) {
for (size_t i = 0; i < base::size(kPromoStringToIdsMap); ++i) {
if (kPromoStringToIdsMap[i].promo_text_str == promo_text)
return l10n_util::GetStringUTF8(kPromoStringToIdsMap[i].message_id);
}
return std::string();
}
} // namespace
NotificationPromoWhatsNew::NotificationPromoWhatsNew(PrefService* local_state)
: local_state_(local_state),
valid_(false),
notification_promo_(local_state_) {}
NotificationPromoWhatsNew::~NotificationPromoWhatsNew() {}
bool NotificationPromoWhatsNew::Init() {
notification_promo_.InitFromVariations();
// Force enable a particular promo if experimental flag is set.
experimental_flags::WhatsNewPromoStatus forceEnabled =
experimental_flags::GetWhatsNewPromoStatus();
if (forceEnabled != experimental_flags::WHATS_NEW_DEFAULT) {
switch (forceEnabled) {
case experimental_flags::WHATS_NEW_APP_RATING:
InjectFakePromo("1", "appRatingPromo", "chrome_command", "ratethisapp",
"", "RateThisAppPromo", "logo");
break;
case experimental_flags::WHATS_NEW_MOVE_TO_DOCK_TIP:
InjectFakePromo(
"2", "moveToDockTip", "url", "",
"https://support.google.com/chrome/?p=iphone_dock&ios=1",
"MoveToDockTipPromo", "logoWithRoundedRectangle");
break;
default:
NOTREACHED();
break;
}
}
notification_promo_.InitFromPrefs();
return InitFromNotificationPromo();
}
bool NotificationPromoWhatsNew::ClearAndInitFromJson(
const base::DictionaryValue& json) {
// This clears away old promos.
notification_promo_.MigrateUserPrefs(local_state_);
notification_promo_.InitFromJson(json);
return InitFromNotificationPromo();
}
bool NotificationPromoWhatsNew::CanShow() const {
if (!valid_ || !notification_promo_.CanShow()) {
return false;
}
// Check optional restrictions.
if (seconds_since_install_ > 0) {
// Do not show the promo if the app's installation did not occur more than
// |seconds_since_install_| seconds ago.
int64_t install_date = local_state_->GetInt64(metrics::prefs::kInstallDate);
const base::Time first_view_time =
base::Time::FromTimeT(install_date) +
base::TimeDelta::FromSeconds(seconds_since_install_);
if (first_view_time > base::Time::Now()) {
return false;
}
}
if (max_seconds_since_install_ > 0) {
// Do not show the promo if the app's installation occurred more than
// |max_seconds_since_install_| seconds ago.
int64_t install_date = local_state_->GetInt64(metrics::prefs::kInstallDate);
const base::Time last_view_time =
base::Time::FromTimeT(install_date) +
base::TimeDelta::FromSeconds(max_seconds_since_install_);
if (last_view_time < base::Time::Now()) {
return false;
}
}
return true;
}
void NotificationPromoWhatsNew::HandleViewed() {
// TODO(justincohen): metrics actions names should be inlined. Since
// metric_name_ is retrieved from a server, it's not possible to know at build
// time the values. We should investigate to find a solution. In the meantime,
// metrics will be reported hashed.
// http://crbug.com/437500
std::string metric = std::string("WhatsNewPromoViewed_") + metric_name_;
base::RecordAction(base::UserMetricsAction(metric.c_str()));
base::RecordAction(base::UserMetricsAction("NTPPromoShown"));
notification_promo_.HandleViewed();
}
void NotificationPromoWhatsNew::HandleClosed() {
// TODO(justincohen): metrics actions names should be inlined. Since
// metric_name_ is retrieved from a server, it's not possible to know at build
// time the values. We should investigate to find a solution. In the meantime,
// metrics will be reported hashed.
// http://crbug.com/437500
std::string metric = std::string("WhatsNewPromoClosed_") + metric_name_;
base::RecordAction(base::UserMetricsAction(metric.c_str()));
base::RecordAction(base::UserMetricsAction("NTPPromoClosed"));
notification_promo_.HandleClosed();
}
bool NotificationPromoWhatsNew::IsURLPromo() const {
return promo_type_ == "url";
}
bool NotificationPromoWhatsNew::IsChromeCommandPromo() const {
return promo_type_ == "chrome_command";
}
WhatsNewIcon NotificationPromoWhatsNew::ParseIconName(
const std::string& icon_name) {
if (icon_name == "logo") {
return WHATS_NEW_LOGO;
} else if (icon_name == "logoWithRoundedRectangle") {
return WHATS_NEW_LOGO_ROUNDED_RECTANGLE;
}
return WHATS_NEW_INFO;
}
bool NotificationPromoWhatsNew::InitFromNotificationPromo() {
valid_ = false;
promo_text_ = GetLocalizedPromoText(notification_promo_.promo_text());
if (promo_text_.empty())
return valid_;
notification_promo_.promo_payload()->GetString("metric_name", &metric_name_);
if (metric_name_.empty())
return valid_;
notification_promo_.promo_payload()->GetString("promo_type", &promo_type_);
if (IsURLPromo()) {
std::string url_text;
notification_promo_.promo_payload()->GetString("url", &url_text);
url_ = GURL(url_text);
if (url_.is_empty() || !url_.is_valid()) {
return valid_;
}
} else if (IsChromeCommandPromo()) {
notification_promo_.promo_payload()->GetString("command", &command_);
if ((command_ != "bookmark") && (command_ != "ratethisapp")) {
return valid_;
}
} else { // If |promo_type_| is not set to URL or Command, return early.
return valid_;
}
valid_ = true;
// Optional values don't need validation.
std::string icon_name;
notification_promo_.promo_payload()->GetString("icon", &icon_name);
icon_ = ParseIconName(icon_name);
seconds_since_install_ = 0;
notification_promo_.promo_payload()->GetInteger("seconds_since_install",
&seconds_since_install_);
max_seconds_since_install_ = 0;
notification_promo_.promo_payload()->GetInteger("max_seconds_since_install",
&max_seconds_since_install_);
return valid_;
}
void NotificationPromoWhatsNew::InjectFakePromo(const std::string& promo_id,
const std::string& promo_text,
const std::string& promo_type,
const std::string& command,
const std::string& url,
const std::string& metric_name,
const std::string& icon) {
// Build vector to fill in json string with given parameters.
std::vector<std::string> replacements;
replacements.push_back(promo_text);
replacements.push_back(promo_type);
replacements.push_back(metric_name);
replacements.push_back(command);
replacements.push_back(url);
replacements.push_back(icon);
replacements.push_back(promo_id);
std::string promo_json =
"{"
" \"start\":\"1 Jan 1999 0:26:06 GMT\","
" \"end\":\"1 Jan 2199 0:26:06 GMT\","
" \"promo_text\":\"$1\","
" \"max_views\":20,"
" \"payload\":"
" {"
" \"promo_type\":\"$2\","
" \"metric_name\":\"$3\","
" \"command\":\"$4\","
" \"url\":\"$5\","
" \"icon\":\"$6\""
" },"
" \"max_seconds\":259200,"
" \"promo_id\":$7"
"}";
std::string promo_json_filled_in =
base::ReplaceStringPlaceholders(promo_json, replacements, NULL);
std::unique_ptr<base::Value> value(
base::JSONReader::Read(promo_json_filled_in));
base::DictionaryValue* dict = NULL;
if (value->GetAsDictionary(&dict)) {
notification_promo_.InitFromJson(*dict);
}
}