Add method to suppress OFC suggestions.
[chromium-blink-merge.git] / components / open_from_clipboard / clipboard_recent_content_ios.mm
blob459e7903cd328f8ac78f526f4a5c9e65b1f97b87
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/ios/ios_util.h"
10 #include "base/logging.h"
11 #include "base/macros.h"
12 #include "base/memory/singleton.h"
13 #include "base/metrics/user_metrics.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/sys_info.h"
16 #include "url/gurl.h"
17 #include "url/url_constants.h"
19 ClipboardRecentContent* ClipboardRecentContent::GetInstance() {
20   return ClipboardRecentContentIOS::GetInstance();
23 // Bridge that forwards pasteboard change notifications to its delegate.
24 @interface PasteboardNotificationListenerBridge : NSObject {
25   ClipboardRecentContentIOS* _delegate;
27 @end
29 @implementation PasteboardNotificationListenerBridge
31 - (id)initWithDelegate:(ClipboardRecentContentIOS*)delegate {
32   DCHECK(delegate);
33   self = [super init];
34   if (self) {
35     _delegate = delegate;
36     [[NSNotificationCenter defaultCenter]
37         addObserver:self
38            selector:@selector(pasteboardChangedNotification:)
39                name:UIPasteboardChangedNotification
40              object:[UIPasteboard generalPasteboard]];
41     [[NSNotificationCenter defaultCenter]
42         addObserver:self
43            selector:@selector(pasteboardChangedNotification:)
44                name:UIApplicationDidBecomeActiveNotification
45              object:nil];
46   }
47   return self;
50 - (void)dealloc {
51   [[NSNotificationCenter defaultCenter]
52       removeObserver:self
53                 name:UIPasteboardChangedNotification
54               object:[UIPasteboard generalPasteboard]];
55   [[NSNotificationCenter defaultCenter]
56       removeObserver:self
57                 name:UIApplicationDidBecomeActiveNotification
58               object:[UIPasteboard generalPasteboard]];
59   [super dealloc];
62 - (void)pasteboardChangedNotification:(NSNotification*)notification {
63   _delegate->PasteboardChanged();
66 @end
68 namespace {
69 // Key used to store the pasteboard's current change count. If when resuming
70 // chrome the pasteboard's change count is different from the stored one, then
71 // it means that the pasteboard's content has changed.
72 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount";
73 // Key used to store the last date at which it was detected that the pasteboard
74 // changed. It is used to evaluate the age of the pasteboard's content.
75 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate";
76 // Key used to store the
77 NSString* kSuppressedPasteboardEntryCountKey = @"PasteboardSupressedEntryCount";
78 base::TimeDelta kMaximumAgeOfClipboard = base::TimeDelta::FromHours(3);
79 // Schemes accepted by the ClipboardRecentContentIOS.
80 const char* kAuthorizedSchemes[] = {
81     url::kHttpScheme,
82     url::kHttpsScheme,
83     url::kDataScheme,
84     url::kAboutScheme,
86 }  // namespace
88 ClipboardRecentContentIOS* ClipboardRecentContentIOS::GetInstance() {
89   return Singleton<ClipboardRecentContentIOS>::get();
92 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) const {
93   DCHECK(url);
94   if (GetClipboardContentAge() > kMaximumAgeOfClipboard ||
95       [UIPasteboard generalPasteboard].changeCount ==
96           suppressedPasteboardEntryCount_) {
97     return false;
98   }
100   if (urlFromPasteboardCache_.is_valid()) {
101     *url = urlFromPasteboardCache_;
102     return true;
103   }
104   return false;
107 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const {
108   return base::TimeDelta::FromSeconds(
109       static_cast<int64>(-[lastPasteboardChangeDate_ timeIntervalSinceNow]));
112 void ClipboardRecentContentIOS::SuppressClipboardContent() {
113   suppressedPasteboardEntryCount_ =
114       [UIPasteboard generalPasteboard].changeCount;
115   SaveToUserDefaults();
118 void ClipboardRecentContentIOS::PasteboardChanged() {
119   urlFromPasteboardCache_ = URLFromPasteboard();
120   if (!urlFromPasteboardCache_.is_empty()) {
121     base::RecordAction(
122         base::UserMetricsAction("MobileOmniboxClipboardChanged"));
123   }
124   lastPasteboardChangeDate_.reset([[NSDate date] retain]);
125   lastPasteboardChangeCount_ = [UIPasteboard generalPasteboard].changeCount;
126   if (lastPasteboardChangeCount_ != suppressedPasteboardEntryCount_) {
127     suppressedPasteboardEntryCount_ = NSIntegerMax;
128   }
131 ClipboardRecentContentIOS::ClipboardRecentContentIOS() {
132   Init(base::TimeDelta::FromMilliseconds(base::SysInfo::Uptime()));
135 ClipboardRecentContentIOS::ClipboardRecentContentIOS(base::TimeDelta uptime) {
136   Init(uptime);
139 void ClipboardRecentContentIOS::Init(base::TimeDelta uptime) {
140   lastPasteboardChangeCount_ = NSIntegerMax;
141   suppressedPasteboardEntryCount_ = NSIntegerMax;
142   urlFromPasteboardCache_ = URLFromPasteboard();
143   LoadFromUserDefaults();
145   // On iOS 7 (unlike on iOS 8, despite what the documentation says), the change
146   // count is reset when the device is rebooted.
147   if (uptime < GetClipboardContentAge() &&
148       !base::ios::IsRunningOnIOS8OrLater()) {
149     if ([UIPasteboard generalPasteboard].changeCount == 0) {
150       // The user hasn't pasted anything in the clipboard since the device's
151       // reboot. |PasteboardChanged| isn't called because it would update
152       // |lastPasteboardChangeData_|, and record metrics.
153       lastPasteboardChangeCount_ = 0;
154       if (suppressedPasteboardEntryCount_ != NSIntegerMax) {
155         // If the last time Chrome was running the pasteboard was suppressed,
156         // and the user  has not copied anything since the device launched, then
157         // supress this entry.
158         suppressedPasteboardEntryCount_ = 0;
159       }
160       SaveToUserDefaults();
161     } else {
162       // The user pasted something in the clipboard since the device's reboot.
163       PasteboardChanged();
164     }
165   } else {
166     NSInteger changeCount = [UIPasteboard generalPasteboard].changeCount;
167     if (changeCount != lastPasteboardChangeCount_) {
168       PasteboardChanged();
169     }
170   }
171   // Makes sure |lastPasteboardChangeCount_| was properly initialized.
172   DCHECK_NE(lastPasteboardChangeCount_, NSIntegerMax);
173   notificationBridge_.reset(
174       [[PasteboardNotificationListenerBridge alloc] initWithDelegate:this]);
177 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {
180 GURL ClipboardRecentContentIOS::URLFromPasteboard() {
181   const std::string clipboard =
182       base::SysNSStringToUTF8([[UIPasteboard generalPasteboard] string]);
183   GURL gurl = GURL(clipboard);
184   if (gurl.is_valid()) {
185     for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) {
186       if (gurl.SchemeIs(kAuthorizedSchemes[i])) {
187         return gurl;
188       }
189     }
190     if (!application_scheme_.empty() &&
191         gurl.SchemeIs(application_scheme_.c_str())) {
192       return gurl;
193     }
194   }
195   return GURL::EmptyGURL();
198 void ClipboardRecentContentIOS::LoadFromUserDefaults() {
199   NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
201   lastPasteboardChangeCount_ =
202       [defaults integerForKey:kPasteboardChangeCountKey];
203   lastPasteboardChangeDate_.reset(
204       [[defaults objectForKey:kPasteboardChangeDateKey] retain]);
206   if ([[[defaults dictionaryRepresentation] allKeys]
207           containsObject:kSuppressedPasteboardEntryCountKey]) {
208     suppressedPasteboardEntryCount_ =
209         [defaults integerForKey:kSuppressedPasteboardEntryCountKey];
210   } else {
211     suppressedPasteboardEntryCount_ = NSIntegerMax;
212   }
214   DCHECK(!lastPasteboardChangeDate_ ||
215          [lastPasteboardChangeDate_ isKindOfClass:[NSDate class]]);
218 void ClipboardRecentContentIOS::SaveToUserDefaults() {
219   NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
220   [defaults setInteger:lastPasteboardChangeCount_
221                 forKey:kPasteboardChangeCountKey];
222   [defaults setObject:lastPasteboardChangeDate_
223                forKey:kPasteboardChangeDateKey];
224   [defaults setInteger:suppressedPasteboardEntryCount_
225                 forKey:kSuppressedPasteboardEntryCountKey];