blob: 210c4137cc77d13b1efc346e16f7a93970479ef6 [file] [log] [blame]
// Copyright 2017 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/browser/ui/image_util/image_saver.h"
#import <Photos/Photos.h>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/format_macros.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
#include "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
#import "ios/chrome/browser/ui/image_util/image_util.h"
#import "ios/chrome/browser/web/image_fetch_tab_helper.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#include "net/base/mime_util.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface ImageSaver ()
// Base view controller for the alerts.
@property(nonatomic, weak) UIViewController* baseViewController;
// Alert coordinator to give feedback to the user.
@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
@property(nonatomic, readonly) Browser* browser;
@end
@implementation ImageSaver
@synthesize alertCoordinator = _alertCoordinator;
@synthesize baseViewController = _baseViewController;
@synthesize browser = _browser;
- (instancetype)initWithBaseViewController:(UIViewController*)baseViewController
browser:(Browser*)browser {
self = [super init];
if (self) {
_baseViewController = baseViewController;
_browser = browser;
}
return self;
}
- (void)saveImageAtURL:(const GURL&)url
referrer:(const web::Referrer&)referrer
webState:(web::WebState*)webState {
ImageFetchTabHelper* tabHelper = ImageFetchTabHelper::FromWebState(webState);
DCHECK(tabHelper);
__weak ImageSaver* weakSelf = self;
tabHelper->GetImageData(url, referrer, ^(NSData* data) {
ImageSaver* strongSelf = weakSelf;
if (!strongSelf)
return;
if (data.length == 0) {
[strongSelf displayPrivacyErrorAlertOnMainQueue:
l10n_util::GetNSString(
IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
return;
}
// Use -imageWithData to validate |data|, but continue to pass the raw
// |data| to -savePhoto to ensure no data loss occurs.
UIImage* savedImage = [UIImage imageWithData:data];
if (!savedImage) {
[strongSelf
displayPrivacyErrorAlertOnMainQueue:l10n_util::GetNSString(
IDS_IOS_SAVE_IMAGE_ERROR)];
return;
}
[self savePhoto:data];
});
}
// Dump |data| into the photo library. Requires the usage of
// NSPhotoLibraryAddUsageDescription.
- (void)savePhoto:(NSData*)data {
[[PHPhotoLibrary sharedPhotoLibrary]
performChanges:^{
PHAssetResourceCreationOptions* options =
[[PHAssetResourceCreationOptions alloc] init];
[[PHAssetCreationRequest creationRequestForAsset]
addResourceWithType:PHAssetResourceTypePhoto
data:data
options:options];
}
completionHandler:^(BOOL success, NSError* error) {
if (error) {
// Saving photo failed, likely due to a permissions issue.
// This code may be executed outside of the main thread. Make sure to
// display the error on the main thread.
[self displayImageErrorAlertWithSettingsOnMainQueue];
} else {
// TODO(crbug.com/797277): Provide a way for the user to easily
// reach the photos app.
}
}];
}
// Called when Chrome has been denied access to add photos or videos and the
// user can change it.
// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
// settings. Dismiss previous alert if it has not been dismissed yet.
- (void)displayImageErrorAlertWithSettingsOnMainQueue {
NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
BOOL canGoToSetting =
[[UIApplication sharedApplication] canOpenURL:settingURL];
if (canGoToSetting) {
dispatch_async(dispatch_get_main_queue(), ^{
[self displayImageErrorAlertWithSettings:settingURL];
});
} else {
[self displayPrivacyErrorAlertOnMainQueue:
l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
}
}
// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
// previous alert if it has not been dismissed yet.
- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
// Dismiss current alert.
[_alertCoordinator stop];
NSString* title =
l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
NSString* message = l10n_util::GetNSString(
IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
self.alertCoordinator = [[AlertCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:_browser
title:title
message:message];
[self.alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
action:nil
style:UIAlertActionStyleCancel];
[_alertCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
action:^{
[[UIApplication sharedApplication] openURL:settingURL
options:@{}
completionHandler:nil];
}
style:UIAlertActionStyleDefault];
[_alertCoordinator start];
}
// Called when Chrome has been denied access to the photos or videos and the
// user cannot change it.
// Shows a privacy alert on the main queue, with errorContent as the message.
// Dismisses previous alert if it has not been dismissed yet.
- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
dispatch_async(dispatch_get_main_queue(), ^{
NSString* title =
l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
// Dismiss current alert.
[self.alertCoordinator stop];
self.alertCoordinator = [[AlertCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:_browser
title:title
message:errorContent];
[self.alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_OK)
action:nil
style:UIAlertActionStyleDefault];
[self.alertCoordinator start];
});
}
@end