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