OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #import "ios/chrome/app/spotlight/base_spotlight_manager.h" |
| 6 |
| 7 #import <CommonCrypto/CommonCrypto.h> |
| 8 #import <MobileCoreServices/MobileCoreServices.h> |
| 9 |
| 10 #include "base/ios/weak_nsobject.h" |
| 11 #include "base/mac/bind_objc_block.h" |
| 12 #include "base/strings/sys_string_conversions.h" |
| 13 #include "base/task/cancelable_task_tracker.h" |
| 14 #include "components/favicon/core/fallback_url_util.h" |
| 15 #include "components/favicon/core/large_icon_service.h" |
| 16 #include "components/favicon_base/fallback_icon_style.h" |
| 17 #include "components/favicon_base/favicon_types.h" |
| 18 #include "ios/chrome/grit/ios_strings.h" |
| 19 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 20 #include "ios/public/provider/chrome/browser/spotlight/spotlight_provider.h" |
| 21 #import "ios/third_party/material_components_ios/src/components/Typography/src/M
aterialTypography.h" |
| 22 #import "net/base/mac/url_conversions.h" |
| 23 #include "skia/ext/skia_utils_ios.h" |
| 24 #include "ui/base/l10n/l10n_util.h" |
| 25 |
| 26 namespace { |
| 27 // Minimum size of the icon to be used in Spotlight. |
| 28 const NSInteger kMinIconSize = 32; |
| 29 |
| 30 // Preferred size of the icon to be used in Spotlight. |
| 31 const NSInteger kIconSize = 64; |
| 32 |
| 33 // Size of the fallback icon. |
| 34 const CGFloat kFallbackIconSize = 180; |
| 35 |
| 36 // Radius of the rounded corner of the fallback icon. |
| 37 const CGFloat kFallbackRoundedCorner = 8; |
| 38 } |
| 39 |
| 40 @interface BaseSpotlightManager () { |
| 41 // Domain of the spotlight manager. |
| 42 spotlight::Domain _spotlightDomain; |
| 43 |
| 44 // Service to retrieve large favicon or colors for a fallback icon. |
| 45 favicon::LargeIconService* _largeIconService; // weak |
| 46 |
| 47 // Queue to query large icons. |
| 48 base::CancelableTaskTracker _largeIconTaskTracker; |
| 49 |
| 50 // Dictionary to track the tasks querying the large icons. |
| 51 base::scoped_nsobject<NSMutableDictionary> _pendingTasks; |
| 52 } |
| 53 |
| 54 // Compute a hash consisting of the first 8 bytes of the MD5 hash of a string |
| 55 // containing |URL| and |title|. |
| 56 - (int64_t)getHashForURL:(const GURL&)URL title:(NSString*)title; |
| 57 |
| 58 // Create an image with a rounded square with color |backgroundColor| and |
| 59 // |string| centered in color |textColor|. |
| 60 UIImage* GetFallbackImageWithStringAndColor(NSString* string, |
| 61 UIColor* backgroundColor, |
| 62 UIColor* textColor); |
| 63 |
| 64 // Returns an array of Keywords for Spotlight search. |
| 65 - (NSArray*)keywordsForSpotlightItems; |
| 66 |
| 67 @end |
| 68 |
| 69 @implementation BaseSpotlightManager |
| 70 |
| 71 - (instancetype)initWithLargeIconService: |
| 72 (favicon::LargeIconService*)largeIconService |
| 73 domain:(spotlight::Domain)domain { |
| 74 self = [super init]; |
| 75 if (self) { |
| 76 _spotlightDomain = domain; |
| 77 _largeIconService = largeIconService; |
| 78 _pendingTasks.reset([[NSMutableDictionary alloc] init]); |
| 79 } |
| 80 return self; |
| 81 } |
| 82 |
| 83 - (instancetype)init { |
| 84 NOTREACHED(); |
| 85 return nil; |
| 86 } |
| 87 |
| 88 - (int64_t)getHashForURL:(const GURL&)URL title:(NSString*)title { |
| 89 NSString* key = [NSString |
| 90 stringWithFormat:@"%@ %@", base::SysUTF8ToNSString(URL.spec()), title]; |
| 91 unsigned char hash[CC_MD5_DIGEST_LENGTH]; |
| 92 const std::string clipboard = base::SysNSStringToUTF8(key); |
| 93 const char* c_string = clipboard.c_str(); |
| 94 CC_MD5(c_string, strlen(c_string), hash); |
| 95 uint64_t md5 = *(reinterpret_cast<uint64_t*>(hash)); |
| 96 return md5; |
| 97 } |
| 98 |
| 99 - (NSString*)spotlightIDForURL:(const GURL&)URL title:(NSString*)title { |
| 100 NSString* spotlightID = [NSString |
| 101 stringWithFormat:@"%@.%016llx", |
| 102 spotlight::StringFromSpotlightDomain(_spotlightDomain), |
| 103 [self getHashForURL:URL title:title]]; |
| 104 return spotlightID; |
| 105 } |
| 106 |
| 107 - (void)cancelAllLargeIconPendingTasks { |
| 108 _largeIconTaskTracker.TryCancelAll(); |
| 109 [_pendingTasks removeAllObjects]; |
| 110 } |
| 111 |
| 112 - (void)clearAllSpotlightItems:(BlockWithError)callback { |
| 113 [self cancelAllLargeIconPendingTasks]; |
| 114 spotlight::DeleteSearchableDomainItems(_spotlightDomain, callback); |
| 115 } |
| 116 |
| 117 - (CSSearchableItem*)spotlightItemWithItemID:(NSString*)itemID |
| 118 attributeSet:(CSSearchableItemAttributeSet*) |
| 119 attributeSet { |
| 120 CSCustomAttributeKey* key = [[[CSCustomAttributeKey alloc] |
| 121 initWithKeyName:spotlight::GetSpotlightCustomAttributeItemID() |
| 122 searchable:YES |
| 123 searchableByDefault:YES |
| 124 unique:YES |
| 125 multiValued:NO] autorelease]; |
| 126 [attributeSet setValue:itemID forCustomKey:key]; |
| 127 attributeSet.keywords = [self keywordsForSpotlightItems]; |
| 128 |
| 129 NSString* domainID = spotlight::StringFromSpotlightDomain(_spotlightDomain); |
| 130 |
| 131 return [[[CSSearchableItem alloc] initWithUniqueIdentifier:itemID |
| 132 domainIdentifier:domainID |
| 133 attributeSet:attributeSet] |
| 134 autorelease]; |
| 135 } |
| 136 |
| 137 - (NSArray*)spotlightItemsWithURL:(const GURL&)indexedURL |
| 138 favicon:(UIImage*)favicon |
| 139 defaultTitle:(NSString*)defaultTitle { |
| 140 DCHECK(defaultTitle); |
| 141 NSURL* nsURL = net::NSURLWithGURL(indexedURL); |
| 142 std::string description = indexedURL.SchemeIsCryptographic() |
| 143 ? indexedURL.GetOrigin().spec() |
| 144 : indexedURL.spec(); |
| 145 |
| 146 base::scoped_nsobject<CSSearchableItemAttributeSet> attributeSet( |
| 147 [[CSSearchableItemAttributeSet alloc] |
| 148 initWithItemContentType:(NSString*)kUTTypeURL]); |
| 149 [attributeSet setTitle:defaultTitle]; |
| 150 [attributeSet setDisplayName:defaultTitle]; |
| 151 [attributeSet setURL:nsURL]; |
| 152 [attributeSet setContentURL:nsURL]; |
| 153 [attributeSet setContentDescription:base::SysUTF8ToNSString(description)]; |
| 154 [attributeSet setThumbnailData:UIImagePNGRepresentation(favicon)]; |
| 155 |
| 156 NSString* itemID = [self spotlightIDForURL:indexedURL title:defaultTitle]; |
| 157 return [NSArray arrayWithObject:[self spotlightItemWithItemID:itemID |
| 158 attributeSet:attributeSet]]; |
| 159 } |
| 160 |
| 161 UIImage* GetFallbackImageWithStringAndColor(NSString* string, |
| 162 UIColor* backgroundColor, |
| 163 UIColor* textColor) { |
| 164 CGRect rect = CGRectMake(0, 0, kFallbackIconSize, kFallbackIconSize); |
| 165 UIGraphicsBeginImageContext(rect.size); |
| 166 CGContextRef context = UIGraphicsGetCurrentContext(); |
| 167 CGContextSetFillColorWithColor(context, [backgroundColor CGColor]); |
| 168 CGContextSetStrokeColorWithColor(context, [textColor CGColor]); |
| 169 UIBezierPath* rounded = |
| 170 [UIBezierPath bezierPathWithRoundedRect:rect |
| 171 cornerRadius:kFallbackRoundedCorner]; |
| 172 [rounded fill]; |
| 173 UIFont* font = [MDCTypography headlineFont]; |
| 174 font = [font fontWithSize:(kFallbackIconSize / 2)]; |
| 175 CGRect textRect = CGRectMake(0, (kFallbackIconSize - [font lineHeight]) / 2, |
| 176 kFallbackIconSize, [font lineHeight]); |
| 177 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( |
| 178 [[NSMutableParagraphStyle alloc] init]); |
| 179 [paragraphStyle setAlignment:NSTextAlignmentCenter]; |
| 180 base::scoped_nsobject<NSMutableDictionary> attributes( |
| 181 [[NSMutableDictionary alloc] init]); |
| 182 [attributes setValue:font forKey:NSFontAttributeName]; |
| 183 [attributes setValue:textColor forKey:NSForegroundColorAttributeName]; |
| 184 [attributes setValue:paragraphStyle forKey:NSParagraphStyleAttributeName]; |
| 185 |
| 186 [string drawInRect:textRect withAttributes:attributes]; |
| 187 UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); |
| 188 UIGraphicsEndImageContext(); |
| 189 return image; |
| 190 } |
| 191 |
| 192 - (void)refreshItemsWithURL:(const GURL&)URLToRefresh title:(NSString*)title { |
| 193 NSURL* NSURL = net::NSURLWithGURL(URLToRefresh); |
| 194 |
| 195 if (!NSURL || [_pendingTasks objectForKey:NSURL]) { |
| 196 return; |
| 197 } |
| 198 |
| 199 base::WeakNSObject<BaseSpotlightManager> weakSelf(self); |
| 200 GURL URL = URLToRefresh; |
| 201 void (^faviconBlock)(const favicon_base::LargeIconResult&) = ^( |
| 202 const favicon_base::LargeIconResult& result) { |
| 203 base::scoped_nsobject<BaseSpotlightManager> strongSelf([weakSelf retain]); |
| 204 if (!strongSelf) { |
| 205 return; |
| 206 } |
| 207 [strongSelf.get()->_pendingTasks removeObjectForKey:NSURL]; |
| 208 UIImage* favicon; |
| 209 if (result.bitmap.is_valid()) { |
| 210 scoped_refptr<base::RefCountedMemory> data = |
| 211 result.bitmap.bitmap_data.get(); |
| 212 favicon = [UIImage |
| 213 imageWithData:[NSData dataWithBytes:data->front() length:data->size()] |
| 214 scale:[UIScreen mainScreen].scale]; |
| 215 } else { |
| 216 NSString* iconText = |
| 217 base::SysUTF16ToNSString(favicon::GetFallbackIconText(URL)); |
| 218 UIColor* backgroundColor = skia::UIColorFromSkColor( |
| 219 result.fallback_icon_style->background_color); |
| 220 UIColor* textColor = |
| 221 skia::UIColorFromSkColor(result.fallback_icon_style->text_color); |
| 222 favicon = GetFallbackImageWithStringAndColor(iconText, backgroundColor, |
| 223 textColor); |
| 224 } |
| 225 NSArray* spotlightItems = [strongSelf spotlightItemsWithURL:URL |
| 226 favicon:favicon |
| 227 defaultTitle:title]; |
| 228 |
| 229 if ([spotlightItems count]) { |
| 230 [[CSSearchableIndex defaultSearchableIndex] |
| 231 indexSearchableItems:spotlightItems |
| 232 completionHandler:nil]; |
| 233 } |
| 234 }; |
| 235 |
| 236 base::CancelableTaskTracker::TaskId taskID = |
| 237 _largeIconService->GetLargeIconOrFallbackStyle( |
| 238 URL, kMinIconSize * [UIScreen mainScreen].scale, |
| 239 kIconSize * [UIScreen mainScreen].scale, |
| 240 base::BindBlock(faviconBlock), &_largeIconTaskTracker); |
| 241 [_pendingTasks setObject:[NSNumber numberWithLongLong:taskID] forKey:NSURL]; |
| 242 } |
| 243 |
| 244 - (NSUInteger)pendingLargeIconTasksCount { |
| 245 return [_pendingTasks count]; |
| 246 } |
| 247 |
| 248 #pragma mark private methods |
| 249 |
| 250 - (NSArray*)keywordsForSpotlightItems { |
| 251 NSMutableArray* keywordsArray = [NSMutableArray arrayWithArray:@[ |
| 252 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_ONE), |
| 253 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_TWO), |
| 254 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_THREE), |
| 255 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_FOUR), |
| 256 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_FIVE), |
| 257 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_SIX), |
| 258 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_SEVEN), |
| 259 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_EIGHT), |
| 260 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_NINE), |
| 261 l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_TEN) |
| 262 ]]; |
| 263 NSArray* additionalArray = ios::GetChromeBrowserProvider() |
| 264 ->GetSpotlightProvider() |
| 265 ->GetAdditionalKeywords(); |
| 266 if (additionalArray) { |
| 267 [keywordsArray addObjectsFromArray:additionalArray]; |
| 268 } |
| 269 return keywordsArray; |
| 270 } |
| 271 |
| 272 @end |
OLD | NEW |