Index: ios/chrome/browser/ui/downloads/download_manager_controller.mm |
diff --git a/ios/chrome/browser/ui/downloads/download_manager_controller.mm b/ios/chrome/browser/ui/downloads/download_manager_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..95b28bedc90bef3b41e07d39860fa8432284f188 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/downloads/download_manager_controller.mm |
@@ -0,0 +1,1646 @@ |
+// Copyright 2014 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/downloads/download_manager_controller.h" |
+ |
+#include <stdint.h> |
+#include <memory> |
+ |
+#include "base/files/file_path.h" |
+#include "base/files/file_util.h" |
+#include "base/ios/weak_nsobject.h" |
+#include "base/location.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/mac/objc_property_releaser.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/metrics/histogram.h" |
+#include "base/metrics/histogram_macros.h" |
+#include "base/metrics/user_metrics.h" |
+#include "base/metrics/user_metrics_action.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/threading/sequenced_worker_pool.h" |
+#include "components/strings/grit/components_strings.h" |
+#import "ios/chrome/browser/installation_notifier.h" |
+#include "ios/chrome/browser/native_app_launcher/ios_appstore_ids.h" |
+#import "ios/chrome/browser/storekit_launcher.h" |
+#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" |
+#import "ios/chrome/browser/ui/network_activity_indicator_manager.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/public/provider/chrome/browser/chrome_browser_provider.h" |
+#import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metadata.h" |
+#import "ios/public/provider/chrome/browser/native_app_launcher/native_app_whitelist_manager.h" |
+#import "ios/third_party/material_components_ios/src/components/ActivityIndicator/src/MaterialActivityIndicator.h" |
+#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h" |
+#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h" |
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#include "ios/web/public/web_thread.h" |
+#include "net/base/filename_util.h" |
+#include "net/http/http_response_headers.h" |
+#include "net/http/http_util.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 "net/url_request/url_request_status.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+#import "ui/gfx/ios/uikit_util.h" |
+ |
+using base::UserMetricsAction; |
+using net::HttpResponseHeaders; |
+using net::URLFetcher; |
+using net::URLFetcherDelegate; |
+using net::URLRequestContextGetter; |
+using net::URLRequestStatus; |
+ |
+@interface DownloadManagerController () |
+ |
+// Creates the portrait and landscape mode constraints that are switched every |
+// time the interface is rotated. |
+- (void)initConstraints; |
+ |
+// Runs every time the interface switches between portrait and landscape mode, |
+// and applies the appropriate UI constraints for |orientation|. |
+- (void)updateConstraints:(UIInterfaceOrientation)orientation; |
+ |
+// Modifies (only) the constraints that anchor the action bar in portrait |
+// mode (there are different constraints for when the Google Drive |
+// button is showing versus not showing). This should only be called when |
+// the interface orientation is portrait. |
+- (void)updatePortraitActionBarConstraints; |
+ |
+// Removes all constraints that position the action bar in portrait mode. |
+- (void)removePortraitActionBarConstraints; |
+ |
+// Adds in the correct constraints to position the action bar in portrait mode, |
+// based on whether the Google Drive button is showing or not. This should only |
+// be called when the interface orientation is portrait. |
+- (void)addPortraitActionBarConstraints; |
+ |
+// Updates the file type label's vertical constraint constant to position the |
+// file type label depending on the device orientation and whether or not the |
+// error icon is displayed. |
+- (void)updateFileTypeLabelConstraint; |
+ |
+// Called every time the device orientation is about to change. |
+- (void)interfaceOrientationWillChangeNotification: |
+ (NSNotification*)notification; |
+ |
+// Called every time the device orientation has changed. |
+- (void)deviceOrientationDidChangeNotification:(NSNotification*)notification; |
+ |
+// If |down| is YES, animates the file type label down. Otherwise animates |
+// the file type label up. Runs |completion| at the end of the animation. |
+- (void)animateFileTypeLabelDown:(BOOL)down |
+ withCompletion:(void (^)(BOOL))completion; |
+ |
+// Initializes the URL fetcher to make a HEAD request to get information about |
+// the file to download and starts the network activity indicator. |
+- (void)startHeadFetch; |
+ |
+// Called when the HEAD request for the downloaded file returns and there is |
+// information about the file to display (size, type, etc.). |
+- (void)onHeadFetchComplete; |
+ |
+// Creates the activity indicator to animate while download a file with an |
+// unknown file size. |
+- (void)initializeActivityIndicator; |
+ |
+// Displays a dialog informing the user that no application on the device can |
+// open the file. The dialog contains a button for downloading Google Drive. |
+- (void)displayUnableToOpenFileDialog; |
+ |
+// Displays the error UI to the user, notifying them that their download |
+// failed. This method only ensures that the file type label is in the right |
+// place, then calls |finishDisplayingError| to do all the other work. |
+- (void)displayError; |
+ |
+// Updates the views to display the error UI. |
+- (void)finishDisplayingError; |
+ |
+// Handles clicks on the download button - also the only way to get out of the |
+// error flow. |
+- (IBAction)downloadButtonTapped:(id)sender; |
+ |
+// Undoes all the changes to the UI caused by calling |displayError|, and |
+// then calls |finishDownloadButtonTapped|. |
+- (void)hideError; |
+ |
+// In practice this will almost always call |beginStartingContentDownload|, but |
+// it will call |startHeadFetch| if the head fetch never completed successfully |
+// and led to the error flow. |
+- (void)finishDownloadButtonTapped; |
+ |
+// Begins the work start a download, including creating the downloads directory |
+- (void)beginStartingContentDownload; |
+ |
+// Called after an attempt to create the downloads directory on another |
+// thread has completed. |directoryCreated| is true if the creation was |
+// successful, or false otherwise. |
+- (void)finishStartingContentDownload:(bool)directoryCreated; |
+ |
+// Called when another chunk of the file has successfully been downloaded. |
+// |bytesDownloaded| gives the number of total bytes downloaded so far. |
+- (void)onContentFetchProgress:(long long)bytesDownloaded; |
+ |
+// Changes the height of the progress bar to be |self.fractionDownloaded| the |
+// height of the document icon, and adjusts its y-coordinate so its bottom |
+// always aligns with the bottom of the document icon. If |withAnimation| is |
+// YES, it will animate the change (as long as the change is > 1 point). If |
+// |withCompletionAnimation| is YES, the file download complete animation will |
+// run once the progress bar has finished being set. |
+- (void)setProgressBarHeightWithAnimation:(BOOL)animated |
+ withCompletionAnimation:(BOOL)completionAnimation; |
+ |
+// Makes the file icon "pop" and fades the image in the fold icon to the |
+// completed fold icon. This will also call -showGoogleDriveButton if the user |
+// doesn't have Google Drive installed on their device. |
+- (void)runDownloadCompleteAnimation; |
+ |
+// Handles clicks on the cancel button. |
+- (IBAction)cancelButtonTapped:(id)sender; |
+ |
+// Sets the time left label to the given text. If |animated| is YES, it will |
+// animate the text change. |
+- (void)setTimeLeft:(NSString*)text withAnimation:(BOOL)animated; |
+ |
+// Called when the request for downloading the file has completed (successfully |
+// or not). |
+- (void)onContentFetchComplete; |
+ |
+// Animates the Google Drive button onto the screen after |delay| time has |
+// passed. |
+- (void)showGoogleDriveButton; |
+ |
+// Animates the Google Drive button off the screen. |
+- (void)hideGoogleDriveButton; |
+ |
+// Handles clicks on the open in button. |
+- (IBAction)openInButtonTapped:(id)sender; |
+ |
+// Opens a view controller that allows the user to install Google Drive. |
+- (void)openGoogleDriveInAppStore; |
+ |
+// Calls -openGoogleDriveInAppStore to allow the user to install Google Drive. |
+- (IBAction)googleDriveButtonTapped:(id)sender; |
+ |
+// Cleans up this DownloadManagerController, and deletes its file from the |
+// downloads directory if it has been created there. |
+- (void)dealloc; |
+ |
+// Returns an NSString* unique to |self|, to use with the |
+// NetworkActivityIndicatorManager. |
+- (NSString*)getNetworkActivityKey; |
+ |
+// Fills |path| with the FilePath to the downloads directory. Returns YES if |
+// this is successul, or NO otherwise. |
++ (BOOL)fetchDownloadsDirectoryFilePath:(base::FilePath*)path; |
+ |
+@end |
+ |
+namespace { |
+ |
+NSString* kGoogleDriveBundleId = @"com.google.Drive"; |
+ |
+// Key of the UMA Download.IOSDownloadFileResult histogram. |
+const char* const kUMADownloadFileResult = "Download.IOSDownloadFileResult"; |
+// Values of the UMA Download.IOSDownloadFileResult histogram. |
+enum DownloadFileResult { |
+ DOWNLOAD_COMPLETED, |
+ DOWNLOAD_CANCELED, |
+ DOWNLOAD_FAILURE, |
+ DOWNLOAD_OTHER, |
+ DOWNLOAD_FILE_RESULT_COUNT |
+}; |
+ |
+// Key of the UMA Download.IOSDownloadedFileAction histogram. |
+const char* const kUMADownloadedFileAction = "Download.IOSDownloadedFileAction"; |
+// Values of the UMA Download.IOSDownloadedFileAction histogram. |
+enum DownloadedFileAction { |
+ OPENED_IN_DRIVE, |
+ OPENED_IN_OTHER_APP, |
+ NO_ACTION, |
+ DOWNLOADED_FILE_ACTION_COUNT |
+}; |
+ |
+// Key of the UMA Download.IOSDownloadedFileStatusCode histogram. |
+const char kUMADownloadedFileStatusCode[] = |
+ "Download.IOSDownloadedFileStatusCode"; |
+ |
+int g_download_manager_id = 0; |
+ |
+const int kNoFileSizeGiven = -1; |
+ |
+const NSTimeInterval kFileTypeLabelAnimationDuration = 0.5; |
+const NSTimeInterval kErrorAnimationDuration = 0.5; |
+ |
+const NSTimeInterval kProgressBarAnimationDuration = 1.0; |
+ |
+const NSTimeInterval kDownloadCompleteAnimationDuration = 0.4; |
+NSString* const kDocumentPopAnimationKey = @"DocumentPopAnimationKey"; |
+ |
+const NSTimeInterval kTimeLeftAnimationDuration = 0.25; |
+NSString* const kTimeLeftAnimationKey = @"TimeLeftAnimationKey"; |
+ |
+const NSTimeInterval kGoogleDriveButtonAnimationDuration = 0.5; |
+ |
+const int kUndownloadedDocumentColor = 0x7BAAF8; |
+const int kDownloadedDocumentColor = 0x4285F5; |
+const int kErrorDocumentColor = 0xDB4437; |
+const int kIndeterminateFileSizeColor = 0xEAEAEA; |
+ |
+const CGFloat kFileTypeLabelWhiteAlpha = 0.87; |
+const CGFloat kFileTypeLabelBlackAlpha = 0.56; |
+ |
+const CGFloat kDocumentContainerWidthPortrait = 136; |
+const CGFloat kDocumentContainerHeightPortrait = 180; |
+const CGFloat kDocumentContainerCenterYOneButtonOffsetPortrait = -34; |
+const CGFloat kDocumentContainerCenterYTwoButtonOffsetPortrait = -52; |
+ |
+const CGFloat kDocumentContainerWidthLandscape = 82; |
+const CGFloat kDocumentContainerHeightLandscape = 109; |
+const CGFloat kDocumentContainerCenterYOffsetLandscape = -48; |
+ |
+const CGFloat kFoldIconWidthPortrait = 56; |
+const CGFloat kFoldIconHeightPortrait = 106; |
+ |
+const CGFloat kFoldIconWidthLandscape = 34; |
+const CGFloat kFoldIconHeightLandscape = 64; |
+ |
+const CGFloat kErrorIconWidthPortrait = 40; |
+const CGFloat kErrorIconHeightPortrait = 40; |
+ |
+const CGFloat kErrorIconWidthLandscape = 20; |
+const CGFloat kErrorIconHeightLandscape = 20; |
+ |
+const CGFloat kFileTypeLabelWidthDeltaPortrait = -24; |
+const CGFloat kFileTypeLabelHeightPortrait = 42; |
+ |
+const CGFloat kFileTypeLabelWidthDeltaLandscape = -16; |
+const CGFloat kFileTypeLabelHeightLandscape = 24; |
+ |
+const CGFloat kFileTypeLabelVerticalOffset = 10; |
+const CGFloat kFileTypeLabelAnimationDeltaYPortrait = 44; |
+const CGFloat kFileTypeLabelAnimationDeltaYLandscape = 27; |
+ |
+const CGFloat kTimeLeftLabelWidthDeltaPortrait = -24; |
+const CGFloat kTimeLeftLabelHeightPortrait = 16; |
+const CGFloat kTimeLeftLabelBottomSpacingOffsetPortrait = -12; |
+ |
+const CGFloat kTimeLeftLabelWidthDeltaLandscape = -16; |
+const CGFloat kTimeLeftLabelHeightLandscape = 14; |
+const CGFloat kTimeLeftLabelBottomSpacingOffsetLandscape = -8; |
+ |
+const CGFloat kFileNameLabelHeightPortrait = 20; |
+const CGFloat kFileNameLabelTopSpacingOffsetPortrait = 12; |
+ |
+const CGFloat kFileNameLabelHeightLandscape = 16; |
+const CGFloat kFileNameLabelTopSpacingOffsetLandscape = 8; |
+ |
+const CGFloat kErrorOrSizeLabelHeightPortrait = 16; |
+const CGFloat kErrorOrSizeLabelHeightLandscape = 14; |
+ |
+const CGFloat kActionBarHeightOneButtonPortrait = 68; |
+const CGFloat kActionBarHeightTwoButtonPortrait = 104; |
+ |
+const CGFloat kActionBarHeightLandscape = 52; |
+ |
+const CGFloat kActionBarBorderHeight = 0.5; |
+ |
+const CGFloat kActionBarButtonTopSpacingOffsetPortrait = 16; |
+ |
+const CGFloat kActionBarButtonTopSpacingOffsetLandscape = 8; |
+ |
+// This value is the same in portrait and landscape. |
+const CGFloat kActionBarButtonTrailingSpacingOffset = -16; |
+ |
+const CGFloat kActivityIndicatorWidth = 30; |
+ |
+// URLFetcher delegate that bridges from C++ to an Obj-C class for the HEAD |
+// request to get information about the file. |
+class DownloadHeadDelegate : public URLFetcherDelegate { |
+ public: |
+ explicit DownloadHeadDelegate(DownloadManagerController* owner) |
+ : owner_(owner) {} |
+ void OnURLFetchComplete(const URLFetcher* source) override { |
+ [owner_ onHeadFetchComplete]; |
+ }; |
+ |
+ private: |
+ DownloadManagerController* owner_; // weak. |
+ DISALLOW_COPY_AND_ASSIGN(DownloadHeadDelegate); |
+}; |
+ |
+// URLFetcher delegate that bridges from C++ to this Obj-C class for the |
+// request to download the contents of the file. |
+class DownloadContentDelegate : public URLFetcherDelegate { |
+ public: |
+ explicit DownloadContentDelegate(DownloadManagerController* owner) |
+ : owner_(owner) {} |
+ void OnURLFetchDownloadProgress(const URLFetcher* source, |
+ int64_t current, |
+ int64_t total, |
+ int64_t current_network_bytes) override { |
+ [owner_ onContentFetchProgress:current]; |
+ } |
+ void OnURLFetchComplete(const URLFetcher* source) override { |
+ [owner_ onContentFetchComplete]; |
+ }; |
+ |
+ private: |
+ DownloadManagerController* owner_; // weak. |
+ DISALLOW_COPY_AND_ASSIGN(DownloadContentDelegate); |
+}; |
+ |
+} // namespace |
+ |
+@interface DownloadManagerController () { |
+ int _downloadManagerId; |
+ |
+ // Coordinator for displaying the alert informing the user that no application |
+ // on the device can open the file. |
+ base::scoped_nsobject<AlertCoordinator> _alertCoordinator; |
+ |
+ // The size of the file to be downloaded, as determined by the Content-Length |
+ // header field in the initial HEAD request. This is set to |kNoFileSizeGiven| |
+ // if the Content-Length header is not given. |
+ long long _totalFileSize; |
+ |
+ // YES if |_fileTypeLabel| is vertically centered in |_documentContainer|. NO |
+ // if |_fileTypeLabel| is lower to account for another view in |
+ // |_documentContainer|. |
+ BOOL _isFileTypeLabelCentered; |
+ BOOL _isDisplayingError; |
+ BOOL _didSuccessfullyFinishHeadFetch; |
+ scoped_refptr<URLRequestContextGetter> _requestContextGetter; |
+ std::unique_ptr<URLFetcher> _fetcher; |
+ std::unique_ptr<DownloadHeadDelegate> _headFetcherDelegate; |
+ std::unique_ptr<DownloadContentDelegate> _contentFetcherDelegate; |
+ base::WeakNSProtocol<id<StoreKitLauncher>> _storeKitLauncher; |
+ base::FilePath _downloadFilePath; |
+ base::scoped_nsobject<MDCActivityIndicator> _activityIndicator; |
+ // Set to YES when a download begins and is used to determine if the |
+ // DownloadFileResult histogram needs to be recorded on -dealloc. |
+ BOOL _recordDownloadResultHistogram; |
+ // Set to YES when a file is downloaded and is used to determine if the |
+ // DownloadedFileAction histogram needs to be recorded on -dealloc. |
+ BOOL _recordFileActionHistogram; |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_DownloadManagerController; |
+} |
+ |
+// The container that holds the |documentIcon|, the |progressBar|, the |
+// |foldIcon|, the |fileTypeLabel|, and the |timeLeftLabel|. |
+@property(nonatomic, retain) IBOutlet UIView* documentContainer; |
+ |
+// The progress bar that displays download progress. |
+@property(nonatomic, retain) IBOutlet UIView* progressBar; |
+ |
+// The image of the document. |
+@property(nonatomic, retain) IBOutlet UIImageView* documentIcon; |
+ |
+// The image of the document fold. |
+@property(nonatomic, retain) IBOutlet UIImageView* foldIcon; |
+ |
+// The error image displayed inside the document. |
+@property(nonatomic, retain) IBOutlet UIImageView* errorIcon; |
+ |
+// The label that displays the file type of the file to be downloaded. |
+@property(nonatomic, retain) IBOutlet UILabel* fileTypeLabel; |
+ |
+// The label that displays the estimate of how much time is still needed to |
+// finish the file download. |
+@property(nonatomic, retain) IBOutlet UILabel* timeLeftLabel; |
+ |
+// The label that displays the name of the file to be downloaded, as it will |
+// be saved on the user's device. |
+@property(nonatomic, retain) IBOutlet UILabel* fileNameLabel; |
+ |
+// The label that displays the size of the file to be downloaded or the error |
+// message. |
+@property(nonatomic, retain) IBOutlet UILabel* errorOrSizeLabel; |
+ |
+// The label that displays error messages when errors occur. |
+@property(nonatomic, retain) IBOutlet UILabel* errorLabel; |
+ |
+// The container that holds the |downloadButton|, |cancelButton|, |
+// |openInButton|, and |googleDriveButton|. |
+@property(nonatomic, retain) IBOutlet UIView* actionBar; |
+ |
+// View that appears at the top of the action bar and acts as a border. |
+@property(nonatomic, retain) IBOutlet UIView* actionBarBorder; |
+ |
+// The button which starts the file download. |
+@property(nonatomic, retain) IBOutlet MDCButton* downloadButton; |
+ |
+// The button which switches with the |downloadButton| during a download. |
+// Pressing it cancels the download. |
+@property(nonatomic, retain) IBOutlet MDCButton* cancelButton; |
+ |
+// The button that switches with the |cancelButton| when a file download |
+// completes. Pressing it opens the UIDocumentInteractionController, letting |
+// the user select another app in which to open the downloaded file. |
+@property(nonatomic, retain) IBOutlet MDCButton* openInButton; |
+ |
+// The button that opens a view controller to allow the user to install |
+// Google Drive. |
+@property(nonatomic, retain) IBOutlet MDCButton* googleDriveButton; |
+ |
+// The controller that displays the list of other apps that the downloaded file |
+// can be opened in. |
+@property(nonatomic, retain) |
+ UIDocumentInteractionController* docInteractionController; |
+ |
+// Contains all the constraints that should be applied only in portrait mode. |
+@property(nonatomic, retain) NSArray* portraitConstraintsArray; |
+ |
+// Contains all the constraints that should be applied only in landscape mode. |
+@property(nonatomic, retain) NSArray* landscapeConstraintsArray; |
+ |
+// Contains all the constraints that should be applied only in portrait mode |
+// when there is only one button showing in the action bar (i.e. the Google |
+// Drive button is NOT showing). |
+@property(nonatomic, retain) |
+ NSArray* portraitActionBarOneButtonConstraintsArray; |
+ |
+// Contains all the constraints that should be applied only in portrait mode |
+// when there are two buttons showing in the action bar (i.e. the Google Drive |
+// button IS showing). |
+@property(nonatomic, retain) |
+ NSArray* portraitActionBarTwoButtonConstraintsArray; |
+ |
+// Constraint that positions the file type label vertically in the center of the |
+// document with an additional offset. |
+@property(nonatomic, retain) NSLayoutConstraint* fileTypeLabelCentered; |
+ |
+// Records the time the download started, to display an estimate of how much |
+// time is required to finish the download. |
+@property(nonatomic, retain) NSDate* downloadStartedTime; |
+ |
+// Records the fraction (from 0.0 to 1.0) of the file that has been |
+// downloaded. |
+@property(nonatomic) double fractionDownloaded; |
+ |
+// Used to get a URL scheme that Drive responds to, to register with the |
+// InstallationNotifier. |
+@property(nonatomic, retain) id<NativeAppMetadata> googleDriveMetadata; |
+ |
+@end |
+ |
+@implementation DownloadManagerController |
+ |
+@synthesize documentContainer = _documentContainer; |
+@synthesize progressBar = _progressBar; |
+@synthesize documentIcon = _documentIcon; |
+@synthesize foldIcon = _foldIcon; |
+@synthesize errorIcon = _errorIcon; |
+@synthesize fileTypeLabel = _fileTypeLabel; |
+@synthesize timeLeftLabel = _timeLeftLabel; |
+@synthesize fileNameLabel = _fileNameLabel; |
+@synthesize errorOrSizeLabel = _errorOrSizeLabel; |
+@synthesize errorLabel = _errorLabel; |
+@synthesize actionBar = _actionBar; |
+@synthesize actionBarBorder = _actionBarBorder; |
+@synthesize downloadButton = _downloadButton; |
+@synthesize cancelButton = _cancelButton; |
+@synthesize openInButton = _openInButton; |
+@synthesize googleDriveButton = _googleDriveButton; |
+@synthesize docInteractionController = _docInteractionController; |
+@synthesize portraitConstraintsArray = _portraitConstraintsArray; |
+@synthesize landscapeConstraintsArray = _landscapeConstraintsArray; |
+@synthesize portraitActionBarOneButtonConstraintsArray = |
+ _portraitActionBarOneButtonConstraintsArray; |
+@synthesize portraitActionBarTwoButtonConstraintsArray = |
+ _portraitActionBarTwoButtonConstraintsArray; |
+@synthesize fileTypeLabelCentered = _fileTypeLabelCentered; |
+@synthesize downloadStartedTime = _downloadStartedTime; |
+@synthesize fractionDownloaded = _fractionDownloaded; |
+@synthesize googleDriveMetadata = _googleDriveMetadata; |
+ |
+- (id)initWithURL:(const GURL&)url |
+ requestContextGetter:(URLRequestContextGetter*)requestContextGetter |
+ storeKitLauncher:(id<StoreKitLauncher>)storeLauncher { |
+ self = [super initWithNibName:@"DownloadManagerController" url:url]; |
+ if (self) { |
+ _downloadManagerId = g_download_manager_id++; |
+ _propertyReleaser_DownloadManagerController.Init( |
+ self, [DownloadManagerController class]); |
+ |
+ _requestContextGetter = requestContextGetter; |
+ _headFetcherDelegate.reset(new DownloadHeadDelegate(self)); |
+ _contentFetcherDelegate.reset(new DownloadContentDelegate(self)); |
+ _downloadFilePath = base::FilePath(); |
+ _storeKitLauncher.reset(storeLauncher); |
+ |
+ [_documentContainer |
+ setBackgroundColor:UIColorFromRGB(kUndownloadedDocumentColor)]; |
+ |
+ _isFileTypeLabelCentered = YES; |
+ _isDisplayingError = NO; |
+ _didSuccessfullyFinishHeadFetch = NO; |
+ |
+ NSString* downloadText = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD); |
+ NSString* capsDownloadText = |
+ [downloadText uppercaseStringWithLocale:[NSLocale currentLocale]]; |
+ [_downloadButton setTitle:capsDownloadText forState:UIControlStateNormal]; |
+ |
+ NSString* cancelText = l10n_util::GetNSString(IDS_CANCEL); |
+ NSString* capsCancelText = |
+ [cancelText uppercaseStringWithLocale:[NSLocale currentLocale]]; |
+ [_cancelButton setTitle:capsCancelText forState:UIControlStateNormal]; |
+ |
+ NSString* openInText = l10n_util::GetNSString(IDS_IOS_OPEN_IN); |
+ NSString* capsOpenInText = |
+ [openInText uppercaseStringWithLocale:[NSLocale currentLocale]]; |
+ [_openInButton setTitle:capsOpenInText forState:UIControlStateNormal]; |
+ |
+ NSString* googleDriveText = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_INSTALL_GOOGLE_DRIVE); |
+ NSString* capsGoogleDriveText = |
+ [googleDriveText uppercaseStringWithLocale:[NSLocale currentLocale]]; |
+ [_googleDriveButton setTitle:capsGoogleDriveText |
+ forState:UIControlStateNormal]; |
+ |
+ [_documentContainer setIsAccessibilityElement:YES]; |
+ self.fractionDownloaded = 0; |
+ |
+ // Constraints not in xib: |
+ self.fileTypeLabelCentered = [_fileTypeLabel.centerYAnchor |
+ constraintEqualToAnchor:_documentContainer.centerYAnchor |
+ constant:kFileTypeLabelVerticalOffset]; |
+ self.fileTypeLabelCentered.active = YES; |
+ |
+ // Action Bar Border |
+ [_actionBarBorder.heightAnchor |
+ constraintEqualToConstant:ui::AlignValueToUpperPixel( |
+ kActionBarBorderHeight)] |
+ .active = YES; |
+ |
+ [self initConstraints]; |
+ UIInterfaceOrientation orientation = |
+ [[UIApplication sharedApplication] statusBarOrientation]; |
+ [self updateConstraints:orientation]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(interfaceOrientationWillChangeNotification:) |
+ name:UIApplicationWillChangeStatusBarOrientationNotification |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(deviceOrientationDidChangeNotification:) |
+ name:UIDeviceOrientationDidChangeNotification |
+ object:nil]; |
+ base::RecordAction(UserMetricsAction("MobileDownloadFileUIShown")); |
+ } |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [[NSNotificationCenter defaultCenter] |
+ removeObserver:self |
+ name:UIApplicationWillChangeStatusBarOrientationNotification |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ removeObserver:self |
+ name:UIDeviceOrientationDidChangeNotification |
+ object:nil]; |
+ [[InstallationNotifier sharedInstance] unregisterForNotifications:self]; |
+ [[NetworkActivityIndicatorManager sharedInstance] |
+ clearNetworkTasksForGroup:[self getNetworkActivityKey]]; |
+ [_docInteractionController setDelegate:nil]; |
+ [_docInteractionController dismissMenuAnimated:NO]; |
+ if (!_downloadFilePath.empty()) { |
+ // A local copy of _downloadFilePath must be made: the instance variable |
+ // will be cleaned up during dealloc, but a local copy will be retained by |
+ // the block and won't be deleted until the block completes. |
+ base::FilePath downloadPathCopy = _downloadFilePath; |
+ web::WebThread::PostBlockingPoolTask(FROM_HERE, base::BindBlock(^{ |
+ DeleteFile(downloadPathCopy, false); |
+ })); |
+ } |
+ if (_recordDownloadResultHistogram) { |
+ UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_OTHER, |
+ DOWNLOAD_FILE_RESULT_COUNT); |
+ } |
+ if (_recordFileActionHistogram) { |
+ UMA_HISTOGRAM_ENUMERATION(kUMADownloadedFileAction, NO_ACTION, |
+ DOWNLOADED_FILE_ACTION_COUNT); |
+ } |
+ [super dealloc]; |
+} |
+ |
+#pragma mark - Layout constraints |
+ |
+- (void)initConstraints { |
+ // Document Container |
+ NSLayoutConstraint* portraitDocumentContainerWidth = |
+ [_documentContainer.widthAnchor |
+ constraintEqualToConstant:kDocumentContainerWidthPortrait]; |
+ NSLayoutConstraint* portraitDocumentContainerHeight = |
+ [_documentContainer.heightAnchor |
+ constraintEqualToConstant:kDocumentContainerHeightPortrait]; |
+ |
+ // This constraint varies in portrait mode depending on whether the action |
+ // bar is showing one button or two. |
+ NSLayoutConstraint* portraitDocumentContainerCenterYOneButton = |
+ [_documentContainer.centerYAnchor |
+ constraintEqualToAnchor:self.view.centerYAnchor |
+ constant: |
+ kDocumentContainerCenterYOneButtonOffsetPortrait]; |
+ NSLayoutConstraint* portraitDocumentContainerCenterYTwoButton = |
+ [_documentContainer.centerYAnchor |
+ constraintEqualToAnchor:self.view.centerYAnchor |
+ constant: |
+ kDocumentContainerCenterYTwoButtonOffsetPortrait]; |
+ |
+ NSLayoutConstraint* landscapeDocumentContainerWidth = |
+ [_documentContainer.widthAnchor |
+ constraintEqualToConstant:kDocumentContainerWidthLandscape]; |
+ NSLayoutConstraint* landscapeDocumentContainerHeight = |
+ [_documentContainer.heightAnchor |
+ constraintEqualToConstant:kDocumentContainerHeightLandscape]; |
+ NSLayoutConstraint* landscapeDocumentContainerCenterY = |
+ [_documentContainer.centerYAnchor |
+ constraintEqualToAnchor:self.view.centerYAnchor |
+ constant:kDocumentContainerCenterYOffsetLandscape]; |
+ |
+ // Fold Icon |
+ NSLayoutConstraint* portraitFoldIconWidth = |
+ [_foldIcon.widthAnchor constraintEqualToConstant:kFoldIconWidthPortrait]; |
+ NSLayoutConstraint* portraitFoldIconHeight = [_foldIcon.heightAnchor |
+ constraintEqualToConstant:kFoldIconHeightPortrait]; |
+ NSLayoutConstraint* landscapeFoldIconWidth = |
+ [_foldIcon.widthAnchor constraintEqualToConstant:kFoldIconWidthLandscape]; |
+ NSLayoutConstraint* landscapeFoldIconHeight = [_foldIcon.heightAnchor |
+ constraintEqualToConstant:kFoldIconHeightLandscape]; |
+ |
+ // Error Icon |
+ NSLayoutConstraint* portraitErrorIconWidth = [_errorIcon.widthAnchor |
+ constraintEqualToConstant:kErrorIconWidthPortrait]; |
+ NSLayoutConstraint* portraitErrorIconHeight = [_errorIcon.heightAnchor |
+ constraintEqualToConstant:kErrorIconHeightPortrait]; |
+ |
+ NSLayoutConstraint* landscapeErrorIconWidth = [_errorIcon.widthAnchor |
+ constraintEqualToConstant:kErrorIconWidthLandscape]; |
+ NSLayoutConstraint* landscapeErrorIconHeight = [_errorIcon.heightAnchor |
+ constraintEqualToConstant:kErrorIconHeightLandscape]; |
+ |
+ // File Type Label |
+ NSLayoutConstraint* portraitFileTypeLabelWidth = [_fileTypeLabel.widthAnchor |
+ constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor |
+ constant:kFileTypeLabelWidthDeltaPortrait]; |
+ NSLayoutConstraint* portraitFileTypeLabelHeight = [_fileTypeLabel.heightAnchor |
+ constraintEqualToConstant:kFileTypeLabelHeightPortrait]; |
+ |
+ NSLayoutConstraint* landscapeFileTypeLabelWidth = [_fileTypeLabel.widthAnchor |
+ constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor |
+ constant:kFileTypeLabelWidthDeltaLandscape]; |
+ NSLayoutConstraint* landscapeFileTypeLabelHeight = |
+ [_fileTypeLabel.heightAnchor |
+ constraintEqualToConstant:kFileTypeLabelHeightLandscape]; |
+ |
+ // Time Left Label |
+ NSLayoutConstraint* portraitTimeLeftLabelWidth = [_timeLeftLabel.widthAnchor |
+ constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor |
+ constant:kTimeLeftLabelWidthDeltaPortrait]; |
+ NSLayoutConstraint* portraitTimeLeftLabelHeight = [_timeLeftLabel.heightAnchor |
+ constraintEqualToConstant:kTimeLeftLabelHeightPortrait]; |
+ NSLayoutConstraint* portraitTimeLeftLabelBottomSpacing = |
+ [_timeLeftLabel.bottomAnchor |
+ constraintEqualToAnchor:_documentContainer.bottomAnchor |
+ constant:kTimeLeftLabelBottomSpacingOffsetPortrait]; |
+ |
+ NSLayoutConstraint* landscapeTimeLeftLabelWidth = [_timeLeftLabel.widthAnchor |
+ constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor |
+ constant:kTimeLeftLabelWidthDeltaLandscape]; |
+ NSLayoutConstraint* landscapeTimeLeftLabelHeight = |
+ [_timeLeftLabel.heightAnchor |
+ constraintEqualToConstant:kTimeLeftLabelHeightLandscape]; |
+ NSLayoutConstraint* landscapeTimeLeftLabelBottomSpacing = |
+ [_timeLeftLabel.bottomAnchor |
+ constraintEqualToAnchor:_documentContainer.bottomAnchor |
+ constant:kTimeLeftLabelBottomSpacingOffsetLandscape]; |
+ |
+ // File Name Label |
+ NSLayoutConstraint* portraitFileNameLabelHeight = [_fileNameLabel.heightAnchor |
+ constraintEqualToConstant:kFileNameLabelHeightPortrait]; |
+ NSLayoutConstraint* portraitFileNameLabelTopSpacing = |
+ [_fileNameLabel.topAnchor |
+ constraintEqualToAnchor:_documentContainer.bottomAnchor |
+ constant:kFileNameLabelTopSpacingOffsetPortrait]; |
+ |
+ NSLayoutConstraint* landscapeFileNameLabelHeight = |
+ [_fileNameLabel.heightAnchor |
+ constraintEqualToConstant:kFileNameLabelHeightLandscape]; |
+ NSLayoutConstraint* landscapeFileNameLabelTopSpacing = |
+ [_fileNameLabel.topAnchor |
+ constraintEqualToAnchor:_documentContainer.bottomAnchor |
+ constant:kFileNameLabelTopSpacingOffsetLandscape]; |
+ |
+ // File Size Label |
+ NSLayoutConstraint* portraitFileSizeLabelHeight = |
+ [_errorOrSizeLabel.heightAnchor |
+ constraintEqualToConstant:kErrorOrSizeLabelHeightPortrait]; |
+ NSLayoutConstraint* landscapeFileSizeLabelHeight = |
+ [_errorOrSizeLabel.heightAnchor |
+ constraintEqualToConstant:kErrorOrSizeLabelHeightLandscape]; |
+ |
+ // Action Bar |
+ // This constraint varies in portrait mode depending on whether the action |
+ // bar is showing one button or two. |
+ NSLayoutConstraint* portraitActionBarHeightOneButton = |
+ [_actionBar.heightAnchor |
+ constraintEqualToConstant:kActionBarHeightOneButtonPortrait]; |
+ NSLayoutConstraint* portraitActionBarHeightTwoButton = |
+ [_actionBar.heightAnchor |
+ constraintEqualToConstant:kActionBarHeightTwoButtonPortrait]; |
+ |
+ NSLayoutConstraint* landscapeActionBarHeight = [_actionBar.heightAnchor |
+ constraintEqualToConstant:kActionBarHeightLandscape]; |
+ |
+ // Download Button |
+ NSLayoutConstraint* portraitDownloadButtonTopSpacing = |
+ [_downloadButton.topAnchor |
+ constraintEqualToAnchor:_actionBar.topAnchor |
+ constant:kActionBarButtonTopSpacingOffsetPortrait]; |
+ NSLayoutConstraint* landscapeDownloadButtonTopSpacing = |
+ [_downloadButton.topAnchor |
+ constraintEqualToAnchor:_actionBar.topAnchor |
+ constant:kActionBarButtonTopSpacingOffsetLandscape]; |
+ |
+ // Cancel Button |
+ NSLayoutConstraint* portraitCancelButtonTopSpacing = [_cancelButton.topAnchor |
+ constraintEqualToAnchor:_actionBar.topAnchor |
+ constant:kActionBarButtonTopSpacingOffsetPortrait]; |
+ NSLayoutConstraint* landscapeCancelButtonTopSpacing = [_cancelButton.topAnchor |
+ constraintEqualToAnchor:_actionBar.topAnchor |
+ constant:kActionBarButtonTopSpacingOffsetLandscape]; |
+ |
+ // Open In Button |
+ NSLayoutConstraint* portraitOpenInButtonTopSpacing = [_openInButton.topAnchor |
+ constraintEqualToAnchor:_actionBar.topAnchor |
+ constant:kActionBarButtonTopSpacingOffsetPortrait]; |
+ NSLayoutConstraint* landscapeOpenInButtonTopSpacing = [_openInButton.topAnchor |
+ constraintEqualToAnchor:_actionBar.topAnchor |
+ constant:kActionBarButtonTopSpacingOffsetLandscape]; |
+ |
+ // Google Drive Button |
+ NSLayoutConstraint* portraitGoogleDriveButtonTopAlignment = |
+ [_googleDriveButton.topAnchor |
+ constraintEqualToAnchor:_openInButton.bottomAnchor]; |
+ NSLayoutConstraint* portraitGoogleDriveButtonTrailingSpacing = |
+ [_googleDriveButton.trailingAnchor |
+ constraintEqualToAnchor:_actionBar.trailingAnchor |
+ constant:kActionBarButtonTrailingSpacingOffset]; |
+ |
+ NSLayoutConstraint* landscapeGoogleDriveButtonTopSpacing = |
+ [_googleDriveButton.topAnchor |
+ constraintEqualToAnchor:_actionBar.topAnchor |
+ constant:kActionBarButtonTopSpacingOffsetLandscape]; |
+ NSLayoutConstraint* landscapeGoogleDriveButtonTrailingAlignment = |
+ [_googleDriveButton.trailingAnchor |
+ constraintEqualToAnchor:_openInButton.leadingAnchor]; |
+ |
+ self.portraitConstraintsArray = @[ |
+ portraitDocumentContainerWidth, portraitDocumentContainerHeight, |
+ portraitFoldIconWidth, portraitFoldIconHeight, portraitErrorIconWidth, |
+ portraitErrorIconHeight, portraitFileTypeLabelWidth, |
+ portraitFileTypeLabelHeight, portraitTimeLeftLabelWidth, |
+ portraitTimeLeftLabelHeight, portraitTimeLeftLabelBottomSpacing, |
+ portraitFileNameLabelHeight, portraitFileNameLabelTopSpacing, |
+ portraitFileSizeLabelHeight, portraitDownloadButtonTopSpacing, |
+ portraitCancelButtonTopSpacing, portraitOpenInButtonTopSpacing, |
+ portraitGoogleDriveButtonTopAlignment, |
+ portraitGoogleDriveButtonTrailingSpacing |
+ ]; |
+ self.landscapeConstraintsArray = @[ |
+ landscapeDocumentContainerWidth, |
+ landscapeDocumentContainerHeight, |
+ landscapeDocumentContainerCenterY, |
+ landscapeFoldIconWidth, |
+ landscapeFoldIconHeight, |
+ landscapeErrorIconWidth, |
+ landscapeErrorIconHeight, |
+ landscapeFileTypeLabelWidth, |
+ landscapeFileTypeLabelHeight, |
+ landscapeTimeLeftLabelWidth, |
+ landscapeTimeLeftLabelHeight, |
+ landscapeTimeLeftLabelBottomSpacing, |
+ landscapeFileNameLabelHeight, |
+ landscapeFileNameLabelTopSpacing, |
+ landscapeFileSizeLabelHeight, |
+ landscapeActionBarHeight, |
+ landscapeDownloadButtonTopSpacing, |
+ landscapeCancelButtonTopSpacing, |
+ landscapeOpenInButtonTopSpacing, |
+ landscapeGoogleDriveButtonTopSpacing, |
+ landscapeGoogleDriveButtonTrailingAlignment |
+ ]; |
+ |
+ self.portraitActionBarOneButtonConstraintsArray = @[ |
+ portraitDocumentContainerCenterYOneButton, portraitActionBarHeightOneButton |
+ ]; |
+ |
+ self.portraitActionBarTwoButtonConstraintsArray = @[ |
+ portraitDocumentContainerCenterYTwoButton, portraitActionBarHeightTwoButton |
+ ]; |
+} |
+ |
+- (void)updateConstraints:(UIInterfaceOrientation)orientation { |
+ [self.view removeConstraints:_portraitConstraintsArray]; |
+ [self.view removeConstraints:_landscapeConstraintsArray]; |
+ [self removePortraitActionBarConstraints]; |
+ if (UIInterfaceOrientationIsPortrait(orientation)) { |
+ [self.view addConstraints:_portraitConstraintsArray]; |
+ [self addPortraitActionBarConstraints]; |
+ |
+ [_fileTypeLabel setFont:[MDCTypography display2Font]]; |
+ [_timeLeftLabel setFont:[MDCTypography body1Font]]; |
+ [_fileNameLabel setFont:[MDCTypography subheadFont]]; |
+ [_errorOrSizeLabel setFont:[MDCTypography captionFont]]; |
+ } else { |
+ [self.view addConstraints:_landscapeConstraintsArray]; |
+ |
+ [_fileTypeLabel setFont:[MDCTypography display1Font]]; |
+ [_timeLeftLabel setFont:[MDCTypography captionFont]]; |
+ [_fileNameLabel setFont:[MDCTypography body1Font]]; |
+ [_errorOrSizeLabel |
+ setFont:[[MDFRobotoFontLoader sharedInstance] regularFontOfSize:10]]; |
+ } |
+} |
+ |
+- (void)updatePortraitActionBarConstraints { |
+ [self removePortraitActionBarConstraints]; |
+ [self addPortraitActionBarConstraints]; |
+} |
+ |
+- (void)removePortraitActionBarConstraints { |
+ [self.view removeConstraints:_portraitActionBarOneButtonConstraintsArray]; |
+ [self.view removeConstraints:_portraitActionBarTwoButtonConstraintsArray]; |
+} |
+ |
+- (void)addPortraitActionBarConstraints { |
+ if ([_googleDriveButton isHidden] || _googleDriveButton.alpha == 0) { |
+ [self.view addConstraints:_portraitActionBarOneButtonConstraintsArray]; |
+ } else { |
+ [self.view addConstraints:_portraitActionBarTwoButtonConstraintsArray]; |
+ } |
+} |
+ |
+- (void)updateFileTypeLabelConstraint { |
+ CGFloat constant = kFileTypeLabelVerticalOffset; |
+ if (UIInterfaceOrientationIsPortrait( |
+ [[UIApplication sharedApplication] statusBarOrientation])) { |
+ if (!_isFileTypeLabelCentered) { |
+ constant += kFileTypeLabelAnimationDeltaYPortrait; |
+ } |
+ } else { |
+ if (!_isFileTypeLabelCentered) { |
+ constant += kFileTypeLabelAnimationDeltaYLandscape; |
+ } |
+ } |
+ self.fileTypeLabelCentered.constant = constant; |
+} |
+ |
+- (void)interfaceOrientationWillChangeNotification: |
+ (NSNotification*)notification { |
+ NSNumber* orientationNumber = [[notification userInfo] |
+ objectForKey:UIApplicationStatusBarOrientationUserInfoKey]; |
+ UIInterfaceOrientation orientation = |
+ static_cast<UIInterfaceOrientation>([orientationNumber integerValue]); |
+ [self updateConstraints:orientation]; |
+} |
+ |
+- (void)deviceOrientationDidChangeNotification:(NSNotification*)notification { |
+ [self setProgressBarHeightWithAnimation:NO withCompletionAnimation:NO]; |
+ [self updateFileTypeLabelConstraint]; |
+} |
+ |
+- (void)animateFileTypeLabelDown:(BOOL)down |
+ withCompletion:(void (^)(BOOL))completion { |
+ if (_isFileTypeLabelCentered == !down) { |
+ if (completion) { |
+ completion(YES); |
+ } |
+ return; |
+ } |
+ |
+ _isFileTypeLabelCentered = !down; |
+ [UIView animateWithDuration:kFileTypeLabelAnimationDuration |
+ delay:0 |
+ options:UIViewAnimationOptionCurveEaseInOut |
+ animations:^{ |
+ [self updateFileTypeLabelConstraint]; |
+ [self.view layoutIfNeeded]; |
+ } |
+ completion:^(BOOL finished) { |
+ if (completion) { |
+ completion(finished); |
+ } |
+ }]; |
+} |
+ |
+#pragma mark - Getting metadata |
+ |
+// TODO(fulbright): Investigate moving this code into -wasShown instead |
+// (-wasShown is currently not being called on this class even when it's |
+// implemented). |
+- (void)start { |
+ [self startHeadFetch]; |
+} |
+ |
+- (void)startHeadFetch { |
+ _fetcher = URLFetcher::Create([self url], URLFetcher::HEAD, |
+ _headFetcherDelegate.get()); |
+ _fetcher->SetRequestContext(_requestContextGetter.get()); |
+ [[NetworkActivityIndicatorManager sharedInstance] |
+ startNetworkTaskForGroup:[self getNetworkActivityKey]]; |
+ _fetcher->Start(); |
+} |
+ |
+- (void)onHeadFetchComplete { |
+ [[NetworkActivityIndicatorManager sharedInstance] |
+ stopNetworkTaskForGroup:[self getNetworkActivityKey]]; |
+ int responseCode = _fetcher->GetResponseCode(); |
+ UMA_HISTOGRAM_SPARSE_SLOWLY( |
+ kUMADownloadedFileStatusCode, |
+ net::HttpUtil::MapStatusCodeForHistogram(responseCode)); |
+ |
+ if (!_fetcher->GetStatus().is_success() || responseCode != 200) { |
+ [self displayError]; |
+ return; |
+ } |
+ |
+ _didSuccessfullyFinishHeadFetch = YES; |
+ HttpResponseHeaders* headers = _fetcher->GetResponseHeaders(); |
+ |
+ std::string contentDisposition; |
+ headers->GetNormalizedHeader("Content-Disposition", &contentDisposition); |
+ // TODO(fulbright): It's possible that the filename returned from |
+ // GetSuggestedFilename will already be in use (especially if "document" was |
+ // used in a previous download). Find a way to avoid filename collisions. |
+ // TODO(fulbright): Investigate why the comment on GetSuggestedFilename says |
+ // that |mime_type| should only be passed if it's called on a thread that |
+ // allows IO. |
+ base::string16 utilGeneratedFilename = |
+ net::GetSuggestedFilename(_fetcher->GetURL(), contentDisposition, |
+ "", // charset |
+ "", // "suggested" filename |
+ "", // mime type |
+ "document"); |
+ |
+ NSString* filename = base::SysUTF16ToNSString(utilGeneratedFilename); |
+ if (filename != nil && ![filename isEqualToString:@""]) { |
+ [_fileNameLabel setText:filename]; |
+ [_fileNameLabel setHidden:NO]; |
+ } |
+ |
+ NSString* fileType = [filename pathExtension]; |
+ if (fileType != nil && ![fileType isEqualToString:@""]) { |
+ [_fileTypeLabel setText:[fileType uppercaseString]]; |
+ [_fileTypeLabel setHidden:NO]; |
+ } |
+ |
+ int64_t length = headers->GetContentLength(); |
+ if (length > 0) { |
+ _totalFileSize = length; |
+ NSString* fileSizeText = [NSByteCountFormatter |
+ stringFromByteCount:length |
+ countStyle:NSByteCountFormatterCountStyleFile]; |
+ [_errorOrSizeLabel setText:fileSizeText]; |
+ } else { |
+ [_errorOrSizeLabel |
+ setText:l10n_util::GetNSString( |
+ IDS_IOS_DOWNLOAD_MANAGER_CANNOT_DETERMINE_FILE_SIZE)]; |
+ _totalFileSize = kNoFileSizeGiven; |
+ [self initializeActivityIndicator]; |
+ _documentContainer.backgroundColor = |
+ UIColorFromRGB(kIndeterminateFileSizeColor); |
+ _fileTypeLabel.textColor = |
+ [UIColor colorWithWhite:0 alpha:kFileTypeLabelBlackAlpha]; |
+ } |
+ [_errorOrSizeLabel setHidden:NO]; |
+ |
+ [_downloadButton setHidden:NO]; |
+} |
+ |
+- (void)initializeActivityIndicator { |
+ _activityIndicator.reset([[MDCActivityIndicator alloc] |
+ initWithFrame:CGRectMake(0, 0, kActivityIndicatorWidth, |
+ kActivityIndicatorWidth)]); |
+ [_activityIndicator setRadius:AlignValueToPixel(kActivityIndicatorWidth / 2)]; |
+ [_activityIndicator setStrokeWidth:4]; |
+ [_activityIndicator |
+ setCycleColors:@[ [[MDCPalette cr_bluePalette] tint500] ]]; |
+ [_activityIndicator setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [_documentContainer addSubview:_activityIndicator]; |
+ _activityIndicator.get().center = _documentContainer.center; |
+ [NSLayoutConstraint activateConstraints:@[ |
+ [[_activityIndicator centerYAnchor] |
+ constraintEqualToAnchor:_documentContainer.centerYAnchor], |
+ [[_activityIndicator centerXAnchor] |
+ constraintEqualToAnchor:_documentContainer.centerXAnchor], |
+ [[_activityIndicator heightAnchor] |
+ constraintEqualToConstant:kActivityIndicatorWidth], |
+ [[_activityIndicator widthAnchor] |
+ constraintEqualToConstant:kActivityIndicatorWidth] |
+ ]]; |
+} |
+ |
+#pragma mark - Errors |
+ |
+- (void)displayUnableToOpenFileDialog { |
+ // This code is called inside a xib file, I am using the topViewController. |
+ UIViewController* topViewController = |
+ [[[UIApplication sharedApplication] keyWindow] rootViewController]; |
+ |
+ NSString* title = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_UNABLE_TO_OPEN_FILE); |
+ NSString* message = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_NO_APP_MESSAGE); |
+ |
+ _alertCoordinator.reset([[AlertCoordinator alloc] |
+ initWithBaseViewController:topViewController |
+ title:title |
+ message:message]); |
+ |
+ // |googleDriveMetadata| contains the information necessary to either launch |
+ // |the Google Drive app or navigate to its StoreKit page. If the metadata is |
+ // |not present, do not show the upload button at all. |
+ if (self.googleDriveMetadata) { |
+ NSString* googleDriveButtonTitle = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_UPLOAD_TO_GOOGLE_DRIVE); |
+ base::WeakNSObject<DownloadManagerController> weakSelf(self); |
+ [_alertCoordinator addItemWithTitle:googleDriveButtonTitle |
+ action:^{ |
+ [weakSelf openGoogleDriveInAppStore]; |
+ } |
+ style:UIAlertActionStyleDefault]; |
+ } |
+ [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL) |
+ action:nil |
+ style:UIAlertActionStyleCancel]; |
+ |
+ [_alertCoordinator start]; |
+} |
+ |
+- (void)displayError { |
+ if (_recordDownloadResultHistogram) { |
+ _recordDownloadResultHistogram = NO; |
+ UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_FAILURE, |
+ DOWNLOAD_FILE_RESULT_COUNT); |
+ } |
+ [_activityIndicator stopAnimating]; |
+ _isDisplayingError = YES; |
+ self.fractionDownloaded = 0; |
+ if ([_fileTypeLabel isHidden]) { |
+ [self finishDisplayingError]; |
+ } else { |
+ [self animateFileTypeLabelDown:YES |
+ withCompletion:^(BOOL finished) { |
+ [self finishDisplayingError]; |
+ }]; |
+ } |
+} |
+ |
+- (void)finishDisplayingError { |
+ _timeLeftLabel.hidden = YES; |
+ _progressBar.hidden = YES; |
+ _foldIcon.image = [UIImage imageNamed:@"file_icon_fold"]; |
+ |
+ // Prepare the error icon for the fading in animation. |
+ [_errorIcon setAlpha:0.0]; |
+ [_errorIcon setHidden:NO]; |
+ |
+ // Prepare the "Download failed." label text for the fading in animation. |
+ NSString* errorText = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_FAILED); |
+ CATransition* errorTextAnimation = [CATransition animation]; |
+ errorTextAnimation.duration = kErrorAnimationDuration; |
+ [_documentContainer setAccessibilityLabel:errorText]; |
+ |
+ // Prepare the "RETRY DOWNLOAD" button text for the fading in animation. |
+ NSString* retryDownloadText = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_RETRY_DOWNLOAD); |
+ NSString* retryDownloadTextCaps = |
+ [retryDownloadText uppercaseStringWithLocale:[NSLocale currentLocale]]; |
+ _downloadButton.alpha = 0.0; |
+ _downloadButton.hidden = NO; |
+ [_downloadButton setTitle:retryDownloadTextCaps |
+ forState:UIControlStateNormal]; |
+ |
+ [UIView |
+ animateWithDuration:kErrorAnimationDuration |
+ animations:^{ |
+ [_errorIcon setAlpha:1.0]; |
+ [_documentContainer |
+ setBackgroundColor:UIColorFromRGB(kErrorDocumentColor)]; |
+ [_downloadButton setAlpha:1.0]; |
+ if (!_cancelButton.hidden) { |
+ [_cancelButton setAlpha:0.0]; |
+ } |
+ }]; |
+ |
+ _errorOrSizeLabel.hidden = NO; |
+ [_errorOrSizeLabel.layer addAnimation:errorTextAnimation |
+ forKey:@"displayError"]; |
+ [_errorOrSizeLabel setText:errorText]; |
+} |
+ |
+- (IBAction)downloadButtonTapped:(id)sender { |
+ _recordDownloadResultHistogram = YES; |
+ [_downloadButton setHidden:YES]; |
+ NSString* retryDownloadText = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_RETRY_DOWNLOAD); |
+ if ([_downloadButton.titleLabel.text |
+ caseInsensitiveCompare:retryDownloadText] == NSOrderedSame) { |
+ base::RecordAction(UserMetricsAction("MobileDownloadRetryDownload")); |
+ } |
+ // Hide any error message that may have been shown in a previous attempt to |
+ // download the file. |
+ if (_isDisplayingError) { |
+ [self hideError]; |
+ } else { |
+ [self finishDownloadButtonTapped]; |
+ } |
+} |
+ |
+- (void)hideError { |
+ // Get the file size string to display. |
+ NSString* fileSizeText; |
+ if (!_didSuccessfullyFinishHeadFetch) { |
+ // The file size hasn't been fetched yet, so just show a blank string. |
+ fileSizeText = @""; |
+ } else if (_totalFileSize == kNoFileSizeGiven) { |
+ fileSizeText = l10n_util::GetNSString( |
+ IDS_IOS_DOWNLOAD_MANAGER_CANNOT_DETERMINE_FILE_SIZE); |
+ } else { |
+ fileSizeText = [NSByteCountFormatter |
+ stringFromByteCount:_totalFileSize |
+ countStyle:NSByteCountFormatterCountStyleFile]; |
+ } |
+ CATransition* fileSizeTextAnimation = [CATransition animation]; |
+ fileSizeTextAnimation.duration = kErrorAnimationDuration; |
+ |
+ // Prepare to change the download button back to its original text. |
+ NSString* downloadText = |
+ l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD); |
+ NSString* downloadTextCaps = |
+ [downloadText uppercaseStringWithLocale:[NSLocale currentLocale]]; |
+ |
+ [_errorOrSizeLabel.layer addAnimation:fileSizeTextAnimation |
+ forKey:@"hideError"]; |
+ [_errorOrSizeLabel setText:fileSizeText]; |
+ |
+ if (![_fileTypeLabel isHidden] && _totalFileSize != kNoFileSizeGiven) { |
+ [self animateFileTypeLabelDown:NO withCompletion:nil]; |
+ } |
+ |
+ [UIView animateWithDuration:kErrorAnimationDuration |
+ animations:^{ |
+ _documentContainer.backgroundColor = |
+ UIColorFromRGB(kUndownloadedDocumentColor); |
+ _errorIcon.alpha = 0.0; |
+ _downloadButton.alpha = 0.0; |
+ } |
+ completion:^(BOOL finished) { |
+ _errorIcon.hidden = YES; |
+ _errorIcon.alpha = 1.0; |
+ _downloadButton.hidden = YES; |
+ _downloadButton.alpha = 1.0; |
+ [_downloadButton setTitle:downloadTextCaps |
+ forState:UIControlStateNormal]; |
+ _isDisplayingError = NO; |
+ [self finishDownloadButtonTapped]; |
+ }]; |
+} |
+ |
+- (void)finishDownloadButtonTapped { |
+ if (_didSuccessfullyFinishHeadFetch) { |
+ [self beginStartingContentDownload]; |
+ } else { |
+ [self startHeadFetch]; |
+ } |
+} |
+ |
+#pragma mark - Downloading content |
+ |
+- (void)beginStartingContentDownload { |
+ // Ensure the directory that downloaded files are saved to exists. |
+ base::FilePath downloadsDirectoryPath; |
+ if (![DownloadManagerController |
+ fetchDownloadsDirectoryFilePath:&downloadsDirectoryPath]) { |
+ [self displayError]; |
+ return; |
+ } |
+ base::WeakNSObject<DownloadManagerController> weakSelf(self); |
+ base::PostTaskAndReplyWithResult( |
+ web::WebThread::GetBlockingPool() |
+ ->GetTaskRunnerWithShutdownBehavior( |
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN) |
+ .get(), |
+ FROM_HERE, base::BindBlock(^{ |
+ return CreateDirectory(downloadsDirectoryPath); |
+ }), |
+ base::BindBlock(^(bool directoryCreated) { |
+ [weakSelf finishStartingContentDownload:directoryCreated]; |
+ })); |
+} |
+ |
+- (void)finishStartingContentDownload:(bool)directoryCreated { |
+ if (!directoryCreated) { |
+ [self displayError]; |
+ return; |
+ } |
+ base::FilePath downloadsDirectoryPath; |
+ if (![DownloadManagerController |
+ fetchDownloadsDirectoryFilePath:&downloadsDirectoryPath]) { |
+ [self displayError]; |
+ return; |
+ } |
+ _downloadFilePath = downloadsDirectoryPath.Append( |
+ base::SysNSStringToUTF8([_fileNameLabel text])); |
+ |
+ _fetcher = URLFetcher::Create([self url], URLFetcher::GET, |
+ _contentFetcherDelegate.get()); |
+ _fetcher->SetRequestContext(_requestContextGetter.get()); |
+ base::SequencedWorkerPool::SequenceToken sequenceToken = |
+ web::WebThread::GetBlockingPool()->GetSequenceToken(); |
+ _fetcher->SaveResponseToFileAtPath( |
+ _downloadFilePath, |
+ web::WebThread::GetBlockingPool()->GetSequencedTaskRunner(sequenceToken)); |
+ [[NetworkActivityIndicatorManager sharedInstance] |
+ startNetworkTaskForGroup:[self getNetworkActivityKey]]; |
+ _fetcher->Start(); |
+ |
+ self.downloadStartedTime = [NSDate date]; |
+ self.fractionDownloaded = 0.0; |
+ [self setProgressBarHeightWithAnimation:NO withCompletionAnimation:NO]; |
+ [self setTimeLeft:@"" withAnimation:NO]; |
+ [_progressBar setHidden:NO]; |
+ [_cancelButton setHidden:NO]; |
+ [_cancelButton setAlpha:1]; |
+} |
+ |
+- (void)setProgressBarHeightWithAnimation:(BOOL)animated |
+ withCompletionAnimation:(BOOL)completionAnimation { |
+ CGRect documentIconFrame = [_documentIcon frame]; |
+ CGRect oldProgressFrame = [_progressBar frame]; |
+ CGRect newProgressFrame = oldProgressFrame; |
+ newProgressFrame.size.height = |
+ self.fractionDownloaded * documentIconFrame.size.height; |
+ newProgressFrame.origin.y = |
+ CGRectGetMaxY(documentIconFrame) - newProgressFrame.size.height; |
+ if (animated && |
+ newProgressFrame.size.height - oldProgressFrame.size.height > 1) { |
+ base::WeakNSObject<UIView> weakProgressBar(_progressBar); |
+ if (completionAnimation) { |
+ base::WeakNSObject<DownloadManagerController> weakSelf(self); |
+ [UIView animateWithDuration:kProgressBarAnimationDuration |
+ animations:^{ |
+ [weakProgressBar setFrame:newProgressFrame]; |
+ } |
+ completion:^(BOOL) { |
+ [weakSelf runDownloadCompleteAnimation]; |
+ }]; |
+ } else { |
+ [UIView animateWithDuration:kProgressBarAnimationDuration |
+ animations:^{ |
+ [weakProgressBar setFrame:newProgressFrame]; |
+ }]; |
+ } |
+ } else { |
+ [_progressBar setFrame:newProgressFrame]; |
+ if (completionAnimation) { |
+ [self runDownloadCompleteAnimation]; |
+ } |
+ } |
+} |
+ |
+- (void)runDownloadCompleteAnimation { |
+ [_documentContainer |
+ setBackgroundColor:UIColorFromRGB(kDownloadedDocumentColor)]; |
+ |
+ // If Google Drive is not installed and the metadata is present that knows how |
+ // to install it, display the "Install Google Drive" button. |
+ NSString* driveAppId = base::SysUTF8ToNSString(kIOSAppStoreGoogleDrive); |
+ NSArray* matchingApps = |
+ [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() |
+ filteredAppsUsingBlock:^BOOL(const id<NativeAppMetadata> app, |
+ BOOL* stop) { |
+ if ([[app appId] isEqualToString:driveAppId]) { |
+ *stop = YES; |
+ return YES; |
+ } |
+ return NO; |
+ }]; |
+ BOOL showGoogleDriveButton = NO; |
+ if (matchingApps.count == 1U) { |
+ self.googleDriveMetadata = matchingApps[0]; |
+ showGoogleDriveButton = ![self.googleDriveMetadata isInstalled]; |
+ } |
+ |
+ CAKeyframeAnimation* animation = |
+ [CAKeyframeAnimation animationWithKeyPath:@"transform"]; |
+ NSArray* vals = @[ |
+ [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)], |
+ [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.95, 0.95, 1.0)], |
+ [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1.0)], |
+ [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)] |
+ ]; |
+ NSArray* times = @[ @0.0, @0.33, @0.66, @1.0 ]; |
+ [animation setValues:vals]; |
+ [animation setKeyTimes:times]; |
+ [animation setDuration:kDownloadCompleteAnimationDuration]; |
+ [_documentContainer.layer addAnimation:animation |
+ forKey:kDocumentPopAnimationKey]; |
+ |
+ base::WeakNSObject<UIImageView> weakFoldIcon(_foldIcon); |
+ [UIView transitionWithView:_foldIcon |
+ duration:kDownloadCompleteAnimationDuration |
+ options:UIViewAnimationOptionTransitionCrossDissolve |
+ animations:^{ |
+ [weakFoldIcon setImage:[UIImage imageNamed:@"file_icon_fold_complete"]]; |
+ if (_totalFileSize == kNoFileSizeGiven) { |
+ _fileTypeLabel.textColor = |
+ [UIColor colorWithWhite:1 alpha:kFileTypeLabelWhiteAlpha]; |
+ } |
+ } |
+ completion:^(BOOL finished) { |
+ if (showGoogleDriveButton) { |
+ [self showGoogleDriveButton]; |
+ } |
+ }]; |
+} |
+ |
+- (void)setFractionDownloaded:(double)fractionDownloaded { |
+ _fractionDownloaded = fractionDownloaded; |
+ if (_fractionDownloaded == 1) { |
+ [_documentContainer |
+ setAccessibilityLabel:l10n_util::GetNSString( |
+ IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_COMPLETE)]; |
+ } else { |
+ base::string16 percentDownloaded = base::SysNSStringToUTF16([NSString |
+ stringWithFormat:@"%i", static_cast<int>(100 * _fractionDownloaded)]); |
+ [_documentContainer |
+ setAccessibilityLabel:l10n_util::GetNSStringF( |
+ IDS_IOS_DOWNLOAD_MANAGER_PERCENT_DOWNLOADED, |
+ percentDownloaded)]; |
+ } |
+} |
+ |
+- (IBAction)cancelButtonTapped:(id)sender { |
+ // No-op if the file has finished downloading, which occurs if the download |
+ // completes while the user is pressing down on the Cancel button. |
+ if (self.fractionDownloaded == 1) |
+ return; |
+ |
+ self.fractionDownloaded = 0; |
+ [_cancelButton setHidden:YES]; |
+ [[NetworkActivityIndicatorManager sharedInstance] |
+ stopNetworkTaskForGroup:[self getNetworkActivityKey]]; |
+ if (_totalFileSize == kNoFileSizeGiven) { |
+ [_activityIndicator stopAnimating]; |
+ [self animateFileTypeLabelDown:NO withCompletion:nil]; |
+ } |
+ _fetcher.reset(); |
+ [_progressBar setHidden:YES]; |
+ [_timeLeftLabel setHidden:YES]; |
+ [_downloadButton setHidden:NO]; |
+ if (_recordDownloadResultHistogram) { |
+ _recordDownloadResultHistogram = NO; |
+ UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_CANCELED, |
+ DOWNLOAD_FILE_RESULT_COUNT); |
+ } |
+} |
+ |
+- (void)onContentFetchProgress:(long long)bytesDownloaded { |
+ if (_totalFileSize != kNoFileSizeGiven) { |
+ self.fractionDownloaded = |
+ static_cast<double>(bytesDownloaded) / _totalFileSize; |
+ BOOL showFinishingAnimation = (bytesDownloaded == _totalFileSize); |
+ [self setProgressBarHeightWithAnimation:YES |
+ withCompletionAnimation:showFinishingAnimation]; |
+ |
+ NSTimeInterval elapsedSeconds = |
+ -[self.downloadStartedTime timeIntervalSinceNow]; |
+ long long bytesRemaining = _totalFileSize - bytesDownloaded; |
+ DCHECK(bytesDownloaded > 0); |
+ double secondsRemaining = |
+ static_cast<double>(bytesRemaining) / bytesDownloaded * elapsedSeconds; |
+ double minutesRemaining = secondsRemaining / 60; |
+ int minutesRemainingInt = static_cast<int>(ceil(minutesRemaining)); |
+ base::string16 minutesRemainingString = base::SysNSStringToUTF16( |
+ [NSString stringWithFormat:@"%d", minutesRemainingInt]); |
+ NSString* minutesRemainingText = l10n_util::GetNSStringF( |
+ IDS_IOS_DOWNLOAD_MANAGER_TIME_LEFT, minutesRemainingString); |
+ [self setTimeLeft:minutesRemainingText withAnimation:YES]; |
+ [_timeLeftLabel setHidden:NO]; |
+ } else { |
+ [_documentContainer |
+ setAccessibilityLabel:l10n_util::GetNSString( |
+ IDS_IOS_DOWNLOAD_MANAGER_DOWNLOADING)]; |
+ [_activityIndicator startAnimating]; |
+ if (_isFileTypeLabelCentered) { |
+ [self animateFileTypeLabelDown:YES withCompletion:nil]; |
+ } |
+ } |
+} |
+ |
+- (void)setTimeLeft:(NSString*)text withAnimation:(BOOL)animated { |
+ if (![text isEqualToString:[_timeLeftLabel text]]) { |
+ if (animated) { |
+ CATransition* animation = [CATransition animation]; |
+ animation.duration = kTimeLeftAnimationDuration; |
+ animation.type = kCATransitionPush; |
+ animation.subtype = kCATransitionFromBottom; |
+ animation.timingFunction = [CAMediaTimingFunction |
+ functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; |
+ [_timeLeftLabel.layer addAnimation:animation |
+ forKey:kTimeLeftAnimationKey]; |
+ } |
+ [_timeLeftLabel setText:text]; |
+ } |
+} |
+ |
+- (void)onContentFetchComplete { |
+ [_cancelButton setHidden:YES]; |
+ [_timeLeftLabel setHidden:YES]; |
+ [[NetworkActivityIndicatorManager sharedInstance] |
+ stopNetworkTaskForGroup:[self getNetworkActivityKey]]; |
+ |
+ if (!_fetcher->GetStatus().is_success() || |
+ _fetcher->GetResponseCode() != 200) { |
+ [self displayError]; |
+ return; |
+ } |
+ |
+ base::FilePath filePath; |
+ bool success = _fetcher->GetResponseAsFilePath(YES, &filePath); |
+ DCHECK(success); |
+ NSString* filePathString = base::SysUTF8ToNSString(filePath.value()); |
+ |
+ if (_totalFileSize == kNoFileSizeGiven) { |
+ [_activityIndicator stopAnimating]; |
+ _activityIndicator.reset(); |
+ |
+ // Display the file size. |
+ NSError* error = nil; |
+ NSDictionary* attributes = |
+ [[NSFileManager defaultManager] attributesOfItemAtPath:filePathString |
+ error:&error]; |
+ if (!error) { |
+ NSNumber* fileSizeNumber = [attributes objectForKey:NSFileSize]; |
+ NSString* fileSizeText = [NSByteCountFormatter |
+ stringFromByteCount:[fileSizeNumber longLongValue] |
+ countStyle:NSByteCountFormatterCountStyleFile]; |
+ [_errorOrSizeLabel setText:fileSizeText]; |
+ } |
+ |
+ [self animateFileTypeLabelDown:NO withCompletion:nil]; |
+ // Set the progress bar to 100% to give the document the filled in blue |
+ // background. |
+ self.fractionDownloaded = 1.0; |
+ [self setProgressBarHeightWithAnimation:YES withCompletionAnimation:YES]; |
+ } |
+ |
+ NSURL* fileURL = [NSURL fileURLWithPath:filePathString]; |
+ self.docInteractionController = |
+ [UIDocumentInteractionController interactionControllerWithURL:fileURL]; |
+ self.docInteractionController.delegate = self; |
+ |
+ [_openInButton setHidden:NO]; |
+ |
+ _recordFileActionHistogram = YES; |
+ _recordDownloadResultHistogram = NO; |
+ UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_COMPLETED, |
+ DOWNLOAD_FILE_RESULT_COUNT); |
+} |
+ |
+#pragma mark - Completed download actions |
+ |
+- (void)showGoogleDriveButton { |
+ _googleDriveButton.alpha = 0.0; |
+ _googleDriveButton.hidden = NO; |
+ UIInterfaceOrientation orientation = |
+ [[UIApplication sharedApplication] statusBarOrientation]; |
+ if (UIInterfaceOrientationIsPortrait(orientation)) { |
+ [UIView animateWithDuration:kGoogleDriveButtonAnimationDuration |
+ animations:^{ |
+ _googleDriveButton.alpha = 1.0; |
+ [self updatePortraitActionBarConstraints]; |
+ [self.view layoutIfNeeded]; |
+ }]; |
+ } else { |
+ [UIView animateWithDuration:kGoogleDriveButtonAnimationDuration |
+ animations:^{ |
+ _googleDriveButton.alpha = 1.0; |
+ }]; |
+ } |
+} |
+ |
+- (void)hideGoogleDriveButton { |
+ base::RecordAction( |
+ UserMetricsAction("MobileDownloadFileUIInstallGoogleDrive")); |
+ UIInterfaceOrientation orientation = |
+ [[UIApplication sharedApplication] statusBarOrientation]; |
+ if (UIInterfaceOrientationIsPortrait(orientation)) { |
+ [UIView animateWithDuration:kDownloadCompleteAnimationDuration |
+ animations:^{ |
+ _googleDriveButton.alpha = 0.0; |
+ [self updatePortraitActionBarConstraints]; |
+ [self.view layoutIfNeeded]; |
+ }]; |
+ } else { |
+ [UIView animateWithDuration:kDownloadCompleteAnimationDuration |
+ animations:^{ |
+ _googleDriveButton.alpha = 0.0; |
+ }]; |
+ } |
+} |
+ |
+- (IBAction)openInButtonTapped:(id)sender { |
+ BOOL showedMenu = |
+ [_docInteractionController presentOpenInMenuFromRect:_openInButton.frame |
+ inView:_actionBar |
+ animated:YES]; |
+ if (!showedMenu) { |
+ [self displayUnableToOpenFileDialog]; |
+ } |
+} |
+ |
+- (IBAction)googleDriveButtonTapped:(id)sender { |
+ [self openGoogleDriveInAppStore]; |
+} |
+ |
+- (void)openGoogleDriveInAppStore { |
+ [[InstallationNotifier sharedInstance] |
+ registerForInstallationNotifications:self |
+ withSelector:@selector(hideGoogleDriveButton) |
+ forScheme:[_googleDriveMetadata anyScheme]]; |
+ |
+ [_storeKitLauncher openAppStore:[_googleDriveMetadata appId]]; |
+} |
+ |
+- (NSString*)getNetworkActivityKey { |
+ return |
+ [NSString stringWithFormat: |
+ @"DownloadManagerController.NetworkActivityIndicatorKey.%d", |
+ _downloadManagerId]; |
+} |
+ |
++ (BOOL)fetchDownloadsDirectoryFilePath:(base::FilePath*)path { |
+ if (!GetTempDir(path)) { |
+ return NO; |
+ } |
+ *path = path->Append("downloads"); |
+ return YES; |
+} |
+ |
++ (void)clearDownloadsDirectory { |
+ web::WebThread::PostBlockingPoolTask( |
+ FROM_HERE, base::BindBlock(^{ |
+ base::FilePath downloadsDirectory; |
+ if (![DownloadManagerController |
+ fetchDownloadsDirectoryFilePath:&downloadsDirectory]) { |
+ return; |
+ } |
+ DeleteFile(downloadsDirectory, true); |
+ })); |
+} |
+ |
+#pragma mark - UIDocumentInteractionControllerDelegate |
+ |
+- (void)documentInteractionController: |
+ (UIDocumentInteractionController*)controller |
+ willBeginSendingToApplication:(NSString*)application { |
+ if (_recordFileActionHistogram) { |
+ if ([application isEqualToString:kGoogleDriveBundleId]) { |
+ UMA_HISTOGRAM_ENUMERATION(kUMADownloadedFileAction, OPENED_IN_DRIVE, |
+ DOWNLOADED_FILE_ACTION_COUNT); |
+ } else { |
+ UMA_HISTOGRAM_ENUMERATION(kUMADownloadedFileAction, OPENED_IN_OTHER_APP, |
+ DOWNLOADED_FILE_ACTION_COUNT); |
+ } |
+ _recordFileActionHistogram = NO; |
+ } |
+} |
+ |
+#pragma mark - Methods for testing |
+ |
+- (long long)totalFileSize { |
+ return _totalFileSize; |
+} |
+ |
+#pragma mark - CRWNativeContent |
+ |
+- (void)close { |
+ // Makes sure that all outstanding network requests are shut down before |
+ // this controller is closed. |
+ _fetcher.reset(); |
+} |
+ |
+@end |