1// Copyright (c) 2012 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 <string>
6
7#include "win8/metro_driver/stdafx.h"
8#include "win8/metro_driver/toast_notification_handler.h"
9
10#include "base/files/file_path.h"
11#include "base/logging.h"
12#include "base/path_service.h"
13#include "base/strings/utf_string_conversions.h"
14#include "chrome/installer/util/browser_distribution.h"
15#include "chrome/installer/util/install_util.h"
16#include "chrome/installer/util/shell_util.h"
17
18#include "win8/metro_driver/winrt_utils.h"
19
20typedef winfoundtn::ITypedEventHandler<
21    winui::Notifications::ToastNotification*, IInspectable*>
22    ToastActivationHandler;
23
24typedef winfoundtn::ITypedEventHandler<
25    winui::Notifications::ToastNotification*,
26    winui::Notifications::ToastDismissedEventArgs*> ToastDismissedHandler;
27
28namespace {
29
30// Helper function to return the text node root identified by the index passed
31// in.
32HRESULT GetTextNodeRoot(
33    unsigned int index,
34    winxml::Dom::IXmlDocument* xml_doc,
35    winxml::Dom::IXmlNode** node) {
36  DCHECK(xml_doc);
37  DCHECK(node);
38
39  mswr::ComPtr<winxml::Dom::IXmlElement> document_element;
40  HRESULT hr = xml_doc->get_DocumentElement(&document_element);
41  CheckHR(hr);
42
43  mswr::ComPtr<winxml::Dom::IXmlNodeList> elements;
44  mswrw::HString tag_name;
45  tag_name.Attach(MakeHString(L"text"));
46  hr = document_element->GetElementsByTagName(tag_name.Get(),
47                                              &elements);
48  CheckHR(hr);
49
50  unsigned int count = 0;
51  elements->get_Length(&count);
52
53  if (index > count) {
54    DVLOG(1) << "Invalid text node index passed in : " << index;
55    return E_FAIL;
56  }
57  hr = elements->Item(index, node);
58  CheckHR(hr);
59  return hr;
60}
61
62// Helper function to append a text element to the text section in the
63// XML document passed in.
64// The index parameter identifies which text node we append to.
65HRESULT CreateTextNode(winxml::Dom::IXmlDocument* xml_doc,
66                       int index,
67                       const base::string16& text_string) {
68  DCHECK(xml_doc);
69
70  mswr::ComPtr<winxml::Dom::IXmlElement> document_element;
71  HRESULT hr = xml_doc->get_DocumentElement(&document_element);
72  CheckHR(hr);
73
74  mswr::ComPtr<winxml::Dom::IXmlText> xml_text_node;
75  mswrw::HString data_hstring;
76  data_hstring.Attach(MakeHString(text_string.c_str()));
77  hr = xml_doc->CreateTextNode(data_hstring.Get(), &xml_text_node);
78  CheckHR(hr);
79
80  mswr::ComPtr<winxml::Dom::IXmlNode> created_node;
81  hr = xml_text_node.CopyTo(
82      winxml::Dom::IID_IXmlNode,
83      reinterpret_cast<void**>(created_node.GetAddressOf()));
84  CheckHR(hr);
85
86  mswr::ComPtr<winxml::Dom::IXmlNode> text_node_root;
87  hr = GetTextNodeRoot(index, xml_doc, &text_node_root);
88  CheckHR(hr);
89
90  mswr::ComPtr<winxml::Dom::IXmlNode> appended_node;
91  hr = text_node_root->AppendChild(created_node.Get(), &appended_node);
92  CheckHR(hr);
93  return hr;
94}
95
96}  // namespace
97
98ToastNotificationHandler::DesktopNotification::DesktopNotification(
99    const char* notification_origin,
100    const char* notification_icon,
101    const wchar_t* notification_title,
102    const wchar_t* notification_body,
103    const wchar_t* notification_display_source,
104    const char* notification_id,
105    base::win::MetroNotificationClickedHandler handler,
106    const wchar_t* handler_context)
107    : origin_url(notification_origin),
108      icon_url(notification_icon),
109      title(notification_title),
110      body(notification_body),
111      display_source(notification_display_source),
112      id(notification_id),
113      notification_handler(handler) {
114  if (handler_context)
115    notification_context = handler_context;
116}
117
118ToastNotificationHandler::DesktopNotification::DesktopNotification()
119    : notification_handler(NULL) {
120}
121
122ToastNotificationHandler::ToastNotificationHandler() {
123  DVLOG(1) << __FUNCTION__;
124}
125
126ToastNotificationHandler::~ToastNotificationHandler() {
127  DVLOG(1) << __FUNCTION__;
128
129  if (notifier_ && notification_)
130    CancelNotification();
131}
132
133void ToastNotificationHandler::DisplayNotification(
134    const DesktopNotification& notification) {
135  DVLOG(1) << __FUNCTION__;
136
137  DCHECK(notifier_.Get() == NULL);
138  DCHECK(notification_.Get() == NULL);
139
140  notification_info_ = notification;
141
142  mswr::ComPtr<winui::Notifications::IToastNotificationManagerStatics>
143      toast_manager;
144
145  HRESULT hr = winrt_utils::CreateActivationFactory(
146      RuntimeClass_Windows_UI_Notifications_ToastNotificationManager,
147      toast_manager.GetAddressOf());
148  CheckHR(hr);
149
150  mswr::ComPtr<winxml::Dom::IXmlDocument> toast_xml;
151  hr = toast_manager->GetTemplateContent(
152      winui::Notifications::ToastTemplateType_ToastText02,
153      &toast_xml);
154  CheckHR(hr);
155
156  if (!toast_xml)
157    return;
158
159  mswr::ComPtr<winxml::Dom::IXmlElement> document_element;
160  hr = toast_xml->get_DocumentElement(&document_element);
161  CheckHR(hr);
162
163  if (!document_element)
164    return;
165
166  hr = CreateTextNode(toast_xml.Get(), 0, notification.title);
167  CheckHR(hr);
168
169  hr = CreateTextNode(toast_xml.Get(), 1, notification.body);
170  CheckHR(hr);
171
172  mswrw::HString duration_attribute_name;
173  duration_attribute_name.Attach(MakeHString(L"duration"));
174  mswrw::HString duration_attribute_value;
175  duration_attribute_value.Attach(MakeHString(L"long"));
176
177  hr = document_element->SetAttribute(duration_attribute_name.Get(),
178                                      duration_attribute_value.Get());
179  CheckHR(hr);
180
181  // TODO(ananta)
182  // We should set the image and launch params attribute in the notification
183  // XNL as described here: http://msdn.microsoft.com/en-us/library/hh465448
184  // To set the image we may have to extract the image and specify it in the
185  // following url form. ms-appx:///images/foo.png
186  // The launch params as described don't get passed back to us via the
187  // winapp::Activation::ILaunchActivatedEventArgs argument. Needs to be
188  // investigated.
189  mswr::ComPtr<winui::Notifications::IToastNotificationFactory>
190      toast_notification_factory;
191  hr = winrt_utils::CreateActivationFactory(
192      RuntimeClass_Windows_UI_Notifications_ToastNotification,
193      toast_notification_factory.GetAddressOf());
194  CheckHR(hr);
195
196  hr = toast_notification_factory->CreateToastNotification(
197      toast_xml.Get(), &notification_);
198  CheckHR(hr);
199
200  base::FilePath chrome_path;
201  if (!PathService::Get(base::FILE_EXE, &chrome_path)) {
202    NOTREACHED() << "Failed to get chrome exe path";
203    return;
204  }
205
206  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
207  bool is_per_user_install = InstallUtil::IsPerUserInstall(
208      chrome_path.value().c_str());
209  base::string16 appid =
210      ShellUtil::GetBrowserModelId(dist, is_per_user_install);
211  DVLOG(1) << "Chrome Appid is " << appid.c_str();
212
213  mswrw::HString app_user_model_id;
214  app_user_model_id.Attach(MakeHString(appid));
215
216  hr = toast_manager->CreateToastNotifierWithId(app_user_model_id.Get(),
217                                                &notifier_);
218  CheckHR(hr);
219
220  hr = notification_->add_Activated(
221      mswr::Callback<ToastActivationHandler>(
222          this, &ToastNotificationHandler::OnActivate).Get(),
223      &activated_token_);
224  CheckHR(hr);
225
226  hr = notifier_->Show(notification_.Get());
227  CheckHR(hr);
228}
229
230void ToastNotificationHandler::CancelNotification() {
231  DVLOG(1) << __FUNCTION__;
232
233  DCHECK(notifier_);
234  DCHECK(notification_);
235
236  notifier_->Hide(notification_.Get());
237}
238
239HRESULT ToastNotificationHandler::OnActivate(
240    winui::Notifications::IToastNotification* notification,
241    IInspectable* inspectable) {
242  // TODO(ananta)
243  // We should pass back information from the notification like the source url
244  // etc to ChromeAppView which would enable it to ensure that the
245  // correct tab in chrome is activated.
246  DVLOG(1) << __FUNCTION__;
247
248  if (notification_info_.notification_handler) {
249    notification_info_.notification_handler(
250        notification_info_.notification_context.c_str());
251  }
252  return S_OK;
253}
254