| 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
|
|
|