Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(996)

Unified Diff: ios/chrome/browser/ui/downloads/download_manager_controller.mm

Issue 2588713002: Upstream Chrome on iOS source code [4/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698