Index: ios/chrome/browser/ui/open_in_controller.mm |
diff --git a/ios/chrome/browser/ui/open_in_controller.mm b/ios/chrome/browser/ui/open_in_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a66e9f3d0fe23fba8414c2e7b9188eb2dcaf7207 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/open_in_controller.mm |
@@ -0,0 +1,652 @@ |
+// Copyright 2012 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/open_in_controller.h" |
+ |
+#include "base/files/file_path.h" |
+#include "base/ios/weak_nsobject.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#import "base/mac/bind_objc_block.h" |
+#include "base/mac/objc_property_releaser.h" |
+#include "base/sequenced_task_runner.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/threading/sequenced_worker_pool.h" |
+#include "base/threading/thread_restrictions.h" |
+#include "components/strings/grit/components_strings.h" |
+#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
+#import "ios/chrome/browser/ui/open_in_controller_testing.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/ui/uikit_ui_util.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/web/public/web_thread.h" |
+#import "ios/web/web_state/ui/crw_web_controller.h" |
+#include "net/base/load_flags.h" |
+#include "net/url_request/url_fetcher.h" |
+#include "net/url_request/url_fetcher_delegate.h" |
+#include "net/url_request/url_request_context_getter.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+#import "ui/gfx/ios/NSString+CrStringDrawing.h" |
+#include "url/gurl.h" |
+ |
+namespace { |
+// The path in the temp directory containing documents that are to be opened in |
+// other applications. |
+static NSString* const kDocumentsTempPath = @"OpenIn"; |
+ |
+static const int kHTTPResponseCodeSucceeded = 200; |
+ |
+// Duration of the show/hide animation for the |openInToolbar_|. |
+const NSTimeInterval kOpenInToolbarAnimationDuration = 0.2; |
+ |
+// Duration to show or hide the |overlayedView_|. |
+const NSTimeInterval kOverlayViewAnimationDuration = 0.3; |
+ |
+// Time interval after which the |openInToolbar_| is automatically hidden. |
+const NSTimeInterval kOpenInToolbarDisplayDuration = 2.0; |
+ |
+// Text size used for the label indicating a download in progress. |
+const CGFloat kLabelTextSize = 22.0; |
+ |
+// Alpha value for the background view of |overlayedView_|. |
+const CGFloat kOverlayedViewBackgroundAlpha = 0.6; |
+ |
+// Width of the label displayed on the |overlayedView_| as a percentage of the |
+// |overlayedView_|'s width. |
+const CGFloat kOverlayedViewLabelWidthPercentage = 0.7; |
+ |
+// Bottom margin for the label displayed on the |overlayedView_|. |
+const CGFloat kOverlayedViewLabelBottomMargin = 60; |
+ |
+} // anonymous namespace |
+ |
+@interface OpenInController () { |
+ // AlertCoordinator for showing an alert if no applications were found to open |
+ // the current document. |
+ base::scoped_nsobject<AlertCoordinator> _alertCoordinator; |
+} |
+ |
+// URLFetcher delegate method called when |fetcher_| completes a request. |
+- (void)urlFetchDidComplete:(const net::URLFetcher*)source; |
+// Ensures the destination directory is created and any contained obsolete files |
+// are deleted. Returns YES if the directory is created successfully. |
++ (BOOL)createDestinationDirectoryAndRemoveObsoleteFiles; |
+// Starts downloading the file at path |kDocumentsTempPath| with the name |
+// |suggestedFilename_|. |
+- (void)startDownload; |
+// Shows the overlayed toolbar |openInToolbar_|. |
+- (void)showOpenInToolbar; |
+// Hides the overlayed toolbar |openInToolbar_|. |
+- (void)hideOpenInToolbar; |
+// Called when there is a tap on the |webController_|'s view to display the |
+// overlayed toolbar |openInToolbar_| if necessary and (re)schedule the |
+// |openInTimer_|. |
+- (void)handleTapFrom:(UIGestureRecognizer*)gestureRecognizer; |
+// Downloads the file at |documentURL_| and presents the OpenIn menu for opening |
+// it in other applications. |
+- (void)exportFileWithOpenInMenuAnchoredAt:(id)sender; |
+// Called when there is a tap on the |overlayedView_| to cancel the file |
+// download. |
+- (void)handleTapOnOverlayedView:(UIGestureRecognizer*)gestureRecognizer; |
+// Removes |overlayedView_| from the top view of the application. |
+- (void)removeOverlayedView; |
+// Shows an alert with the given error message. |
+- (void)showErrorWithMessage:(NSString*)message; |
+// Presents the OpenIn menu for the file at |fileURL|. |
+- (void)presentOpenInMenuForFileAtURL:(NSURL*)fileURL; |
+// Removes the file at path |path|. |
+- (void)removeDocumentAtPath:(NSString*)path; |
+// Removes all the stored files at path |path|. |
++ (void)removeAllStoredDocumentsAtPath:(NSString*)path; |
+// Shows an overlayed spinner on the top view to indicate that a file download |
+// is in progress. |
+- (void)showDownloadOverlayView; |
+// Returns a toolbar with an "Open in..." button to be overlayed on a document |
+// on tap. |
+- (UIToolbar*)openInToolbar; |
+@end |
+ |
+// Bridge to deliver method calls from C++ to the |OpenInController| class. |
+class OpenInControllerBridge |
+ : public net::URLFetcherDelegate, |
+ public base::RefCountedThreadSafe<OpenInControllerBridge> { |
+ public: |
+ explicit OpenInControllerBridge(OpenInController* owner) : owner_(owner) {} |
+ |
+ void OnURLFetchComplete(const net::URLFetcher* source) override { |
+ DCHECK(owner_); |
+ [owner_ urlFetchDidComplete:source]; |
+ } |
+ |
+ BOOL CreateDestinationDirectoryAndRemoveObsoleteFiles(void) { |
+ return [OpenInController createDestinationDirectoryAndRemoveObsoleteFiles]; |
+ } |
+ |
+ void OnDestinationDirectoryCreated(BOOL success) { |
+ DCHECK_CURRENTLY_ON(web::WebThread::UI); |
+ if (!success) |
+ [owner_ hideOpenInToolbar]; |
+ else |
+ [owner_ startDownload]; |
+ } |
+ |
+ void OnOwnerDisabled() { |
+ // When the owner is disabled: |
+ // - if there is a task in flight posted via |PostTaskAndReplyWithResult| |
+ // then dereferencing |bridge_| will not release it as |bridge_| is also |
+ // referenced by the task posting; setting |owner_| to nil makes sure that |
+ // no methods are called on it, and it works since |owner_| is only used on |
+ // the main thread. |
+ // - if there is a task in flight posted by the URLFetcher then |
+ // |OpenInController| destroys the fetcher and cancels the callback. This is |
+ // why |OnURLFetchComplete| will neved be called after |owner_| is disabled. |
+ owner_ = nil; |
+ } |
+ |
+ protected: |
+ friend base::RefCountedThreadSafe<OpenInControllerBridge>; |
+ ~OpenInControllerBridge() override {} |
+ |
+ private: |
+ OpenInController* owner_; // weak |
+}; |
+ |
+@implementation OpenInController { |
+ // Bridge from C++ to Obj-C class. |
+ scoped_refptr<OpenInControllerBridge> bridge_; |
+ |
+ // URL of the document. |
+ GURL documentURL_; |
+ |
+ // Controller for opening documents in other applications. |
+ base::scoped_nsobject<UIDocumentInteractionController> documentController_; |
+ |
+ // Toolbar overlay to be displayed on tap. |
+ base::scoped_nsobject<OpenInToolbar> openInToolbar_; |
+ |
+ // Timer used to automatically hide the |openInToolbar_| after a period. |
+ base::scoped_nsobject<NSTimer> openInTimer_; |
+ |
+ // Gesture recognizer to catch taps on the document. |
+ base::scoped_nsobject<UITapGestureRecognizer> tapRecognizer_; |
+ |
+ // Suggested filename for the document. |
+ base::scoped_nsobject<NSString> suggestedFilename_; |
+ |
+ // Fetcher used to redownload the document and save it in the sandbox. |
+ std::unique_ptr<net::URLFetcher> fetcher_; |
+ |
+ // CRWWebController used to check if the tap is not on a link and the |
+ // |openInToolbar_| should be displayed. |
+ base::scoped_nsobject<CRWWebController> webController_; |
+ |
+ // URLRequestContextGetter needed for the URLFetcher. |
+ scoped_refptr<net::URLRequestContextGetter> requestContext_; |
+ |
+ // Spinner view displayed while the file is downloading. |
+ base::scoped_nsobject<UIView> overlayedView_; |
+ |
+ // The location where the "Open in..." menu is anchored. |
+ CGRect anchorLocation_; |
+ |
+ // YES if the file download was canceled. |
+ BOOL downloadCanceled_; |
+ |
+ // YES if the OpenIn menu is displayed. |
+ BOOL isOpenInMenuDisplayed_; |
+ |
+ // Task runner on which file operations should happen. |
+ scoped_refptr<base::SequencedTaskRunner> sequencedTaskRunner_; |
+ |
+ base::mac::ObjCPropertyReleaser propertyReleaser_OpenInController_; |
+} |
+ |
+- (id)initWithRequestContext:(net::URLRequestContextGetter*)requestContext |
+ webController:(CRWWebController*)webController { |
+ self = [super init]; |
+ if (self) { |
+ requestContext_ = requestContext; |
+ webController_.reset([webController retain]); |
+ tapRecognizer_.reset([[UITapGestureRecognizer alloc] |
+ initWithTarget:self |
+ action:@selector(handleTapFrom:)]); |
+ [tapRecognizer_ setDelegate:self]; |
+ base::SequencedWorkerPool* pool = web::WebThread::GetBlockingPool(); |
+ sequencedTaskRunner_ = |
+ pool->GetSequencedTaskRunner(pool->GetSequenceToken()); |
+ isOpenInMenuDisplayed_ = NO; |
+ propertyReleaser_OpenInController_.Init(self, [OpenInController class]); |
+ } |
+ return self; |
+} |
+ |
+- (void)enableWithDocumentURL:(const GURL&)documentURL |
+ suggestedFilename:(NSString*)suggestedFilename { |
+ documentURL_ = GURL(documentURL); |
+ suggestedFilename_.reset([suggestedFilename retain]); |
+ [webController_ addGestureRecognizerToWebView:tapRecognizer_]; |
+ [self openInToolbar].alpha = 0.0f; |
+ [webController_ addToolbarViewToWebView:[self openInToolbar]]; |
+} |
+ |
+- (void)disable { |
+ [self openInToolbar].alpha = 0.0f; |
+ [openInTimer_ invalidate]; |
+ if (bridge_.get()) |
+ bridge_->OnOwnerDisabled(); |
+ bridge_ = nil; |
+ [webController_ removeGestureRecognizerFromWebView:tapRecognizer_]; |
+ [webController_ removeToolbarViewFromWebView:[self openInToolbar]]; |
+ [documentController_ dismissMenuAnimated:NO]; |
+ [documentController_ setDelegate:nil]; |
+ documentURL_ = GURL(); |
+ suggestedFilename_.reset(); |
+ fetcher_.reset(); |
+} |
+ |
+- (void)detachFromWebController { |
+ [self disable]; |
+ // Animation blocks may be keeping this object alive; don't extend the |
+ // lifetime of CRWWebController. |
+ webController_.reset(); |
+} |
+ |
+- (void)dealloc { |
+ [self disable]; |
+ [super dealloc]; |
+} |
+ |
+- (void)handleTapFrom:(UIGestureRecognizer*)gestureRecognizer { |
+ if ([gestureRecognizer state] == UIGestureRecognizerStateEnded) { |
+ [self showOpenInToolbar]; |
+ } |
+} |
+ |
+- (void)showOpenInToolbar { |
+ if ([openInTimer_ isValid]) { |
+ [openInTimer_ setFireDate:([NSDate dateWithTimeIntervalSinceNow: |
+ kOpenInToolbarDisplayDuration])]; |
+ } else { |
+ openInTimer_.reset( |
+ [[NSTimer scheduledTimerWithTimeInterval:kOpenInToolbarDisplayDuration |
+ target:self |
+ selector:@selector(hideOpenInToolbar) |
+ userInfo:nil |
+ repeats:NO] retain]); |
+ UIView* openInToolbar = [self openInToolbar]; |
+ [UIView animateWithDuration:kOpenInToolbarAnimationDuration |
+ animations:^{ |
+ [openInToolbar setAlpha:1.0]; |
+ }]; |
+ } |
+} |
+ |
+- (void)hideOpenInToolbar { |
+ if (!openInToolbar_) |
+ return; |
+ UIView* openInToolbar = [self openInToolbar]; |
+ [UIView animateWithDuration:kOpenInToolbarAnimationDuration |
+ animations:^{ |
+ [openInToolbar setAlpha:0.0]; |
+ }]; |
+} |
+ |
+- (void)exportFileWithOpenInMenuAnchoredAt:(UIView*)view { |
+ DCHECK([view isKindOfClass:[UIView class]]); |
+ DCHECK_CURRENTLY_ON(web::WebThread::UI); |
+ if (!webController_) |
+ return; |
+ |
+ anchorLocation_ = [[self openInToolbar] convertRect:view.frame |
+ toView:[webController_ view]]; |
+ [openInTimer_ invalidate]; |
+ if (!bridge_.get()) |
+ bridge_ = new OpenInControllerBridge(self); |
+ |
+ // This needs to be done in two steps, on two separate threads. The |
+ // first task needs to be done on the worker pool and returns a BOOL which is |
+ // then used in the second function, |OnDestinationDirectoryCreated|, which |
+ // runs on the UI thread. |
+ base::Callback<BOOL(void)> task = base::Bind( |
+ &OpenInControllerBridge::CreateDestinationDirectoryAndRemoveObsoleteFiles, |
+ bridge_); |
+ base::Callback<void(BOOL)> reply = base::Bind( |
+ &OpenInControllerBridge::OnDestinationDirectoryCreated, bridge_); |
+ base::PostTaskAndReplyWithResult(sequencedTaskRunner_.get(), FROM_HERE, task, |
+ reply); |
+} |
+ |
+- (void)startDownload { |
+ NSString* tempDirPath = [NSTemporaryDirectory() |
+ stringByAppendingPathComponent:kDocumentsTempPath]; |
+ NSString* filePath = |
+ [tempDirPath stringByAppendingPathComponent:suggestedFilename_.get()]; |
+ |
+ // In iPad the toolbar has to be displayed to anchor the "Open in" menu. |
+ if (!IsIPadIdiom()) |
+ [self hideOpenInToolbar]; |
+ |
+ // Show an overlayed view to indicate a download is in progress. On tap this |
+ // view can be dismissed and the download canceled. |
+ [self showDownloadOverlayView]; |
+ downloadCanceled_ = NO; |
+ |
+ // Ensure |bridge_| is set in case this function is called from a unittest. |
+ if (!bridge_.get()) |
+ bridge_ = new OpenInControllerBridge(self); |
+ |
+ // Download the document and save it at |filePath|. |
+ fetcher_ = net::URLFetcher::Create(0, documentURL_, net::URLFetcher::GET, |
+ bridge_.get()); |
+ fetcher_->SetRequestContext(requestContext_.get()); |
+ fetcher_->SetLoadFlags(net::LOAD_SKIP_CACHE_VALIDATION); |
+ fetcher_->SaveResponseToFileAtPath( |
+ base::FilePath(base::SysNSStringToUTF8(filePath)), sequencedTaskRunner_); |
+ fetcher_->Start(); |
+} |
+ |
+- (void)handleTapOnOverlayedView:(UIGestureRecognizer*)gestureRecognizer { |
+ if ([gestureRecognizer state] != UIGestureRecognizerStateEnded) |
+ return; |
+ |
+ [self removeOverlayedView]; |
+ if (IsIPadIdiom()) |
+ [self hideOpenInToolbar]; |
+ downloadCanceled_ = YES; |
+} |
+ |
+- (void)removeOverlayedView { |
+ if (!overlayedView_) |
+ return; |
+ |
+ UIView* overlayedView = overlayedView_.get(); |
+ [UIView animateWithDuration:kOverlayViewAnimationDuration |
+ animations:^{ |
+ [overlayedView setAlpha:0.0]; |
+ } |
+ completion:^(BOOL finished) { |
+ [overlayedView removeFromSuperview]; |
+ }]; |
+ overlayedView_.reset(); |
+} |
+ |
+- (void)showErrorWithMessage:(NSString*)message { |
+ UIViewController* topViewController = |
+ [[[UIApplication sharedApplication] keyWindow] rootViewController]; |
+ |
+ _alertCoordinator.reset([[AlertCoordinator alloc] |
+ initWithBaseViewController:topViewController |
+ title:nil |
+ message:message]); |
+ |
+ [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_OK) |
+ action:nil |
+ style:UIAlertActionStyleDefault]; |
+ |
+ [_alertCoordinator start]; |
+} |
+ |
+- (void)presentOpenInMenuForFileAtURL:(NSURL*)fileURL { |
+ if (!webController_) |
+ return; |
+ |
+ if (requestContext_.get()) { |
+ // |requestContext_| is nil only if this is called from a unit test, in |
+ // which case the |documentController_| was set already. |
+ documentController_.reset([[UIDocumentInteractionController |
+ interactionControllerWithURL:fileURL] retain]); |
+ } |
+ |
+ // TODO(cgrigoruta): The UTI is hardcoded for now, change this when we add |
+ // support for other file types as well. |
+ [documentController_ setUTI:@"com.adobe.pdf"]; |
+ [documentController_ setDelegate:self]; |
+ BOOL success = |
+ [documentController_ presentOpenInMenuFromRect:anchorLocation_ |
+ inView:[webController_ view] |
+ animated:YES]; |
+ if (requestContext_.get()) { |
+ [self removeOverlayedView]; |
+ if (!success) { |
+ if (IsIPadIdiom()) |
+ [self hideOpenInToolbar]; |
+ NSString* errorMessage = |
+ l10n_util::GetNSStringWithFixup(IDS_IOS_OPEN_IN_NO_APPS_REGISTERED); |
+ [self showErrorWithMessage:errorMessage]; |
+ } else { |
+ isOpenInMenuDisplayed_ = YES; |
+ } |
+ } |
+} |
+ |
+- (void)showDownloadOverlayView { |
+ UIViewController* topViewController = |
+ [[[UIApplication sharedApplication] keyWindow] rootViewController]; |
+ UIView* topView = topViewController.view; |
+ overlayedView_.reset([[UIView alloc] initWithFrame:[topView bounds]]); |
+ [overlayedView_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleHeight)]; |
+ base::scoped_nsobject<UIView> grayBackgroundView( |
+ [[UIView alloc] initWithFrame:[overlayedView_ frame]]); |
+ [grayBackgroundView setBackgroundColor:[UIColor darkGrayColor]]; |
+ [grayBackgroundView setAlpha:kOverlayedViewBackgroundAlpha]; |
+ [grayBackgroundView setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleHeight)]; |
+ [overlayedView_ addSubview:grayBackgroundView]; |
+ |
+ base::scoped_nsobject<UIActivityIndicatorView> spinner([ |
+ [UIActivityIndicatorView alloc] |
+ initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]); |
+ [spinner setFrame:[overlayedView_ frame]]; |
+ [spinner setHidesWhenStopped:YES]; |
+ [spinner setUserInteractionEnabled:NO]; |
+ [spinner startAnimating]; |
+ [spinner setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | |
+ UIViewAutoresizingFlexibleHeight)]; |
+ [overlayedView_ addSubview:spinner]; |
+ |
+ base::scoped_nsobject<UILabel> label([[UILabel alloc] init]); |
+ [label setTextColor:[UIColor whiteColor]]; |
+ [label setFont:GetUIFont(FONT_HELVETICA, true, kLabelTextSize)]; |
+ [label setNumberOfLines:0]; |
+ [label setShadowColor:[UIColor blackColor]]; |
+ [label setShadowOffset:CGSizeMake(0.0, 1.0)]; |
+ [label setBackgroundColor:[UIColor clearColor]]; |
+ [label setText:l10n_util::GetNSString(IDS_IOS_OPEN_IN_FILE_DOWNLOAD_CANCEL)]; |
+ [label setLineBreakMode:NSLineBreakByWordWrapping]; |
+ [label setTextAlignment:NSTextAlignmentCenter]; |
+ CGFloat labelWidth = |
+ [overlayedView_ frame].size.width * kOverlayedViewLabelWidthPercentage; |
+ CGFloat originX = ([overlayedView_ frame].size.width - labelWidth) / 2; |
+ |
+ CGFloat labelHeight = |
+ [[label text] cr_boundingSizeWithSize:CGSizeMake(labelWidth, CGFLOAT_MAX) |
+ font:[label font]] |
+ .height; |
+ CGFloat originY = |
+ [overlayedView_ center].y - labelHeight - kOverlayedViewLabelBottomMargin; |
+ [label setFrame:CGRectMake(originX, originY, labelWidth, labelHeight)]; |
+ [overlayedView_ addSubview:label]; |
+ |
+ base::scoped_nsobject<UITapGestureRecognizer> tapRecognizer( |
+ [[UITapGestureRecognizer alloc] |
+ initWithTarget:self |
+ action:@selector(handleTapOnOverlayedView:)]); |
+ [tapRecognizer setDelegate:self]; |
+ [overlayedView_ addGestureRecognizer:tapRecognizer]; |
+ |
+ [overlayedView_ setAlpha:0.0]; |
+ [topView addSubview:overlayedView_]; |
+ UIView* overlayedView = overlayedView_.get(); |
+ [UIView animateWithDuration:kOverlayViewAnimationDuration |
+ animations:^{ |
+ [overlayedView setAlpha:1.0]; |
+ }]; |
+} |
+ |
+- (UIView*)openInToolbar { |
+ if (!openInToolbar_) { |
+ openInToolbar_.reset([[OpenInToolbar alloc] |
+ initWithTarget:self |
+ action:@selector(exportFileWithOpenInMenuAnchoredAt:)]); |
+ } |
+ return openInToolbar_.get(); |
+} |
+ |
+#pragma mark - |
+#pragma mark File management |
+ |
+- (void)removeDocumentAtPath:(NSString*)path { |
+ base::ThreadRestrictions::AssertIOAllowed(); |
+ NSFileManager* fileManager = [NSFileManager defaultManager]; |
+ NSError* error = nil; |
+ if (![fileManager removeItemAtPath:path error:&error]) { |
+ DLOG(ERROR) << "Failed to remove file: " |
+ << base::SysNSStringToUTF8([error description]); |
+ } |
+} |
+ |
++ (void)removeAllStoredDocumentsAtPath:(NSString*)tempDirPath { |
+ base::ThreadRestrictions::AssertIOAllowed(); |
+ NSFileManager* fileManager = [NSFileManager defaultManager]; |
+ NSError* error = nil; |
+ NSArray* documentFiles = |
+ [fileManager contentsOfDirectoryAtPath:tempDirPath error:&error]; |
+ if (!documentFiles) { |
+ DLOG(ERROR) << "Failed to get content of directory at path: " |
+ << base::SysNSStringToUTF8([error description]); |
+ return; |
+ } |
+ |
+ for (NSString* filename in documentFiles) { |
+ NSString* filePath = [tempDirPath stringByAppendingPathComponent:filename]; |
+ if (![fileManager removeItemAtPath:filePath error:&error]) { |
+ DLOG(ERROR) << "Failed to remove file: " |
+ << base::SysNSStringToUTF8([error description]); |
+ } |
+ } |
+} |
+ |
++ (BOOL)createDestinationDirectoryAndRemoveObsoleteFiles { |
+ base::ThreadRestrictions::AssertIOAllowed(); |
+ NSString* tempDirPath = [NSTemporaryDirectory() |
+ stringByAppendingPathComponent:kDocumentsTempPath]; |
+ NSFileManager* fileManager = [NSFileManager defaultManager]; |
+ BOOL isDirectory; |
+ NSError* error = nil; |
+ if (![fileManager fileExistsAtPath:tempDirPath isDirectory:&isDirectory]) { |
+ BOOL created = [fileManager createDirectoryAtPath:tempDirPath |
+ withIntermediateDirectories:YES |
+ attributes:nil |
+ error:&error]; |
+ DCHECK(created); |
+ if (!created) { |
+ DLOG(ERROR) << "Error creating destination dir: " |
+ << base::SysNSStringToUTF8([error description]); |
+ return NO; |
+ } |
+ } else { |
+ DCHECK(isDirectory); |
+ if (!isDirectory) { |
+ DLOG(ERROR) << "Destination Directory already exists and is a file."; |
+ return NO; |
+ } |
+ // Remove all documents that might be still on temporary storage. |
+ [self removeAllStoredDocumentsAtPath:(NSString*)tempDirPath]; |
+ } |
+ return YES; |
+} |
+ |
+#pragma mark - |
+#pragma mark URLFetcher delegate method |
+ |
+- (void)urlFetchDidComplete:(const net::URLFetcher*)fetcher { |
+ DCHECK(fetcher); |
+ if (requestContext_.get()) |
+ DCHECK_CURRENTLY_ON(web::WebThread::UI); |
+ base::FilePath filePath; |
+ if (fetcher->GetResponseCode() == kHTTPResponseCodeSucceeded && |
+ fetcher->GetResponseAsFilePath(true, &filePath)) { |
+ NSURL* fileURL = |
+ [NSURL fileURLWithPath:base::SysUTF8ToNSString(filePath.value())]; |
+ if (downloadCanceled_) { |
+ sequencedTaskRunner_->PostTask(FROM_HERE, base::BindBlock(^{ |
+ [self |
+ removeDocumentAtPath:[fileURL path]]; |
+ })); |
+ } else { |
+ [self presentOpenInMenuForFileAtURL:fileURL]; |
+ } |
+ } else if (!downloadCanceled_) { |
+ if (IsIPadIdiom()) |
+ [self hideOpenInToolbar]; |
+ [self removeOverlayedView]; |
+ [self showErrorWithMessage:l10n_util::GetNSStringWithFixup( |
+ IDS_IOS_OPEN_IN_FILE_DOWNLOAD_FAILED)]; |
+ } |
+} |
+ |
+#pragma mark - |
+#pragma mark UIDocumentInteractionControllerDelegate Methods |
+ |
+- (void)documentInteractionController:(UIDocumentInteractionController*)contr |
+ didEndSendingToApplication:(NSString*)application { |
+ sequencedTaskRunner_->PostTask(FROM_HERE, base::BindBlock(^{ |
+ [self |
+ removeDocumentAtPath:[[contr URL] path]]; |
+ })); |
+ if (IsIPadIdiom()) { |
+ // Call the |documentInteractionControllerDidDismissOpenInMenu:| method |
+ // as this is not called on the iPad after the document has been opened |
+ // in another application. |
+ [self documentInteractionControllerDidDismissOpenInMenu:contr]; |
+ } |
+} |
+ |
+- (void)documentInteractionControllerDidDismissOpenInMenu: |
+ (UIDocumentInteractionController*)controller { |
+ if (!IsIPadIdiom()) { |
+ isOpenInMenuDisplayed_ = NO; |
+ // On the iPhone the |openInToolber_| is hidden already. |
+ return; |
+ } |
+ |
+ // On iPad this method is called whenever the device changes orientation, |
+ // even thought the OpenIn menu is not displayed. To distinguish the cases |
+ // when this method is called after the OpenIn menu is dismissed, we |
+ // check the BOOL |isOpenInMenuDisplayed|. |
+ if (isOpenInMenuDisplayed_) { |
+ openInTimer_.reset( |
+ [[NSTimer scheduledTimerWithTimeInterval:kOpenInToolbarDisplayDuration |
+ target:self |
+ selector:@selector(hideOpenInToolbar) |
+ userInfo:nil |
+ repeats:NO] retain]); |
+ } |
+ isOpenInMenuDisplayed_ = NO; |
+} |
+ |
+#pragma mark - |
+#pragma mark UIGestureRecognizerDelegate Methods |
+ |
+- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer { |
+ if ([gestureRecognizer.view isEqual:overlayedView_]) |
+ return YES; |
+ |
+ CGPoint location = [gestureRecognizer locationInView:[self openInToolbar]]; |
+ return ![[self openInToolbar] pointInside:location withEvent:nil]; |
+} |
+ |
+#pragma mark - TestingAditions |
+ |
+- (void)setDocumentInteractionController: |
+ (UIDocumentInteractionController*)controller { |
+ documentController_.reset([controller retain]); |
+} |
+ |
+- (NSString*)suggestedFilename { |
+ return suggestedFilename_.get(); |
+} |
+ |
+@end |