Index: ios/chrome/app/spotlight/base_spotlight_manager.mm |
diff --git a/ios/chrome/app/spotlight/base_spotlight_manager.mm b/ios/chrome/app/spotlight/base_spotlight_manager.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4d497c1b1d9a2a1d21c614816576a63a5260d1ca |
--- /dev/null |
+++ b/ios/chrome/app/spotlight/base_spotlight_manager.mm |
@@ -0,0 +1,272 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/app/spotlight/base_spotlight_manager.h" |
+ |
+#import <CommonCrypto/CommonCrypto.h> |
+#import <MobileCoreServices/MobileCoreServices.h> |
+ |
+#include "base/ios/weak_nsobject.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/task/cancelable_task_tracker.h" |
+#include "components/favicon/core/fallback_url_util.h" |
+#include "components/favicon/core/large_icon_service.h" |
+#include "components/favicon_base/fallback_icon_style.h" |
+#include "components/favicon_base/favicon_types.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#include "ios/public/provider/chrome/browser/spotlight/spotlight_provider.h" |
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "skia/ext/skia_utils_ios.h" |
+#include "ui/base/l10n/l10n_util.h" |
+ |
+namespace { |
+// Minimum size of the icon to be used in Spotlight. |
+const NSInteger kMinIconSize = 32; |
+ |
+// Preferred size of the icon to be used in Spotlight. |
+const NSInteger kIconSize = 64; |
+ |
+// Size of the fallback icon. |
+const CGFloat kFallbackIconSize = 180; |
+ |
+// Radius of the rounded corner of the fallback icon. |
+const CGFloat kFallbackRoundedCorner = 8; |
+} |
+ |
+@interface BaseSpotlightManager () { |
+ // Domain of the spotlight manager. |
+ spotlight::Domain _spotlightDomain; |
+ |
+ // Service to retrieve large favicon or colors for a fallback icon. |
+ favicon::LargeIconService* _largeIconService; // weak |
+ |
+ // Queue to query large icons. |
+ base::CancelableTaskTracker _largeIconTaskTracker; |
+ |
+ // Dictionary to track the tasks querying the large icons. |
+ base::scoped_nsobject<NSMutableDictionary> _pendingTasks; |
+} |
+ |
+// Compute a hash consisting of the first 8 bytes of the MD5 hash of a string |
+// containing |URL| and |title|. |
+- (int64_t)getHashForURL:(const GURL&)URL title:(NSString*)title; |
+ |
+// Create an image with a rounded square with color |backgroundColor| and |
+// |string| centered in color |textColor|. |
+UIImage* GetFallbackImageWithStringAndColor(NSString* string, |
+ UIColor* backgroundColor, |
+ UIColor* textColor); |
+ |
+// Returns an array of Keywords for Spotlight search. |
+- (NSArray*)keywordsForSpotlightItems; |
+ |
+@end |
+ |
+@implementation BaseSpotlightManager |
+ |
+- (instancetype)initWithLargeIconService: |
+ (favicon::LargeIconService*)largeIconService |
+ domain:(spotlight::Domain)domain { |
+ self = [super init]; |
+ if (self) { |
+ _spotlightDomain = domain; |
+ _largeIconService = largeIconService; |
+ _pendingTasks.reset([[NSMutableDictionary alloc] init]); |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (int64_t)getHashForURL:(const GURL&)URL title:(NSString*)title { |
+ NSString* key = [NSString |
+ stringWithFormat:@"%@ %@", base::SysUTF8ToNSString(URL.spec()), title]; |
+ unsigned char hash[CC_MD5_DIGEST_LENGTH]; |
+ const std::string clipboard = base::SysNSStringToUTF8(key); |
+ const char* c_string = clipboard.c_str(); |
+ CC_MD5(c_string, strlen(c_string), hash); |
+ uint64_t md5 = *(reinterpret_cast<uint64_t*>(hash)); |
+ return md5; |
+} |
+ |
+- (NSString*)spotlightIDForURL:(const GURL&)URL title:(NSString*)title { |
+ NSString* spotlightID = [NSString |
+ stringWithFormat:@"%@.%016llx", |
+ spotlight::StringFromSpotlightDomain(_spotlightDomain), |
+ [self getHashForURL:URL title:title]]; |
+ return spotlightID; |
+} |
+ |
+- (void)cancelAllLargeIconPendingTasks { |
+ _largeIconTaskTracker.TryCancelAll(); |
+ [_pendingTasks removeAllObjects]; |
+} |
+ |
+- (void)clearAllSpotlightItems:(BlockWithError)callback { |
+ [self cancelAllLargeIconPendingTasks]; |
+ spotlight::DeleteSearchableDomainItems(_spotlightDomain, callback); |
+} |
+ |
+- (CSSearchableItem*)spotlightItemWithItemID:(NSString*)itemID |
+ attributeSet:(CSSearchableItemAttributeSet*) |
+ attributeSet { |
+ CSCustomAttributeKey* key = [[[CSCustomAttributeKey alloc] |
+ initWithKeyName:spotlight::GetSpotlightCustomAttributeItemID() |
+ searchable:YES |
+ searchableByDefault:YES |
+ unique:YES |
+ multiValued:NO] autorelease]; |
+ [attributeSet setValue:itemID forCustomKey:key]; |
+ attributeSet.keywords = [self keywordsForSpotlightItems]; |
+ |
+ NSString* domainID = spotlight::StringFromSpotlightDomain(_spotlightDomain); |
+ |
+ return [[[CSSearchableItem alloc] initWithUniqueIdentifier:itemID |
+ domainIdentifier:domainID |
+ attributeSet:attributeSet] |
+ autorelease]; |
+} |
+ |
+- (NSArray*)spotlightItemsWithURL:(const GURL&)indexedURL |
+ favicon:(UIImage*)favicon |
+ defaultTitle:(NSString*)defaultTitle { |
+ DCHECK(defaultTitle); |
+ NSURL* nsURL = net::NSURLWithGURL(indexedURL); |
+ std::string description = indexedURL.SchemeIsCryptographic() |
+ ? indexedURL.GetOrigin().spec() |
+ : indexedURL.spec(); |
+ |
+ base::scoped_nsobject<CSSearchableItemAttributeSet> attributeSet( |
+ [[CSSearchableItemAttributeSet alloc] |
+ initWithItemContentType:(NSString*)kUTTypeURL]); |
+ [attributeSet setTitle:defaultTitle]; |
+ [attributeSet setDisplayName:defaultTitle]; |
+ [attributeSet setURL:nsURL]; |
+ [attributeSet setContentURL:nsURL]; |
+ [attributeSet setContentDescription:base::SysUTF8ToNSString(description)]; |
+ [attributeSet setThumbnailData:UIImagePNGRepresentation(favicon)]; |
+ |
+ NSString* itemID = [self spotlightIDForURL:indexedURL title:defaultTitle]; |
+ return [NSArray arrayWithObject:[self spotlightItemWithItemID:itemID |
+ attributeSet:attributeSet]]; |
+} |
+ |
+UIImage* GetFallbackImageWithStringAndColor(NSString* string, |
+ UIColor* backgroundColor, |
+ UIColor* textColor) { |
+ CGRect rect = CGRectMake(0, 0, kFallbackIconSize, kFallbackIconSize); |
+ UIGraphicsBeginImageContext(rect.size); |
+ CGContextRef context = UIGraphicsGetCurrentContext(); |
+ CGContextSetFillColorWithColor(context, [backgroundColor CGColor]); |
+ CGContextSetStrokeColorWithColor(context, [textColor CGColor]); |
+ UIBezierPath* rounded = |
+ [UIBezierPath bezierPathWithRoundedRect:rect |
+ cornerRadius:kFallbackRoundedCorner]; |
+ [rounded fill]; |
+ UIFont* font = [MDCTypography headlineFont]; |
+ font = [font fontWithSize:(kFallbackIconSize / 2)]; |
+ CGRect textRect = CGRectMake(0, (kFallbackIconSize - [font lineHeight]) / 2, |
+ kFallbackIconSize, [font lineHeight]); |
+ base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( |
+ [[NSMutableParagraphStyle alloc] init]); |
+ [paragraphStyle setAlignment:NSTextAlignmentCenter]; |
+ base::scoped_nsobject<NSMutableDictionary> attributes( |
+ [[NSMutableDictionary alloc] init]); |
+ [attributes setValue:font forKey:NSFontAttributeName]; |
+ [attributes setValue:textColor forKey:NSForegroundColorAttributeName]; |
+ [attributes setValue:paragraphStyle forKey:NSParagraphStyleAttributeName]; |
+ |
+ [string drawInRect:textRect withAttributes:attributes]; |
+ UIImage* image = UIGraphicsGetImageFromCurrentImageContext(); |
+ UIGraphicsEndImageContext(); |
+ return image; |
+} |
+ |
+- (void)refreshItemsWithURL:(const GURL&)URLToRefresh title:(NSString*)title { |
+ NSURL* NSURL = net::NSURLWithGURL(URLToRefresh); |
+ |
+ if (!NSURL || [_pendingTasks objectForKey:NSURL]) { |
+ return; |
+ } |
+ |
+ base::WeakNSObject<BaseSpotlightManager> weakSelf(self); |
+ GURL URL = URLToRefresh; |
+ void (^faviconBlock)(const favicon_base::LargeIconResult&) = ^( |
+ const favicon_base::LargeIconResult& result) { |
+ base::scoped_nsobject<BaseSpotlightManager> strongSelf([weakSelf retain]); |
+ if (!strongSelf) { |
+ return; |
+ } |
+ [strongSelf.get()->_pendingTasks removeObjectForKey:NSURL]; |
+ UIImage* favicon; |
+ if (result.bitmap.is_valid()) { |
+ scoped_refptr<base::RefCountedMemory> data = |
+ result.bitmap.bitmap_data.get(); |
+ favicon = [UIImage |
+ imageWithData:[NSData dataWithBytes:data->front() length:data->size()] |
+ scale:[UIScreen mainScreen].scale]; |
+ } else { |
+ NSString* iconText = |
+ base::SysUTF16ToNSString(favicon::GetFallbackIconText(URL)); |
+ UIColor* backgroundColor = skia::UIColorFromSkColor( |
+ result.fallback_icon_style->background_color); |
+ UIColor* textColor = |
+ skia::UIColorFromSkColor(result.fallback_icon_style->text_color); |
+ favicon = GetFallbackImageWithStringAndColor(iconText, backgroundColor, |
+ textColor); |
+ } |
+ NSArray* spotlightItems = [strongSelf spotlightItemsWithURL:URL |
+ favicon:favicon |
+ defaultTitle:title]; |
+ |
+ if ([spotlightItems count]) { |
+ [[CSSearchableIndex defaultSearchableIndex] |
+ indexSearchableItems:spotlightItems |
+ completionHandler:nil]; |
+ } |
+ }; |
+ |
+ base::CancelableTaskTracker::TaskId taskID = |
+ _largeIconService->GetLargeIconOrFallbackStyle( |
+ URL, kMinIconSize * [UIScreen mainScreen].scale, |
+ kIconSize * [UIScreen mainScreen].scale, |
+ base::BindBlock(faviconBlock), &_largeIconTaskTracker); |
+ [_pendingTasks setObject:[NSNumber numberWithLongLong:taskID] forKey:NSURL]; |
+} |
+ |
+- (NSUInteger)pendingLargeIconTasksCount { |
+ return [_pendingTasks count]; |
+} |
+ |
+#pragma mark private methods |
+ |
+- (NSArray*)keywordsForSpotlightItems { |
+ NSMutableArray* keywordsArray = [NSMutableArray arrayWithArray:@[ |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_ONE), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_TWO), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_THREE), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_FOUR), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_FIVE), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_SIX), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_SEVEN), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_EIGHT), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_NINE), |
+ l10n_util::GetNSString(IDS_IOS_SPOTLIGHT_KEYWORD_TEN) |
+ ]]; |
+ NSArray* additionalArray = ios::GetChromeBrowserProvider() |
+ ->GetSpotlightProvider() |
+ ->GetAdditionalKeywords(); |
+ if (additionalArray) { |
+ [keywordsArray addObjectsFromArray:additionalArray]; |
+ } |
+ return keywordsArray; |
+} |
+ |
+@end |