1// Copyright 2014 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/notifications/notification_conversion_helper.h"
6
7#include "base/logging.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/common/extensions/api/notification_provider.h"
11#include "chrome/common/extensions/api/notifications/notification_style.h"
12#include "ui/gfx/image/image_skia.h"
13#include "ui/gfx/image/image_skia_rep.h"
14#include "ui/gfx/skia_util.h"
15
16void NotificationConversionHelper::NotificationToNotificationOptions(
17    const Notification& notification,
18    extensions::api::notifications::NotificationOptions* options) {
19  // Extract required fields: type, title, message, and icon.
20  std::string type = MapTypeToString(notification.type());
21  options->type = extensions::api::notifications::ParseTemplateType(type);
22
23  if (!notification.icon().IsEmpty()) {
24    scoped_ptr<extensions::api::notifications::NotificationBitmap> icon(
25        new extensions::api::notifications::NotificationBitmap());
26    GfxImageToNotificationBitmap(&notification.icon(), icon.get());
27    options->icon_bitmap = icon.Pass();
28  }
29
30  options->title.reset(
31      new std::string(base::UTF16ToUTF8(notification.title())));
32  options->message.reset(
33      new std::string(base::UTF16ToUTF8(notification.message())));
34
35  // Handle optional data provided.
36  const message_center::RichNotificationData* rich_data =
37      &notification.rich_notification_data();
38
39  if (!rich_data->small_image.IsEmpty()) {
40    scoped_ptr<extensions::api::notifications::NotificationBitmap> icon_mask(
41        new extensions::api::notifications::NotificationBitmap());
42    GfxImageToNotificationBitmap(&rich_data->small_image, icon_mask.get());
43    options->app_icon_mask_bitmap = icon_mask.Pass();
44  }
45
46  options->priority.reset(new int(rich_data->priority));
47
48  options->is_clickable.reset(new bool(rich_data->clickable));
49
50  options->event_time.reset(new double(rich_data->timestamp.ToDoubleT()));
51
52  if (!rich_data->context_message.empty())
53    options->context_message.reset(
54        new std::string(base::UTF16ToUTF8(rich_data->context_message)));
55
56  if (!rich_data->buttons.empty()) {
57    scoped_ptr<std::vector<
58        linked_ptr<extensions::api::notifications::NotificationButton> > >
59        button_list(new std::vector<
60            linked_ptr<extensions::api::notifications::NotificationButton> >);
61    for (size_t i = 0; i < rich_data->buttons.size(); i++) {
62      linked_ptr<extensions::api::notifications::NotificationButton> button(
63          new extensions::api::notifications::NotificationButton);
64      button->title = base::UTF16ToUTF8(rich_data->buttons[i].title);
65
66      if (!rich_data->buttons[i].icon.IsEmpty()) {
67        scoped_ptr<extensions::api::notifications::NotificationBitmap> icon(
68            new extensions::api::notifications::NotificationBitmap());
69        GfxImageToNotificationBitmap(&rich_data->buttons[i].icon, icon.get());
70        button->icon_bitmap = icon.Pass();
71      }
72      button_list->push_back(button);
73    }
74    options->buttons = button_list.Pass();
75  }
76
77  // Only image type notifications should have images.
78  if (type == "image" && !rich_data->image.IsEmpty()) {
79    scoped_ptr<extensions::api::notifications::NotificationBitmap> image(
80        new extensions::api::notifications::NotificationBitmap());
81    GfxImageToNotificationBitmap(&notification.image(), image.get());
82    options->image_bitmap = image.Pass();
83  } else if (type != "image" && !rich_data->image.IsEmpty()) {
84    DVLOG(1) << "Only image type notifications should have images.";
85  }
86
87  // Only progress type notifications should have progress bars.
88  if (type == "progress")
89    options->progress.reset(new int(rich_data->progress));
90  else if (rich_data->progress != 0)
91    DVLOG(1) << "Only progress type notifications should have progress.";
92
93  // Only list type notifications should have lists.
94  if (type == "list" && !rich_data->items.empty()) {
95    scoped_ptr<std::vector<
96        linked_ptr<extensions::api::notifications::NotificationItem> > >
97        list(new std::vector<
98            linked_ptr<extensions::api::notifications::NotificationItem> >);
99    for (size_t j = 0; j < rich_data->items.size(); j++) {
100      linked_ptr<extensions::api::notifications::NotificationItem> item(
101          new extensions::api::notifications::NotificationItem);
102      item->title = base::UTF16ToUTF8(rich_data->items[j].title);
103      item->message = base::UTF16ToUTF8(rich_data->items[j].message);
104      list->push_back(item);
105    }
106    options->items = list.Pass();
107  } else if (type != "list" && !rich_data->items.empty()) {
108    DVLOG(1) << "Only list type notifications should have lists.";
109  }
110}
111
112void NotificationConversionHelper::GfxImageToNotificationBitmap(
113    const gfx::Image* gfx_image,
114    extensions::api::notifications::NotificationBitmap* notification_bitmap) {
115  SkBitmap sk_bitmap = gfx_image->AsBitmap();
116  sk_bitmap.lockPixels();
117
118  notification_bitmap->width = sk_bitmap.width();
119  notification_bitmap->height = sk_bitmap.height();
120  int pixel_count = sk_bitmap.width() * sk_bitmap.height();
121  const int BYTES_PER_PIXEL = 4;
122
123  uint32_t* bitmap_pixels = sk_bitmap.getAddr32(0, 0);
124  const unsigned char* bitmap =
125      reinterpret_cast<const unsigned char*>(bitmap_pixels);
126  scoped_ptr<unsigned char[]> rgba_bitmap_data(
127      new unsigned char[pixel_count * BYTES_PER_PIXEL]);
128
129  gfx::ConvertSkiaToRGBA(bitmap, pixel_count, rgba_bitmap_data.get());
130  sk_bitmap.unlockPixels();
131
132  notification_bitmap->data.reset(new std::string(
133      rgba_bitmap_data.get(),
134      (rgba_bitmap_data.get() + pixel_count * BYTES_PER_PIXEL)));
135  return;
136}
137
138bool NotificationConversionHelper::NotificationBitmapToGfxImage(
139    float max_scale,
140    const gfx::Size& target_size_dips,
141    extensions::api::notifications::NotificationBitmap* notification_bitmap,
142    gfx::Image* return_image) {
143  if (!notification_bitmap)
144    return false;
145
146  const int max_device_pixel_width = target_size_dips.width() * max_scale;
147  const int max_device_pixel_height = target_size_dips.height() * max_scale;
148
149  const int BYTES_PER_PIXEL = 4;
150
151  const int width = notification_bitmap->width;
152  const int height = notification_bitmap->height;
153
154  if (width < 0 || height < 0 || width > max_device_pixel_width ||
155      height > max_device_pixel_height)
156    return false;
157
158  // Ensure we have rgba data.
159  std::string* rgba_data = notification_bitmap->data.get();
160  if (!rgba_data)
161    return false;
162
163  const size_t rgba_data_length = rgba_data->length();
164  const size_t rgba_area = width * height;
165
166  if (rgba_data_length != rgba_area * BYTES_PER_PIXEL)
167    return false;
168
169  SkBitmap bitmap;
170  // Allocate the actual backing store with the sanitized dimensions.
171  if (!bitmap.tryAllocN32Pixels(width, height))
172    return false;
173
174  // Ensure that our bitmap and our data now refer to the same number of pixels.
175  if (rgba_data_length != bitmap.getSafeSize())
176    return false;
177
178  uint32_t* pixels = bitmap.getAddr32(0, 0);
179  const char* c_rgba_data = rgba_data->data();
180
181  for (size_t t = 0; t < rgba_area; ++t) {
182    // |c_rgba_data| is RGBA, pixels is ARGB.
183    size_t rgba_index = t * BYTES_PER_PIXEL;
184    pixels[t] =
185        SkPreMultiplyColor(((c_rgba_data[rgba_index + 3] & 0xFF) << 24) |
186                           ((c_rgba_data[rgba_index + 0] & 0xFF) << 16) |
187                           ((c_rgba_data[rgba_index + 1] & 0xFF) << 8) |
188                           ((c_rgba_data[rgba_index + 2] & 0xFF) << 0));
189  }
190
191  // TODO(dewittj): Handle HiDPI images with more than one scale factor
192  // representation.
193  gfx::ImageSkia skia(gfx::ImageSkiaRep(bitmap, 1.0f));
194  *return_image = gfx::Image(skia);
195  return true;
196}
197
198std::string NotificationConversionHelper::MapTypeToString(
199    message_center::NotificationType type) {
200  switch (type) {
201    case message_center::NOTIFICATION_TYPE_BASE_FORMAT:
202      return "basic";
203    case message_center::NOTIFICATION_TYPE_IMAGE:
204      return "image";
205    case message_center::NOTIFICATION_TYPE_MULTIPLE:
206      return "list";
207    case message_center::NOTIFICATION_TYPE_PROGRESS:
208      return "progress";
209    default:
210      NOTREACHED();
211      return "";
212  }
213}
214