1// Copyright (c) 2011 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/chromeos/offline/offline_load_page.h"
6
7#include "base/i18n/rtl.h"
8#include "base/metrics/histogram.h"
9#include "base/string_piece.h"
10#include "base/stringprintf.h"
11#include "base/utf_string_conversions.h"
12#include "base/values.h"
13#include "chrome/browser/chromeos/cros/cros_library.h"
14#include "chrome/browser/chromeos/cros/network_library.h"
15#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/tab_contents/tab_util.h"
18#include "chrome/browser/ui/browser.h"
19#include "chrome/browser/ui/browser_list.h"
20#include "chrome/common/extensions/extension.h"
21#include "chrome/common/jstemplate_builder.h"
22#include "chrome/common/url_constants.h"
23#include "content/browser/browser_thread.h"
24#include "content/browser/tab_contents/navigation_controller.h"
25#include "content/browser/tab_contents/navigation_entry.h"
26#include "content/browser/tab_contents/tab_contents.h"
27#include "content/common/notification_service.h"
28#include "content/common/notification_type.h"
29#include "grit/browser_resources.h"
30#include "grit/generated_resources.h"
31#include "ui/base/l10n/l10n_util.h"
32#include "ui/base/resource/resource_bundle.h"
33
34namespace {
35
36// Maximum time to show a blank page.
37const int kMaxBlankPeriod = 3000;
38
39// A utility function to set the dictionary's value given by |resource_id|.
40void SetString(DictionaryValue* strings, const char* name, int resource_id) {
41  strings->SetString(name, l10n_util::GetStringUTF16(resource_id));
42}
43
44}  // namespace
45
46namespace chromeos {
47
48// static
49void OfflineLoadPage::Show(int process_host_id, int render_view_id,
50                           const GURL& url, Delegate* delegate) {
51  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
52  if (NetworkStateNotifier::is_connected()) {
53    // Check again in UI thread and proceed if it's connected.
54    delegate->OnBlockingPageComplete(true);
55  } else {
56    TabContents* tab_contents =
57        tab_util::GetTabContentsByID(process_host_id, render_view_id);
58    // There is a chance that the tab is closed after we decided to show
59    // offline and before we actually show the offline page.
60    if (!tab_contents)
61      return;
62    (new OfflineLoadPage(tab_contents, url, delegate))->Show();
63  }
64}
65
66OfflineLoadPage::OfflineLoadPage(TabContents* tab_contents,
67                                 const GURL& url,
68                                 Delegate* delegate)
69    : InterstitialPage(tab_contents, true, url),
70      delegate_(delegate),
71      proceeded_(false),
72      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
73      in_test_(false) {
74  registrar_.Add(this, NotificationType::NETWORK_STATE_CHANGED,
75                 NotificationService::AllSources());
76}
77
78std::string OfflineLoadPage::GetHTMLContents() {
79  DictionaryValue strings;
80  int64 time_to_wait = std::max(
81      static_cast<int64>(0),
82      kMaxBlankPeriod -
83      NetworkStateNotifier::GetOfflineDuration().InMilliseconds());
84  // Set the timeout to show the page.
85  strings.SetInteger("time_to_wait", static_cast<int>(time_to_wait));
86  // Button labels
87  SetString(&strings, "heading", IDS_OFFLINE_LOAD_HEADLINE);
88  SetString(&strings, "try_loading", IDS_OFFLINE_TRY_LOADING);
89  SetString(&strings, "network_settings", IDS_OFFLINE_NETWORK_SETTINGS);
90
91  // Activation
92  strings.SetBoolean("show_activation", ShowActivationMessage());
93
94  bool rtl = base::i18n::IsRTL();
95  strings.SetString("textdirection", rtl ? "rtl" : "ltr");
96
97  string16 failed_url(ASCIIToUTF16(url().spec()));
98  if (rtl)
99    base::i18n::WrapStringWithLTRFormatting(&failed_url);
100  strings.SetString("url", failed_url);
101
102  // The offline page for app has icons and slightly different message.
103  Profile* profile = tab()->profile();
104  DCHECK(profile);
105  const Extension* extension = NULL;
106  ExtensionService* extensions_service = profile->GetExtensionService();
107  // Extension service does not exist in test.
108  if (extensions_service)
109    extension = extensions_service->GetExtensionByWebExtent(url());
110
111  if (extension)
112    GetAppOfflineStrings(extension, failed_url, &strings);
113  else
114    GetNormalOfflineStrings(failed_url, &strings);
115
116  base::StringPiece html(
117      ResourceBundle::GetSharedInstance().GetRawDataResource(
118          IDR_OFFLINE_LOAD_HTML));
119  return jstemplate_builder::GetI18nTemplateHtml(html, &strings);
120}
121
122void OfflineLoadPage::GetAppOfflineStrings(
123    const Extension* app,
124    const string16& failed_url,
125    DictionaryValue* strings) const {
126  strings->SetString("title", app->name());
127
128  GURL icon_url = app->GetIconURL(Extension::EXTENSION_ICON_LARGE,
129                                  ExtensionIconSet::MATCH_EXACTLY);
130  if (icon_url.is_empty()) {
131    strings->SetString("display_icon", "none");
132    strings->SetString("icon", string16());
133  } else {
134    // Default icon is not accessible from interstitial page.
135    // TODO(oshima): Figure out how to use default icon.
136    strings->SetString("display_icon", "block");
137    strings->SetString("icon", icon_url.spec());
138  }
139
140  strings->SetString(
141      "msg",
142      l10n_util::GetStringFUTF16(IDS_APP_OFFLINE_LOAD_DESCRIPTION,
143                                 failed_url));
144}
145
146void OfflineLoadPage::GetNormalOfflineStrings(
147    const string16& failed_url, DictionaryValue* strings) const {
148  strings->SetString("title", tab()->GetTitle());
149
150  // No icon for normal web site.
151  strings->SetString("display_icon", "none");
152  strings->SetString("icon", string16());
153
154  strings->SetString(
155      "msg",
156      l10n_util::GetStringFUTF16(IDS_SITE_OFFLINE_LOAD_DESCRIPTION,
157                                 failed_url));
158}
159
160void OfflineLoadPage::CommandReceived(const std::string& cmd) {
161  std::string command(cmd);
162  // The Jasonified response has quotes, remove them.
163  if (command.length() > 1 && command[0] == '"') {
164    command = command.substr(1, command.length() - 2);
165  }
166  // TODO(oshima): record action for metrics.
167  if (command == "proceed") {
168    Proceed();
169  } else if (command == "dontproceed") {
170    DontProceed();
171  } else if (command == "open_network_settings") {
172    Browser* browser = BrowserList::GetLastActive();
173    DCHECK(browser);
174    browser->ShowOptionsTab(chrome::kInternetOptionsSubPage);
175  } else if (command == "open_activate_broadband") {
176    Browser* browser = BrowserList::GetLastActive();
177    DCHECK(browser);
178    browser->OpenMobilePlanTabAndActivate();
179  } else {
180    LOG(WARNING) << "Unknown command:" << cmd;
181  }
182}
183
184void OfflineLoadPage::Proceed() {
185  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
186  proceeded_ = true;
187  delegate_->OnBlockingPageComplete(true);
188  InterstitialPage::Proceed();
189}
190
191void OfflineLoadPage::DontProceed() {
192  // Inogre if it's already proceeded.
193  if (proceeded_)
194    return;
195  delegate_->OnBlockingPageComplete(false);
196  InterstitialPage::DontProceed();
197}
198
199void OfflineLoadPage::Observe(NotificationType type,
200                              const NotificationSource& source,
201                              const NotificationDetails& details) {
202  if (type.value == NotificationType::NETWORK_STATE_CHANGED) {
203    chromeos::NetworkStateDetails* state_details =
204        Details<chromeos::NetworkStateDetails>(details).ptr();
205    DVLOG(1) << "NetworkStateChanaged notification received: state="
206             << state_details->state();
207    if (state_details->state() ==
208        chromeos::NetworkStateDetails::CONNECTED) {
209      registrar_.Remove(this, NotificationType::NETWORK_STATE_CHANGED,
210                        NotificationService::AllSources());
211      Proceed();
212    }
213  } else {
214    InterstitialPage::Observe(type, source, details);
215  }
216}
217
218bool OfflineLoadPage::ShowActivationMessage() {
219  CrosLibrary* cros = CrosLibrary::Get();
220  if (!cros || !cros->GetNetworkLibrary()->cellular_available())
221    return false;
222
223  const CellularNetworkVector& cell_networks =
224      cros->GetNetworkLibrary()->cellular_networks();
225  for (size_t i = 0; i < cell_networks.size(); ++i) {
226    chromeos::ActivationState activation_state =
227        cell_networks[i]->activation_state();
228    if (activation_state == ACTIVATION_STATE_ACTIVATED)
229      return false;
230  }
231  return true;
232}
233
234}  // namespace chromeos
235