1 // Copyright 2015 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.
5 #include "components/open_from_clipboard/clipboard_recent_content_ios.h"
7 #import <UIKit/UIKit.h>
9 #include "base/logging.h"
10 #include "base/macros.h"
11 #include "base/memory/singleton.h"
12 #include "base/metrics/user_metrics.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/sys_info.h"
16 #include "url/url_constants.h"
18 ClipboardRecentContent* ClipboardRecentContent::GetInstance() {
19 return ClipboardRecentContentIOS::GetInstance();
22 // Bridge that forwards pasteboard change notifications to its delegate.
23 @interface PasteboardNotificationListenerBridge : NSObject {
24 ClipboardRecentContentIOS* _delegate;
28 @implementation PasteboardNotificationListenerBridge
30 - (id)initWithDelegate:(ClipboardRecentContentIOS*)delegate {
35 [[NSNotificationCenter defaultCenter]
37 selector:@selector(pasteboardChangedNotification:)
38 name:UIPasteboardChangedNotification
39 object:[UIPasteboard generalPasteboard]];
40 [[NSNotificationCenter defaultCenter]
42 selector:@selector(pasteboardChangedNotification:)
43 name:UIApplicationDidBecomeActiveNotification
50 [[NSNotificationCenter defaultCenter]
52 name:UIPasteboardChangedNotification
53 object:[UIPasteboard generalPasteboard]];
54 [[NSNotificationCenter defaultCenter]
56 name:UIApplicationDidBecomeActiveNotification
57 object:[UIPasteboard generalPasteboard]];
61 - (void)pasteboardChangedNotification:(NSNotification*)notification {
62 _delegate->PasteboardChanged();
68 // Key used to store the pasteboard's current change count. If when resuming
69 // chrome the pasteboard's change count is different from the stored one, then
70 // it means that the pasteboard's content has changed.
71 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount";
72 // Key used to store the last date at which it was detected that the pasteboard
73 // changed. It is used to evaluate the age of the pasteboard's content.
74 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate";
75 NSTimeInterval kMaximumAgeOfClipboardInSeconds = 6 * 60 * 60;
76 // Schemes accepted by the ClipboardRecentContentIOS.
77 const char* kAuthorizedSchemes[] = {
85 ClipboardRecentContentIOS* ClipboardRecentContentIOS::GetInstance() {
86 return Singleton<ClipboardRecentContentIOS>::get();
89 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) const {
91 if (-[lastPasteboardChangeDate_ timeIntervalSinceNow] >
92 kMaximumAgeOfClipboardInSeconds) {
95 if (urlFromPasteboardCache_.is_valid()) {
96 *url = urlFromPasteboardCache_;
102 void ClipboardRecentContentIOS::PasteboardChanged() {
103 urlFromPasteboardCache_ = URLFromPasteboard();
104 if (!urlFromPasteboardCache_.is_empty()) {
106 base::UserMetricsAction("MobileOmniboxClipboardChanged"));
108 lastPasteboardChangeDate_.reset([[NSDate date] retain]);
109 lastPasteboardChangeCount_ = [UIPasteboard generalPasteboard].changeCount;
110 SaveToUserDefaults();
113 ClipboardRecentContentIOS::ClipboardRecentContentIOS()
114 : ClipboardRecentContent() {
115 urlFromPasteboardCache_ = URLFromPasteboard();
116 LoadFromUserDefaults();
117 // The pasteboard's changeCount is reset to zero when the device is restarted.
118 // This means that even if |changeCount| hasn't changed, the pasteboard
119 // content could have changed. In order to avoid missing pasteboard changes,
120 // the changeCount is reset if the device has restarted.
121 NSInteger changeCount = [UIPasteboard generalPasteboard].changeCount;
122 if (changeCount != lastPasteboardChangeCount_ ||
123 DeviceRestartedSincePasteboardChanged()) {
126 notificationBridge_.reset(
127 [[PasteboardNotificationListenerBridge alloc] initWithDelegate:this]);
130 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {
133 GURL ClipboardRecentContentIOS::URLFromPasteboard() {
134 const std::string clipboard =
135 base::SysNSStringToUTF8([[UIPasteboard generalPasteboard] string]);
136 GURL gurl = GURL(clipboard);
137 if (gurl.is_valid()) {
138 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) {
139 if (gurl.SchemeIs(kAuthorizedSchemes[i])) {
143 if (!application_scheme_.empty() &&
144 gurl.SchemeIs(application_scheme_.c_str())) {
148 return GURL::EmptyGURL();
151 void ClipboardRecentContentIOS::LoadFromUserDefaults() {
152 lastPasteboardChangeCount_ = [[NSUserDefaults standardUserDefaults]
153 integerForKey:kPasteboardChangeCountKey];
154 lastPasteboardChangeDate_.reset([[[NSUserDefaults standardUserDefaults]
155 objectForKey:kPasteboardChangeDateKey] retain]);
156 DCHECK(!lastPasteboardChangeDate_ ||
157 [lastPasteboardChangeDate_ isKindOfClass:[NSDate class]]);
160 void ClipboardRecentContentIOS::SaveToUserDefaults() {
161 [[NSUserDefaults standardUserDefaults] setInteger:lastPasteboardChangeCount_
162 forKey:kPasteboardChangeCountKey];
163 [[NSUserDefaults standardUserDefaults] setObject:lastPasteboardChangeDate_
164 forKey:kPasteboardChangeDateKey];
167 bool ClipboardRecentContentIOS::DeviceRestartedSincePasteboardChanged() {
168 int64 secondsSincePasteboardChange =
169 -static_cast<int64>([lastPasteboardChangeDate_ timeIntervalSinceNow]);
170 int64 secondsSinceLastDeviceRestart = base::SysInfo::Uptime() / 1000;
171 return secondsSincePasteboardChange > secondsSinceLastDeviceRestart;