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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/ui/downloads/download_manager_controller.h"
6
7 #include <stdint.h>
8 #include <memory>
9
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/ios/weak_nsobject.h"
13 #include "base/location.h"
14 #include "base/mac/bind_objc_block.h"
15 #include "base/mac/objc_property_releaser.h"
16 #include "base/mac/scoped_nsobject.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/metrics/histogram.h"
19 #include "base/metrics/histogram_macros.h"
20 #include "base/metrics/user_metrics.h"
21 #include "base/metrics/user_metrics_action.h"
22 #include "base/strings/sys_string_conversions.h"
23 #include "base/threading/sequenced_worker_pool.h"
24 #include "components/strings/grit/components_strings.h"
25 #import "ios/chrome/browser/installation_notifier.h"
26 #include "ios/chrome/browser/native_app_launcher/ios_appstore_ids.h"
27 #import "ios/chrome/browser/storekit_launcher.h"
28 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
29 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
30 #import "ios/chrome/browser/ui/network_activity_indicator_manager.h"
31 #include "ios/chrome/browser/ui/ui_util.h"
32 #import "ios/chrome/browser/ui/uikit_ui_util.h"
33 #include "ios/chrome/grit/ios_strings.h"
34 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
35 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metad ata.h"
36 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_white list_manager.h"
37 #import "ios/third_party/material_components_ios/src/components/ActivityIndicato r/src/MaterialActivityIndicator.h"
38 #import "ios/third_party/material_components_ios/src/components/Buttons/src/Mate rialButtons.h"
39 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h"
40 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF ontLoader.h"
41 #include "ios/web/public/web_thread.h"
42 #include "net/base/filename_util.h"
43 #include "net/http/http_response_headers.h"
44 #include "net/http/http_util.h"
45 #include "net/url_request/url_fetcher.h"
46 #include "net/url_request/url_fetcher_delegate.h"
47 #include "net/url_request/url_request_context_getter.h"
48 #include "net/url_request/url_request_status.h"
49 #include "ui/base/l10n/l10n_util_mac.h"
50 #import "ui/gfx/ios/uikit_util.h"
51
52 using base::UserMetricsAction;
53 using net::HttpResponseHeaders;
54 using net::URLFetcher;
55 using net::URLFetcherDelegate;
56 using net::URLRequestContextGetter;
57 using net::URLRequestStatus;
58
59 @interface DownloadManagerController ()
60
61 // Creates the portrait and landscape mode constraints that are switched every
62 // time the interface is rotated.
63 - (void)initConstraints;
64
65 // Runs every time the interface switches between portrait and landscape mode,
66 // and applies the appropriate UI constraints for |orientation|.
67 - (void)updateConstraints:(UIInterfaceOrientation)orientation;
68
69 // Modifies (only) the constraints that anchor the action bar in portrait
70 // mode (there are different constraints for when the Google Drive
71 // button is showing versus not showing). This should only be called when
72 // the interface orientation is portrait.
73 - (void)updatePortraitActionBarConstraints;
74
75 // Removes all constraints that position the action bar in portrait mode.
76 - (void)removePortraitActionBarConstraints;
77
78 // Adds in the correct constraints to position the action bar in portrait mode,
79 // based on whether the Google Drive button is showing or not. This should only
80 // be called when the interface orientation is portrait.
81 - (void)addPortraitActionBarConstraints;
82
83 // Updates the file type label's vertical constraint constant to position the
84 // file type label depending on the device orientation and whether or not the
85 // error icon is displayed.
86 - (void)updateFileTypeLabelConstraint;
87
88 // Called every time the device orientation is about to change.
89 - (void)interfaceOrientationWillChangeNotification:
90 (NSNotification*)notification;
91
92 // Called every time the device orientation has changed.
93 - (void)deviceOrientationDidChangeNotification:(NSNotification*)notification;
94
95 // If |down| is YES, animates the file type label down. Otherwise animates
96 // the file type label up. Runs |completion| at the end of the animation.
97 - (void)animateFileTypeLabelDown:(BOOL)down
98 withCompletion:(void (^)(BOOL))completion;
99
100 // Initializes the URL fetcher to make a HEAD request to get information about
101 // the file to download and starts the network activity indicator.
102 - (void)startHeadFetch;
103
104 // Called when the HEAD request for the downloaded file returns and there is
105 // information about the file to display (size, type, etc.).
106 - (void)onHeadFetchComplete;
107
108 // Creates the activity indicator to animate while download a file with an
109 // unknown file size.
110 - (void)initializeActivityIndicator;
111
112 // Displays a dialog informing the user that no application on the device can
113 // open the file. The dialog contains a button for downloading Google Drive.
114 - (void)displayUnableToOpenFileDialog;
115
116 // Displays the error UI to the user, notifying them that their download
117 // failed. This method only ensures that the file type label is in the right
118 // place, then calls |finishDisplayingError| to do all the other work.
119 - (void)displayError;
120
121 // Updates the views to display the error UI.
122 - (void)finishDisplayingError;
123
124 // Handles clicks on the download button - also the only way to get out of the
125 // error flow.
126 - (IBAction)downloadButtonTapped:(id)sender;
127
128 // Undoes all the changes to the UI caused by calling |displayError|, and
129 // then calls |finishDownloadButtonTapped|.
130 - (void)hideError;
131
132 // In practice this will almost always call |beginStartingContentDownload|, but
133 // it will call |startHeadFetch| if the head fetch never completed successfully
134 // and led to the error flow.
135 - (void)finishDownloadButtonTapped;
136
137 // Begins the work start a download, including creating the downloads directory
138 - (void)beginStartingContentDownload;
139
140 // Called after an attempt to create the downloads directory on another
141 // thread has completed. |directoryCreated| is true if the creation was
142 // successful, or false otherwise.
143 - (void)finishStartingContentDownload:(bool)directoryCreated;
144
145 // Called when another chunk of the file has successfully been downloaded.
146 // |bytesDownloaded| gives the number of total bytes downloaded so far.
147 - (void)onContentFetchProgress:(long long)bytesDownloaded;
148
149 // Changes the height of the progress bar to be |self.fractionDownloaded| the
150 // height of the document icon, and adjusts its y-coordinate so its bottom
151 // always aligns with the bottom of the document icon. If |withAnimation| is
152 // YES, it will animate the change (as long as the change is > 1 point). If
153 // |withCompletionAnimation| is YES, the file download complete animation will
154 // run once the progress bar has finished being set.
155 - (void)setProgressBarHeightWithAnimation:(BOOL)animated
156 withCompletionAnimation:(BOOL)completionAnimation;
157
158 // Makes the file icon "pop" and fades the image in the fold icon to the
159 // completed fold icon. This will also call -showGoogleDriveButton if the user
160 // doesn't have Google Drive installed on their device.
161 - (void)runDownloadCompleteAnimation;
162
163 // Handles clicks on the cancel button.
164 - (IBAction)cancelButtonTapped:(id)sender;
165
166 // Sets the time left label to the given text. If |animated| is YES, it will
167 // animate the text change.
168 - (void)setTimeLeft:(NSString*)text withAnimation:(BOOL)animated;
169
170 // Called when the request for downloading the file has completed (successfully
171 // or not).
172 - (void)onContentFetchComplete;
173
174 // Animates the Google Drive button onto the screen after |delay| time has
175 // passed.
176 - (void)showGoogleDriveButton;
177
178 // Animates the Google Drive button off the screen.
179 - (void)hideGoogleDriveButton;
180
181 // Handles clicks on the open in button.
182 - (IBAction)openInButtonTapped:(id)sender;
183
184 // Opens a view controller that allows the user to install Google Drive.
185 - (void)openGoogleDriveInAppStore;
186
187 // Calls -openGoogleDriveInAppStore to allow the user to install Google Drive.
188 - (IBAction)googleDriveButtonTapped:(id)sender;
189
190 // Cleans up this DownloadManagerController, and deletes its file from the
191 // downloads directory if it has been created there.
192 - (void)dealloc;
193
194 // Returns an NSString* unique to |self|, to use with the
195 // NetworkActivityIndicatorManager.
196 - (NSString*)getNetworkActivityKey;
197
198 // Fills |path| with the FilePath to the downloads directory. Returns YES if
199 // this is successul, or NO otherwise.
200 + (BOOL)fetchDownloadsDirectoryFilePath:(base::FilePath*)path;
201
202 @end
203
204 namespace {
205
206 NSString* kGoogleDriveBundleId = @"com.google.Drive";
207
208 // Key of the UMA Download.IOSDownloadFileResult histogram.
209 const char* const kUMADownloadFileResult = "Download.IOSDownloadFileResult";
210 // Values of the UMA Download.IOSDownloadFileResult histogram.
211 enum DownloadFileResult {
212 DOWNLOAD_COMPLETED,
213 DOWNLOAD_CANCELED,
214 DOWNLOAD_FAILURE,
215 DOWNLOAD_OTHER,
216 DOWNLOAD_FILE_RESULT_COUNT
217 };
218
219 // Key of the UMA Download.IOSDownloadedFileAction histogram.
220 const char* const kUMADownloadedFileAction = "Download.IOSDownloadedFileAction";
221 // Values of the UMA Download.IOSDownloadedFileAction histogram.
222 enum DownloadedFileAction {
223 OPENED_IN_DRIVE,
224 OPENED_IN_OTHER_APP,
225 NO_ACTION,
226 DOWNLOADED_FILE_ACTION_COUNT
227 };
228
229 // Key of the UMA Download.IOSDownloadedFileStatusCode histogram.
230 const char kUMADownloadedFileStatusCode[] =
231 "Download.IOSDownloadedFileStatusCode";
232
233 int g_download_manager_id = 0;
234
235 const int kNoFileSizeGiven = -1;
236
237 const NSTimeInterval kFileTypeLabelAnimationDuration = 0.5;
238 const NSTimeInterval kErrorAnimationDuration = 0.5;
239
240 const NSTimeInterval kProgressBarAnimationDuration = 1.0;
241
242 const NSTimeInterval kDownloadCompleteAnimationDuration = 0.4;
243 NSString* const kDocumentPopAnimationKey = @"DocumentPopAnimationKey";
244
245 const NSTimeInterval kTimeLeftAnimationDuration = 0.25;
246 NSString* const kTimeLeftAnimationKey = @"TimeLeftAnimationKey";
247
248 const NSTimeInterval kGoogleDriveButtonAnimationDuration = 0.5;
249
250 const int kUndownloadedDocumentColor = 0x7BAAF8;
251 const int kDownloadedDocumentColor = 0x4285F5;
252 const int kErrorDocumentColor = 0xDB4437;
253 const int kIndeterminateFileSizeColor = 0xEAEAEA;
254
255 const CGFloat kFileTypeLabelWhiteAlpha = 0.87;
256 const CGFloat kFileTypeLabelBlackAlpha = 0.56;
257
258 const CGFloat kDocumentContainerWidthPortrait = 136;
259 const CGFloat kDocumentContainerHeightPortrait = 180;
260 const CGFloat kDocumentContainerCenterYOneButtonOffsetPortrait = -34;
261 const CGFloat kDocumentContainerCenterYTwoButtonOffsetPortrait = -52;
262
263 const CGFloat kDocumentContainerWidthLandscape = 82;
264 const CGFloat kDocumentContainerHeightLandscape = 109;
265 const CGFloat kDocumentContainerCenterYOffsetLandscape = -48;
266
267 const CGFloat kFoldIconWidthPortrait = 56;
268 const CGFloat kFoldIconHeightPortrait = 106;
269
270 const CGFloat kFoldIconWidthLandscape = 34;
271 const CGFloat kFoldIconHeightLandscape = 64;
272
273 const CGFloat kErrorIconWidthPortrait = 40;
274 const CGFloat kErrorIconHeightPortrait = 40;
275
276 const CGFloat kErrorIconWidthLandscape = 20;
277 const CGFloat kErrorIconHeightLandscape = 20;
278
279 const CGFloat kFileTypeLabelWidthDeltaPortrait = -24;
280 const CGFloat kFileTypeLabelHeightPortrait = 42;
281
282 const CGFloat kFileTypeLabelWidthDeltaLandscape = -16;
283 const CGFloat kFileTypeLabelHeightLandscape = 24;
284
285 const CGFloat kFileTypeLabelVerticalOffset = 10;
286 const CGFloat kFileTypeLabelAnimationDeltaYPortrait = 44;
287 const CGFloat kFileTypeLabelAnimationDeltaYLandscape = 27;
288
289 const CGFloat kTimeLeftLabelWidthDeltaPortrait = -24;
290 const CGFloat kTimeLeftLabelHeightPortrait = 16;
291 const CGFloat kTimeLeftLabelBottomSpacingOffsetPortrait = -12;
292
293 const CGFloat kTimeLeftLabelWidthDeltaLandscape = -16;
294 const CGFloat kTimeLeftLabelHeightLandscape = 14;
295 const CGFloat kTimeLeftLabelBottomSpacingOffsetLandscape = -8;
296
297 const CGFloat kFileNameLabelHeightPortrait = 20;
298 const CGFloat kFileNameLabelTopSpacingOffsetPortrait = 12;
299
300 const CGFloat kFileNameLabelHeightLandscape = 16;
301 const CGFloat kFileNameLabelTopSpacingOffsetLandscape = 8;
302
303 const CGFloat kErrorOrSizeLabelHeightPortrait = 16;
304 const CGFloat kErrorOrSizeLabelHeightLandscape = 14;
305
306 const CGFloat kActionBarHeightOneButtonPortrait = 68;
307 const CGFloat kActionBarHeightTwoButtonPortrait = 104;
308
309 const CGFloat kActionBarHeightLandscape = 52;
310
311 const CGFloat kActionBarBorderHeight = 0.5;
312
313 const CGFloat kActionBarButtonTopSpacingOffsetPortrait = 16;
314
315 const CGFloat kActionBarButtonTopSpacingOffsetLandscape = 8;
316
317 // This value is the same in portrait and landscape.
318 const CGFloat kActionBarButtonTrailingSpacingOffset = -16;
319
320 const CGFloat kActivityIndicatorWidth = 30;
321
322 // URLFetcher delegate that bridges from C++ to an Obj-C class for the HEAD
323 // request to get information about the file.
324 class DownloadHeadDelegate : public URLFetcherDelegate {
325 public:
326 explicit DownloadHeadDelegate(DownloadManagerController* owner)
327 : owner_(owner) {}
328 void OnURLFetchComplete(const URLFetcher* source) override {
329 [owner_ onHeadFetchComplete];
330 };
331
332 private:
333 DownloadManagerController* owner_; // weak.
334 DISALLOW_COPY_AND_ASSIGN(DownloadHeadDelegate);
335 };
336
337 // URLFetcher delegate that bridges from C++ to this Obj-C class for the
338 // request to download the contents of the file.
339 class DownloadContentDelegate : public URLFetcherDelegate {
340 public:
341 explicit DownloadContentDelegate(DownloadManagerController* owner)
342 : owner_(owner) {}
343 void OnURLFetchDownloadProgress(const URLFetcher* source,
344 int64_t current,
345 int64_t total,
346 int64_t current_network_bytes) override {
347 [owner_ onContentFetchProgress:current];
348 }
349 void OnURLFetchComplete(const URLFetcher* source) override {
350 [owner_ onContentFetchComplete];
351 };
352
353 private:
354 DownloadManagerController* owner_; // weak.
355 DISALLOW_COPY_AND_ASSIGN(DownloadContentDelegate);
356 };
357
358 } // namespace
359
360 @interface DownloadManagerController () {
361 int _downloadManagerId;
362
363 // Coordinator for displaying the alert informing the user that no application
364 // on the device can open the file.
365 base::scoped_nsobject<AlertCoordinator> _alertCoordinator;
366
367 // The size of the file to be downloaded, as determined by the Content-Length
368 // header field in the initial HEAD request. This is set to |kNoFileSizeGiven|
369 // if the Content-Length header is not given.
370 long long _totalFileSize;
371
372 // YES if |_fileTypeLabel| is vertically centered in |_documentContainer|. NO
373 // if |_fileTypeLabel| is lower to account for another view in
374 // |_documentContainer|.
375 BOOL _isFileTypeLabelCentered;
376 BOOL _isDisplayingError;
377 BOOL _didSuccessfullyFinishHeadFetch;
378 scoped_refptr<URLRequestContextGetter> _requestContextGetter;
379 std::unique_ptr<URLFetcher> _fetcher;
380 std::unique_ptr<DownloadHeadDelegate> _headFetcherDelegate;
381 std::unique_ptr<DownloadContentDelegate> _contentFetcherDelegate;
382 base::WeakNSProtocol<id<StoreKitLauncher>> _storeKitLauncher;
383 base::FilePath _downloadFilePath;
384 base::scoped_nsobject<MDCActivityIndicator> _activityIndicator;
385 // Set to YES when a download begins and is used to determine if the
386 // DownloadFileResult histogram needs to be recorded on -dealloc.
387 BOOL _recordDownloadResultHistogram;
388 // Set to YES when a file is downloaded and is used to determine if the
389 // DownloadedFileAction histogram needs to be recorded on -dealloc.
390 BOOL _recordFileActionHistogram;
391 base::mac::ObjCPropertyReleaser _propertyReleaser_DownloadManagerController;
392 }
393
394 // The container that holds the |documentIcon|, the |progressBar|, the
395 // |foldIcon|, the |fileTypeLabel|, and the |timeLeftLabel|.
396 @property(nonatomic, retain) IBOutlet UIView* documentContainer;
397
398 // The progress bar that displays download progress.
399 @property(nonatomic, retain) IBOutlet UIView* progressBar;
400
401 // The image of the document.
402 @property(nonatomic, retain) IBOutlet UIImageView* documentIcon;
403
404 // The image of the document fold.
405 @property(nonatomic, retain) IBOutlet UIImageView* foldIcon;
406
407 // The error image displayed inside the document.
408 @property(nonatomic, retain) IBOutlet UIImageView* errorIcon;
409
410 // The label that displays the file type of the file to be downloaded.
411 @property(nonatomic, retain) IBOutlet UILabel* fileTypeLabel;
412
413 // The label that displays the estimate of how much time is still needed to
414 // finish the file download.
415 @property(nonatomic, retain) IBOutlet UILabel* timeLeftLabel;
416
417 // The label that displays the name of the file to be downloaded, as it will
418 // be saved on the user's device.
419 @property(nonatomic, retain) IBOutlet UILabel* fileNameLabel;
420
421 // The label that displays the size of the file to be downloaded or the error
422 // message.
423 @property(nonatomic, retain) IBOutlet UILabel* errorOrSizeLabel;
424
425 // The label that displays error messages when errors occur.
426 @property(nonatomic, retain) IBOutlet UILabel* errorLabel;
427
428 // The container that holds the |downloadButton|, |cancelButton|,
429 // |openInButton|, and |googleDriveButton|.
430 @property(nonatomic, retain) IBOutlet UIView* actionBar;
431
432 // View that appears at the top of the action bar and acts as a border.
433 @property(nonatomic, retain) IBOutlet UIView* actionBarBorder;
434
435 // The button which starts the file download.
436 @property(nonatomic, retain) IBOutlet MDCButton* downloadButton;
437
438 // The button which switches with the |downloadButton| during a download.
439 // Pressing it cancels the download.
440 @property(nonatomic, retain) IBOutlet MDCButton* cancelButton;
441
442 // The button that switches with the |cancelButton| when a file download
443 // completes. Pressing it opens the UIDocumentInteractionController, letting
444 // the user select another app in which to open the downloaded file.
445 @property(nonatomic, retain) IBOutlet MDCButton* openInButton;
446
447 // The button that opens a view controller to allow the user to install
448 // Google Drive.
449 @property(nonatomic, retain) IBOutlet MDCButton* googleDriveButton;
450
451 // The controller that displays the list of other apps that the downloaded file
452 // can be opened in.
453 @property(nonatomic, retain)
454 UIDocumentInteractionController* docInteractionController;
455
456 // Contains all the constraints that should be applied only in portrait mode.
457 @property(nonatomic, retain) NSArray* portraitConstraintsArray;
458
459 // Contains all the constraints that should be applied only in landscape mode.
460 @property(nonatomic, retain) NSArray* landscapeConstraintsArray;
461
462 // Contains all the constraints that should be applied only in portrait mode
463 // when there is only one button showing in the action bar (i.e. the Google
464 // Drive button is NOT showing).
465 @property(nonatomic, retain)
466 NSArray* portraitActionBarOneButtonConstraintsArray;
467
468 // Contains all the constraints that should be applied only in portrait mode
469 // when there are two buttons showing in the action bar (i.e. the Google Drive
470 // button IS showing).
471 @property(nonatomic, retain)
472 NSArray* portraitActionBarTwoButtonConstraintsArray;
473
474 // Constraint that positions the file type label vertically in the center of the
475 // document with an additional offset.
476 @property(nonatomic, retain) NSLayoutConstraint* fileTypeLabelCentered;
477
478 // Records the time the download started, to display an estimate of how much
479 // time is required to finish the download.
480 @property(nonatomic, retain) NSDate* downloadStartedTime;
481
482 // Records the fraction (from 0.0 to 1.0) of the file that has been
483 // downloaded.
484 @property(nonatomic) double fractionDownloaded;
485
486 // Used to get a URL scheme that Drive responds to, to register with the
487 // InstallationNotifier.
488 @property(nonatomic, retain) id<NativeAppMetadata> googleDriveMetadata;
489
490 @end
491
492 @implementation DownloadManagerController
493
494 @synthesize documentContainer = _documentContainer;
495 @synthesize progressBar = _progressBar;
496 @synthesize documentIcon = _documentIcon;
497 @synthesize foldIcon = _foldIcon;
498 @synthesize errorIcon = _errorIcon;
499 @synthesize fileTypeLabel = _fileTypeLabel;
500 @synthesize timeLeftLabel = _timeLeftLabel;
501 @synthesize fileNameLabel = _fileNameLabel;
502 @synthesize errorOrSizeLabel = _errorOrSizeLabel;
503 @synthesize errorLabel = _errorLabel;
504 @synthesize actionBar = _actionBar;
505 @synthesize actionBarBorder = _actionBarBorder;
506 @synthesize downloadButton = _downloadButton;
507 @synthesize cancelButton = _cancelButton;
508 @synthesize openInButton = _openInButton;
509 @synthesize googleDriveButton = _googleDriveButton;
510 @synthesize docInteractionController = _docInteractionController;
511 @synthesize portraitConstraintsArray = _portraitConstraintsArray;
512 @synthesize landscapeConstraintsArray = _landscapeConstraintsArray;
513 @synthesize portraitActionBarOneButtonConstraintsArray =
514 _portraitActionBarOneButtonConstraintsArray;
515 @synthesize portraitActionBarTwoButtonConstraintsArray =
516 _portraitActionBarTwoButtonConstraintsArray;
517 @synthesize fileTypeLabelCentered = _fileTypeLabelCentered;
518 @synthesize downloadStartedTime = _downloadStartedTime;
519 @synthesize fractionDownloaded = _fractionDownloaded;
520 @synthesize googleDriveMetadata = _googleDriveMetadata;
521
522 - (id)initWithURL:(const GURL&)url
523 requestContextGetter:(URLRequestContextGetter*)requestContextGetter
524 storeKitLauncher:(id<StoreKitLauncher>)storeLauncher {
525 self = [super initWithNibName:@"DownloadManagerController" url:url];
526 if (self) {
527 _downloadManagerId = g_download_manager_id++;
528 _propertyReleaser_DownloadManagerController.Init(
529 self, [DownloadManagerController class]);
530
531 _requestContextGetter = requestContextGetter;
532 _headFetcherDelegate.reset(new DownloadHeadDelegate(self));
533 _contentFetcherDelegate.reset(new DownloadContentDelegate(self));
534 _downloadFilePath = base::FilePath();
535 _storeKitLauncher.reset(storeLauncher);
536
537 [_documentContainer
538 setBackgroundColor:UIColorFromRGB(kUndownloadedDocumentColor)];
539
540 _isFileTypeLabelCentered = YES;
541 _isDisplayingError = NO;
542 _didSuccessfullyFinishHeadFetch = NO;
543
544 NSString* downloadText =
545 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD);
546 NSString* capsDownloadText =
547 [downloadText uppercaseStringWithLocale:[NSLocale currentLocale]];
548 [_downloadButton setTitle:capsDownloadText forState:UIControlStateNormal];
549
550 NSString* cancelText = l10n_util::GetNSString(IDS_CANCEL);
551 NSString* capsCancelText =
552 [cancelText uppercaseStringWithLocale:[NSLocale currentLocale]];
553 [_cancelButton setTitle:capsCancelText forState:UIControlStateNormal];
554
555 NSString* openInText = l10n_util::GetNSString(IDS_IOS_OPEN_IN);
556 NSString* capsOpenInText =
557 [openInText uppercaseStringWithLocale:[NSLocale currentLocale]];
558 [_openInButton setTitle:capsOpenInText forState:UIControlStateNormal];
559
560 NSString* googleDriveText =
561 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_INSTALL_GOOGLE_DRIVE);
562 NSString* capsGoogleDriveText =
563 [googleDriveText uppercaseStringWithLocale:[NSLocale currentLocale]];
564 [_googleDriveButton setTitle:capsGoogleDriveText
565 forState:UIControlStateNormal];
566
567 [_documentContainer setIsAccessibilityElement:YES];
568 self.fractionDownloaded = 0;
569
570 // Constraints not in xib:
571 self.fileTypeLabelCentered = [_fileTypeLabel.centerYAnchor
572 constraintEqualToAnchor:_documentContainer.centerYAnchor
573 constant:kFileTypeLabelVerticalOffset];
574 self.fileTypeLabelCentered.active = YES;
575
576 // Action Bar Border
577 [_actionBarBorder.heightAnchor
578 constraintEqualToConstant:ui::AlignValueToUpperPixel(
579 kActionBarBorderHeight)]
580 .active = YES;
581
582 [self initConstraints];
583 UIInterfaceOrientation orientation =
584 [[UIApplication sharedApplication] statusBarOrientation];
585 [self updateConstraints:orientation];
586 [[NSNotificationCenter defaultCenter]
587 addObserver:self
588 selector:@selector(interfaceOrientationWillChangeNotification:)
589 name:UIApplicationWillChangeStatusBarOrientationNotification
590 object:nil];
591 [[NSNotificationCenter defaultCenter]
592 addObserver:self
593 selector:@selector(deviceOrientationDidChangeNotification:)
594 name:UIDeviceOrientationDidChangeNotification
595 object:nil];
596 base::RecordAction(UserMetricsAction("MobileDownloadFileUIShown"));
597 }
598 return self;
599 }
600
601 - (void)dealloc {
602 [[NSNotificationCenter defaultCenter]
603 removeObserver:self
604 name:UIApplicationWillChangeStatusBarOrientationNotification
605 object:nil];
606 [[NSNotificationCenter defaultCenter]
607 removeObserver:self
608 name:UIDeviceOrientationDidChangeNotification
609 object:nil];
610 [[InstallationNotifier sharedInstance] unregisterForNotifications:self];
611 [[NetworkActivityIndicatorManager sharedInstance]
612 clearNetworkTasksForGroup:[self getNetworkActivityKey]];
613 [_docInteractionController setDelegate:nil];
614 [_docInteractionController dismissMenuAnimated:NO];
615 if (!_downloadFilePath.empty()) {
616 // A local copy of _downloadFilePath must be made: the instance variable
617 // will be cleaned up during dealloc, but a local copy will be retained by
618 // the block and won't be deleted until the block completes.
619 base::FilePath downloadPathCopy = _downloadFilePath;
620 web::WebThread::PostBlockingPoolTask(FROM_HERE, base::BindBlock(^{
621 DeleteFile(downloadPathCopy, false);
622 }));
623 }
624 if (_recordDownloadResultHistogram) {
625 UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_OTHER,
626 DOWNLOAD_FILE_RESULT_COUNT);
627 }
628 if (_recordFileActionHistogram) {
629 UMA_HISTOGRAM_ENUMERATION(kUMADownloadedFileAction, NO_ACTION,
630 DOWNLOADED_FILE_ACTION_COUNT);
631 }
632 [super dealloc];
633 }
634
635 #pragma mark - Layout constraints
636
637 - (void)initConstraints {
638 // Document Container
639 NSLayoutConstraint* portraitDocumentContainerWidth =
640 [_documentContainer.widthAnchor
641 constraintEqualToConstant:kDocumentContainerWidthPortrait];
642 NSLayoutConstraint* portraitDocumentContainerHeight =
643 [_documentContainer.heightAnchor
644 constraintEqualToConstant:kDocumentContainerHeightPortrait];
645
646 // This constraint varies in portrait mode depending on whether the action
647 // bar is showing one button or two.
648 NSLayoutConstraint* portraitDocumentContainerCenterYOneButton =
649 [_documentContainer.centerYAnchor
650 constraintEqualToAnchor:self.view.centerYAnchor
651 constant:
652 kDocumentContainerCenterYOneButtonOffsetPortrait];
653 NSLayoutConstraint* portraitDocumentContainerCenterYTwoButton =
654 [_documentContainer.centerYAnchor
655 constraintEqualToAnchor:self.view.centerYAnchor
656 constant:
657 kDocumentContainerCenterYTwoButtonOffsetPortrait];
658
659 NSLayoutConstraint* landscapeDocumentContainerWidth =
660 [_documentContainer.widthAnchor
661 constraintEqualToConstant:kDocumentContainerWidthLandscape];
662 NSLayoutConstraint* landscapeDocumentContainerHeight =
663 [_documentContainer.heightAnchor
664 constraintEqualToConstant:kDocumentContainerHeightLandscape];
665 NSLayoutConstraint* landscapeDocumentContainerCenterY =
666 [_documentContainer.centerYAnchor
667 constraintEqualToAnchor:self.view.centerYAnchor
668 constant:kDocumentContainerCenterYOffsetLandscape];
669
670 // Fold Icon
671 NSLayoutConstraint* portraitFoldIconWidth =
672 [_foldIcon.widthAnchor constraintEqualToConstant:kFoldIconWidthPortrait];
673 NSLayoutConstraint* portraitFoldIconHeight = [_foldIcon.heightAnchor
674 constraintEqualToConstant:kFoldIconHeightPortrait];
675 NSLayoutConstraint* landscapeFoldIconWidth =
676 [_foldIcon.widthAnchor constraintEqualToConstant:kFoldIconWidthLandscape];
677 NSLayoutConstraint* landscapeFoldIconHeight = [_foldIcon.heightAnchor
678 constraintEqualToConstant:kFoldIconHeightLandscape];
679
680 // Error Icon
681 NSLayoutConstraint* portraitErrorIconWidth = [_errorIcon.widthAnchor
682 constraintEqualToConstant:kErrorIconWidthPortrait];
683 NSLayoutConstraint* portraitErrorIconHeight = [_errorIcon.heightAnchor
684 constraintEqualToConstant:kErrorIconHeightPortrait];
685
686 NSLayoutConstraint* landscapeErrorIconWidth = [_errorIcon.widthAnchor
687 constraintEqualToConstant:kErrorIconWidthLandscape];
688 NSLayoutConstraint* landscapeErrorIconHeight = [_errorIcon.heightAnchor
689 constraintEqualToConstant:kErrorIconHeightLandscape];
690
691 // File Type Label
692 NSLayoutConstraint* portraitFileTypeLabelWidth = [_fileTypeLabel.widthAnchor
693 constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor
694 constant:kFileTypeLabelWidthDeltaPortrait];
695 NSLayoutConstraint* portraitFileTypeLabelHeight = [_fileTypeLabel.heightAnchor
696 constraintEqualToConstant:kFileTypeLabelHeightPortrait];
697
698 NSLayoutConstraint* landscapeFileTypeLabelWidth = [_fileTypeLabel.widthAnchor
699 constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor
700 constant:kFileTypeLabelWidthDeltaLandscape];
701 NSLayoutConstraint* landscapeFileTypeLabelHeight =
702 [_fileTypeLabel.heightAnchor
703 constraintEqualToConstant:kFileTypeLabelHeightLandscape];
704
705 // Time Left Label
706 NSLayoutConstraint* portraitTimeLeftLabelWidth = [_timeLeftLabel.widthAnchor
707 constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor
708 constant:kTimeLeftLabelWidthDeltaPortrait];
709 NSLayoutConstraint* portraitTimeLeftLabelHeight = [_timeLeftLabel.heightAnchor
710 constraintEqualToConstant:kTimeLeftLabelHeightPortrait];
711 NSLayoutConstraint* portraitTimeLeftLabelBottomSpacing =
712 [_timeLeftLabel.bottomAnchor
713 constraintEqualToAnchor:_documentContainer.bottomAnchor
714 constant:kTimeLeftLabelBottomSpacingOffsetPortrait];
715
716 NSLayoutConstraint* landscapeTimeLeftLabelWidth = [_timeLeftLabel.widthAnchor
717 constraintLessThanOrEqualToAnchor:_documentContainer.widthAnchor
718 constant:kTimeLeftLabelWidthDeltaLandscape];
719 NSLayoutConstraint* landscapeTimeLeftLabelHeight =
720 [_timeLeftLabel.heightAnchor
721 constraintEqualToConstant:kTimeLeftLabelHeightLandscape];
722 NSLayoutConstraint* landscapeTimeLeftLabelBottomSpacing =
723 [_timeLeftLabel.bottomAnchor
724 constraintEqualToAnchor:_documentContainer.bottomAnchor
725 constant:kTimeLeftLabelBottomSpacingOffsetLandscape];
726
727 // File Name Label
728 NSLayoutConstraint* portraitFileNameLabelHeight = [_fileNameLabel.heightAnchor
729 constraintEqualToConstant:kFileNameLabelHeightPortrait];
730 NSLayoutConstraint* portraitFileNameLabelTopSpacing =
731 [_fileNameLabel.topAnchor
732 constraintEqualToAnchor:_documentContainer.bottomAnchor
733 constant:kFileNameLabelTopSpacingOffsetPortrait];
734
735 NSLayoutConstraint* landscapeFileNameLabelHeight =
736 [_fileNameLabel.heightAnchor
737 constraintEqualToConstant:kFileNameLabelHeightLandscape];
738 NSLayoutConstraint* landscapeFileNameLabelTopSpacing =
739 [_fileNameLabel.topAnchor
740 constraintEqualToAnchor:_documentContainer.bottomAnchor
741 constant:kFileNameLabelTopSpacingOffsetLandscape];
742
743 // File Size Label
744 NSLayoutConstraint* portraitFileSizeLabelHeight =
745 [_errorOrSizeLabel.heightAnchor
746 constraintEqualToConstant:kErrorOrSizeLabelHeightPortrait];
747 NSLayoutConstraint* landscapeFileSizeLabelHeight =
748 [_errorOrSizeLabel.heightAnchor
749 constraintEqualToConstant:kErrorOrSizeLabelHeightLandscape];
750
751 // Action Bar
752 // This constraint varies in portrait mode depending on whether the action
753 // bar is showing one button or two.
754 NSLayoutConstraint* portraitActionBarHeightOneButton =
755 [_actionBar.heightAnchor
756 constraintEqualToConstant:kActionBarHeightOneButtonPortrait];
757 NSLayoutConstraint* portraitActionBarHeightTwoButton =
758 [_actionBar.heightAnchor
759 constraintEqualToConstant:kActionBarHeightTwoButtonPortrait];
760
761 NSLayoutConstraint* landscapeActionBarHeight = [_actionBar.heightAnchor
762 constraintEqualToConstant:kActionBarHeightLandscape];
763
764 // Download Button
765 NSLayoutConstraint* portraitDownloadButtonTopSpacing =
766 [_downloadButton.topAnchor
767 constraintEqualToAnchor:_actionBar.topAnchor
768 constant:kActionBarButtonTopSpacingOffsetPortrait];
769 NSLayoutConstraint* landscapeDownloadButtonTopSpacing =
770 [_downloadButton.topAnchor
771 constraintEqualToAnchor:_actionBar.topAnchor
772 constant:kActionBarButtonTopSpacingOffsetLandscape];
773
774 // Cancel Button
775 NSLayoutConstraint* portraitCancelButtonTopSpacing = [_cancelButton.topAnchor
776 constraintEqualToAnchor:_actionBar.topAnchor
777 constant:kActionBarButtonTopSpacingOffsetPortrait];
778 NSLayoutConstraint* landscapeCancelButtonTopSpacing = [_cancelButton.topAnchor
779 constraintEqualToAnchor:_actionBar.topAnchor
780 constant:kActionBarButtonTopSpacingOffsetLandscape];
781
782 // Open In Button
783 NSLayoutConstraint* portraitOpenInButtonTopSpacing = [_openInButton.topAnchor
784 constraintEqualToAnchor:_actionBar.topAnchor
785 constant:kActionBarButtonTopSpacingOffsetPortrait];
786 NSLayoutConstraint* landscapeOpenInButtonTopSpacing = [_openInButton.topAnchor
787 constraintEqualToAnchor:_actionBar.topAnchor
788 constant:kActionBarButtonTopSpacingOffsetLandscape];
789
790 // Google Drive Button
791 NSLayoutConstraint* portraitGoogleDriveButtonTopAlignment =
792 [_googleDriveButton.topAnchor
793 constraintEqualToAnchor:_openInButton.bottomAnchor];
794 NSLayoutConstraint* portraitGoogleDriveButtonTrailingSpacing =
795 [_googleDriveButton.trailingAnchor
796 constraintEqualToAnchor:_actionBar.trailingAnchor
797 constant:kActionBarButtonTrailingSpacingOffset];
798
799 NSLayoutConstraint* landscapeGoogleDriveButtonTopSpacing =
800 [_googleDriveButton.topAnchor
801 constraintEqualToAnchor:_actionBar.topAnchor
802 constant:kActionBarButtonTopSpacingOffsetLandscape];
803 NSLayoutConstraint* landscapeGoogleDriveButtonTrailingAlignment =
804 [_googleDriveButton.trailingAnchor
805 constraintEqualToAnchor:_openInButton.leadingAnchor];
806
807 self.portraitConstraintsArray = @[
808 portraitDocumentContainerWidth, portraitDocumentContainerHeight,
809 portraitFoldIconWidth, portraitFoldIconHeight, portraitErrorIconWidth,
810 portraitErrorIconHeight, portraitFileTypeLabelWidth,
811 portraitFileTypeLabelHeight, portraitTimeLeftLabelWidth,
812 portraitTimeLeftLabelHeight, portraitTimeLeftLabelBottomSpacing,
813 portraitFileNameLabelHeight, portraitFileNameLabelTopSpacing,
814 portraitFileSizeLabelHeight, portraitDownloadButtonTopSpacing,
815 portraitCancelButtonTopSpacing, portraitOpenInButtonTopSpacing,
816 portraitGoogleDriveButtonTopAlignment,
817 portraitGoogleDriveButtonTrailingSpacing
818 ];
819 self.landscapeConstraintsArray = @[
820 landscapeDocumentContainerWidth,
821 landscapeDocumentContainerHeight,
822 landscapeDocumentContainerCenterY,
823 landscapeFoldIconWidth,
824 landscapeFoldIconHeight,
825 landscapeErrorIconWidth,
826 landscapeErrorIconHeight,
827 landscapeFileTypeLabelWidth,
828 landscapeFileTypeLabelHeight,
829 landscapeTimeLeftLabelWidth,
830 landscapeTimeLeftLabelHeight,
831 landscapeTimeLeftLabelBottomSpacing,
832 landscapeFileNameLabelHeight,
833 landscapeFileNameLabelTopSpacing,
834 landscapeFileSizeLabelHeight,
835 landscapeActionBarHeight,
836 landscapeDownloadButtonTopSpacing,
837 landscapeCancelButtonTopSpacing,
838 landscapeOpenInButtonTopSpacing,
839 landscapeGoogleDriveButtonTopSpacing,
840 landscapeGoogleDriveButtonTrailingAlignment
841 ];
842
843 self.portraitActionBarOneButtonConstraintsArray = @[
844 portraitDocumentContainerCenterYOneButton, portraitActionBarHeightOneButton
845 ];
846
847 self.portraitActionBarTwoButtonConstraintsArray = @[
848 portraitDocumentContainerCenterYTwoButton, portraitActionBarHeightTwoButton
849 ];
850 }
851
852 - (void)updateConstraints:(UIInterfaceOrientation)orientation {
853 [self.view removeConstraints:_portraitConstraintsArray];
854 [self.view removeConstraints:_landscapeConstraintsArray];
855 [self removePortraitActionBarConstraints];
856 if (UIInterfaceOrientationIsPortrait(orientation)) {
857 [self.view addConstraints:_portraitConstraintsArray];
858 [self addPortraitActionBarConstraints];
859
860 [_fileTypeLabel setFont:[MDCTypography display2Font]];
861 [_timeLeftLabel setFont:[MDCTypography body1Font]];
862 [_fileNameLabel setFont:[MDCTypography subheadFont]];
863 [_errorOrSizeLabel setFont:[MDCTypography captionFont]];
864 } else {
865 [self.view addConstraints:_landscapeConstraintsArray];
866
867 [_fileTypeLabel setFont:[MDCTypography display1Font]];
868 [_timeLeftLabel setFont:[MDCTypography captionFont]];
869 [_fileNameLabel setFont:[MDCTypography body1Font]];
870 [_errorOrSizeLabel
871 setFont:[[MDFRobotoFontLoader sharedInstance] regularFontOfSize:10]];
872 }
873 }
874
875 - (void)updatePortraitActionBarConstraints {
876 [self removePortraitActionBarConstraints];
877 [self addPortraitActionBarConstraints];
878 }
879
880 - (void)removePortraitActionBarConstraints {
881 [self.view removeConstraints:_portraitActionBarOneButtonConstraintsArray];
882 [self.view removeConstraints:_portraitActionBarTwoButtonConstraintsArray];
883 }
884
885 - (void)addPortraitActionBarConstraints {
886 if ([_googleDriveButton isHidden] || _googleDriveButton.alpha == 0) {
887 [self.view addConstraints:_portraitActionBarOneButtonConstraintsArray];
888 } else {
889 [self.view addConstraints:_portraitActionBarTwoButtonConstraintsArray];
890 }
891 }
892
893 - (void)updateFileTypeLabelConstraint {
894 CGFloat constant = kFileTypeLabelVerticalOffset;
895 if (UIInterfaceOrientationIsPortrait(
896 [[UIApplication sharedApplication] statusBarOrientation])) {
897 if (!_isFileTypeLabelCentered) {
898 constant += kFileTypeLabelAnimationDeltaYPortrait;
899 }
900 } else {
901 if (!_isFileTypeLabelCentered) {
902 constant += kFileTypeLabelAnimationDeltaYLandscape;
903 }
904 }
905 self.fileTypeLabelCentered.constant = constant;
906 }
907
908 - (void)interfaceOrientationWillChangeNotification:
909 (NSNotification*)notification {
910 NSNumber* orientationNumber = [[notification userInfo]
911 objectForKey:UIApplicationStatusBarOrientationUserInfoKey];
912 UIInterfaceOrientation orientation =
913 static_cast<UIInterfaceOrientation>([orientationNumber integerValue]);
914 [self updateConstraints:orientation];
915 }
916
917 - (void)deviceOrientationDidChangeNotification:(NSNotification*)notification {
918 [self setProgressBarHeightWithAnimation:NO withCompletionAnimation:NO];
919 [self updateFileTypeLabelConstraint];
920 }
921
922 - (void)animateFileTypeLabelDown:(BOOL)down
923 withCompletion:(void (^)(BOOL))completion {
924 if (_isFileTypeLabelCentered == !down) {
925 if (completion) {
926 completion(YES);
927 }
928 return;
929 }
930
931 _isFileTypeLabelCentered = !down;
932 [UIView animateWithDuration:kFileTypeLabelAnimationDuration
933 delay:0
934 options:UIViewAnimationOptionCurveEaseInOut
935 animations:^{
936 [self updateFileTypeLabelConstraint];
937 [self.view layoutIfNeeded];
938 }
939 completion:^(BOOL finished) {
940 if (completion) {
941 completion(finished);
942 }
943 }];
944 }
945
946 #pragma mark - Getting metadata
947
948 // TODO(fulbright): Investigate moving this code into -wasShown instead
949 // (-wasShown is currently not being called on this class even when it's
950 // implemented).
951 - (void)start {
952 [self startHeadFetch];
953 }
954
955 - (void)startHeadFetch {
956 _fetcher = URLFetcher::Create([self url], URLFetcher::HEAD,
957 _headFetcherDelegate.get());
958 _fetcher->SetRequestContext(_requestContextGetter.get());
959 [[NetworkActivityIndicatorManager sharedInstance]
960 startNetworkTaskForGroup:[self getNetworkActivityKey]];
961 _fetcher->Start();
962 }
963
964 - (void)onHeadFetchComplete {
965 [[NetworkActivityIndicatorManager sharedInstance]
966 stopNetworkTaskForGroup:[self getNetworkActivityKey]];
967 int responseCode = _fetcher->GetResponseCode();
968 UMA_HISTOGRAM_SPARSE_SLOWLY(
969 kUMADownloadedFileStatusCode,
970 net::HttpUtil::MapStatusCodeForHistogram(responseCode));
971
972 if (!_fetcher->GetStatus().is_success() || responseCode != 200) {
973 [self displayError];
974 return;
975 }
976
977 _didSuccessfullyFinishHeadFetch = YES;
978 HttpResponseHeaders* headers = _fetcher->GetResponseHeaders();
979
980 std::string contentDisposition;
981 headers->GetNormalizedHeader("Content-Disposition", &contentDisposition);
982 // TODO(fulbright): It's possible that the filename returned from
983 // GetSuggestedFilename will already be in use (especially if "document" was
984 // used in a previous download). Find a way to avoid filename collisions.
985 // TODO(fulbright): Investigate why the comment on GetSuggestedFilename says
986 // that |mime_type| should only be passed if it's called on a thread that
987 // allows IO.
988 base::string16 utilGeneratedFilename =
989 net::GetSuggestedFilename(_fetcher->GetURL(), contentDisposition,
990 "", // charset
991 "", // "suggested" filename
992 "", // mime type
993 "document");
994
995 NSString* filename = base::SysUTF16ToNSString(utilGeneratedFilename);
996 if (filename != nil && ![filename isEqualToString:@""]) {
997 [_fileNameLabel setText:filename];
998 [_fileNameLabel setHidden:NO];
999 }
1000
1001 NSString* fileType = [filename pathExtension];
1002 if (fileType != nil && ![fileType isEqualToString:@""]) {
1003 [_fileTypeLabel setText:[fileType uppercaseString]];
1004 [_fileTypeLabel setHidden:NO];
1005 }
1006
1007 int64_t length = headers->GetContentLength();
1008 if (length > 0) {
1009 _totalFileSize = length;
1010 NSString* fileSizeText = [NSByteCountFormatter
1011 stringFromByteCount:length
1012 countStyle:NSByteCountFormatterCountStyleFile];
1013 [_errorOrSizeLabel setText:fileSizeText];
1014 } else {
1015 [_errorOrSizeLabel
1016 setText:l10n_util::GetNSString(
1017 IDS_IOS_DOWNLOAD_MANAGER_CANNOT_DETERMINE_FILE_SIZE)];
1018 _totalFileSize = kNoFileSizeGiven;
1019 [self initializeActivityIndicator];
1020 _documentContainer.backgroundColor =
1021 UIColorFromRGB(kIndeterminateFileSizeColor);
1022 _fileTypeLabel.textColor =
1023 [UIColor colorWithWhite:0 alpha:kFileTypeLabelBlackAlpha];
1024 }
1025 [_errorOrSizeLabel setHidden:NO];
1026
1027 [_downloadButton setHidden:NO];
1028 }
1029
1030 - (void)initializeActivityIndicator {
1031 _activityIndicator.reset([[MDCActivityIndicator alloc]
1032 initWithFrame:CGRectMake(0, 0, kActivityIndicatorWidth,
1033 kActivityIndicatorWidth)]);
1034 [_activityIndicator setRadius:AlignValueToPixel(kActivityIndicatorWidth / 2)];
1035 [_activityIndicator setStrokeWidth:4];
1036 [_activityIndicator
1037 setCycleColors:@[ [[MDCPalette cr_bluePalette] tint500] ]];
1038 [_activityIndicator setTranslatesAutoresizingMaskIntoConstraints:NO];
1039 [_documentContainer addSubview:_activityIndicator];
1040 _activityIndicator.get().center = _documentContainer.center;
1041 [NSLayoutConstraint activateConstraints:@[
1042 [[_activityIndicator centerYAnchor]
1043 constraintEqualToAnchor:_documentContainer.centerYAnchor],
1044 [[_activityIndicator centerXAnchor]
1045 constraintEqualToAnchor:_documentContainer.centerXAnchor],
1046 [[_activityIndicator heightAnchor]
1047 constraintEqualToConstant:kActivityIndicatorWidth],
1048 [[_activityIndicator widthAnchor]
1049 constraintEqualToConstant:kActivityIndicatorWidth]
1050 ]];
1051 }
1052
1053 #pragma mark - Errors
1054
1055 - (void)displayUnableToOpenFileDialog {
1056 // This code is called inside a xib file, I am using the topViewController.
1057 UIViewController* topViewController =
1058 [[[UIApplication sharedApplication] keyWindow] rootViewController];
1059
1060 NSString* title =
1061 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_UNABLE_TO_OPEN_FILE);
1062 NSString* message =
1063 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_NO_APP_MESSAGE);
1064
1065 _alertCoordinator.reset([[AlertCoordinator alloc]
1066 initWithBaseViewController:topViewController
1067 title:title
1068 message:message]);
1069
1070 // |googleDriveMetadata| contains the information necessary to either launch
1071 // |the Google Drive app or navigate to its StoreKit page. If the metadata is
1072 // |not present, do not show the upload button at all.
1073 if (self.googleDriveMetadata) {
1074 NSString* googleDriveButtonTitle =
1075 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_UPLOAD_TO_GOOGLE_DRIVE);
1076 base::WeakNSObject<DownloadManagerController> weakSelf(self);
1077 [_alertCoordinator addItemWithTitle:googleDriveButtonTitle
1078 action:^{
1079 [weakSelf openGoogleDriveInAppStore];
1080 }
1081 style:UIAlertActionStyleDefault];
1082 }
1083 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
1084 action:nil
1085 style:UIAlertActionStyleCancel];
1086
1087 [_alertCoordinator start];
1088 }
1089
1090 - (void)displayError {
1091 if (_recordDownloadResultHistogram) {
1092 _recordDownloadResultHistogram = NO;
1093 UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_FAILURE,
1094 DOWNLOAD_FILE_RESULT_COUNT);
1095 }
1096 [_activityIndicator stopAnimating];
1097 _isDisplayingError = YES;
1098 self.fractionDownloaded = 0;
1099 if ([_fileTypeLabel isHidden]) {
1100 [self finishDisplayingError];
1101 } else {
1102 [self animateFileTypeLabelDown:YES
1103 withCompletion:^(BOOL finished) {
1104 [self finishDisplayingError];
1105 }];
1106 }
1107 }
1108
1109 - (void)finishDisplayingError {
1110 _timeLeftLabel.hidden = YES;
1111 _progressBar.hidden = YES;
1112 _foldIcon.image = [UIImage imageNamed:@"file_icon_fold"];
1113
1114 // Prepare the error icon for the fading in animation.
1115 [_errorIcon setAlpha:0.0];
1116 [_errorIcon setHidden:NO];
1117
1118 // Prepare the "Download failed." label text for the fading in animation.
1119 NSString* errorText =
1120 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_FAILED);
1121 CATransition* errorTextAnimation = [CATransition animation];
1122 errorTextAnimation.duration = kErrorAnimationDuration;
1123 [_documentContainer setAccessibilityLabel:errorText];
1124
1125 // Prepare the "RETRY DOWNLOAD" button text for the fading in animation.
1126 NSString* retryDownloadText =
1127 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_RETRY_DOWNLOAD);
1128 NSString* retryDownloadTextCaps =
1129 [retryDownloadText uppercaseStringWithLocale:[NSLocale currentLocale]];
1130 _downloadButton.alpha = 0.0;
1131 _downloadButton.hidden = NO;
1132 [_downloadButton setTitle:retryDownloadTextCaps
1133 forState:UIControlStateNormal];
1134
1135 [UIView
1136 animateWithDuration:kErrorAnimationDuration
1137 animations:^{
1138 [_errorIcon setAlpha:1.0];
1139 [_documentContainer
1140 setBackgroundColor:UIColorFromRGB(kErrorDocumentColor)];
1141 [_downloadButton setAlpha:1.0];
1142 if (!_cancelButton.hidden) {
1143 [_cancelButton setAlpha:0.0];
1144 }
1145 }];
1146
1147 _errorOrSizeLabel.hidden = NO;
1148 [_errorOrSizeLabel.layer addAnimation:errorTextAnimation
1149 forKey:@"displayError"];
1150 [_errorOrSizeLabel setText:errorText];
1151 }
1152
1153 - (IBAction)downloadButtonTapped:(id)sender {
1154 _recordDownloadResultHistogram = YES;
1155 [_downloadButton setHidden:YES];
1156 NSString* retryDownloadText =
1157 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_RETRY_DOWNLOAD);
1158 if ([_downloadButton.titleLabel.text
1159 caseInsensitiveCompare:retryDownloadText] == NSOrderedSame) {
1160 base::RecordAction(UserMetricsAction("MobileDownloadRetryDownload"));
1161 }
1162 // Hide any error message that may have been shown in a previous attempt to
1163 // download the file.
1164 if (_isDisplayingError) {
1165 [self hideError];
1166 } else {
1167 [self finishDownloadButtonTapped];
1168 }
1169 }
1170
1171 - (void)hideError {
1172 // Get the file size string to display.
1173 NSString* fileSizeText;
1174 if (!_didSuccessfullyFinishHeadFetch) {
1175 // The file size hasn't been fetched yet, so just show a blank string.
1176 fileSizeText = @"";
1177 } else if (_totalFileSize == kNoFileSizeGiven) {
1178 fileSizeText = l10n_util::GetNSString(
1179 IDS_IOS_DOWNLOAD_MANAGER_CANNOT_DETERMINE_FILE_SIZE);
1180 } else {
1181 fileSizeText = [NSByteCountFormatter
1182 stringFromByteCount:_totalFileSize
1183 countStyle:NSByteCountFormatterCountStyleFile];
1184 }
1185 CATransition* fileSizeTextAnimation = [CATransition animation];
1186 fileSizeTextAnimation.duration = kErrorAnimationDuration;
1187
1188 // Prepare to change the download button back to its original text.
1189 NSString* downloadText =
1190 l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD);
1191 NSString* downloadTextCaps =
1192 [downloadText uppercaseStringWithLocale:[NSLocale currentLocale]];
1193
1194 [_errorOrSizeLabel.layer addAnimation:fileSizeTextAnimation
1195 forKey:@"hideError"];
1196 [_errorOrSizeLabel setText:fileSizeText];
1197
1198 if (![_fileTypeLabel isHidden] && _totalFileSize != kNoFileSizeGiven) {
1199 [self animateFileTypeLabelDown:NO withCompletion:nil];
1200 }
1201
1202 [UIView animateWithDuration:kErrorAnimationDuration
1203 animations:^{
1204 _documentContainer.backgroundColor =
1205 UIColorFromRGB(kUndownloadedDocumentColor);
1206 _errorIcon.alpha = 0.0;
1207 _downloadButton.alpha = 0.0;
1208 }
1209 completion:^(BOOL finished) {
1210 _errorIcon.hidden = YES;
1211 _errorIcon.alpha = 1.0;
1212 _downloadButton.hidden = YES;
1213 _downloadButton.alpha = 1.0;
1214 [_downloadButton setTitle:downloadTextCaps
1215 forState:UIControlStateNormal];
1216 _isDisplayingError = NO;
1217 [self finishDownloadButtonTapped];
1218 }];
1219 }
1220
1221 - (void)finishDownloadButtonTapped {
1222 if (_didSuccessfullyFinishHeadFetch) {
1223 [self beginStartingContentDownload];
1224 } else {
1225 [self startHeadFetch];
1226 }
1227 }
1228
1229 #pragma mark - Downloading content
1230
1231 - (void)beginStartingContentDownload {
1232 // Ensure the directory that downloaded files are saved to exists.
1233 base::FilePath downloadsDirectoryPath;
1234 if (![DownloadManagerController
1235 fetchDownloadsDirectoryFilePath:&downloadsDirectoryPath]) {
1236 [self displayError];
1237 return;
1238 }
1239 base::WeakNSObject<DownloadManagerController> weakSelf(self);
1240 base::PostTaskAndReplyWithResult(
1241 web::WebThread::GetBlockingPool()
1242 ->GetTaskRunnerWithShutdownBehavior(
1243 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)
1244 .get(),
1245 FROM_HERE, base::BindBlock(^{
1246 return CreateDirectory(downloadsDirectoryPath);
1247 }),
1248 base::BindBlock(^(bool directoryCreated) {
1249 [weakSelf finishStartingContentDownload:directoryCreated];
1250 }));
1251 }
1252
1253 - (void)finishStartingContentDownload:(bool)directoryCreated {
1254 if (!directoryCreated) {
1255 [self displayError];
1256 return;
1257 }
1258 base::FilePath downloadsDirectoryPath;
1259 if (![DownloadManagerController
1260 fetchDownloadsDirectoryFilePath:&downloadsDirectoryPath]) {
1261 [self displayError];
1262 return;
1263 }
1264 _downloadFilePath = downloadsDirectoryPath.Append(
1265 base::SysNSStringToUTF8([_fileNameLabel text]));
1266
1267 _fetcher = URLFetcher::Create([self url], URLFetcher::GET,
1268 _contentFetcherDelegate.get());
1269 _fetcher->SetRequestContext(_requestContextGetter.get());
1270 base::SequencedWorkerPool::SequenceToken sequenceToken =
1271 web::WebThread::GetBlockingPool()->GetSequenceToken();
1272 _fetcher->SaveResponseToFileAtPath(
1273 _downloadFilePath,
1274 web::WebThread::GetBlockingPool()->GetSequencedTaskRunner(sequenceToken));
1275 [[NetworkActivityIndicatorManager sharedInstance]
1276 startNetworkTaskForGroup:[self getNetworkActivityKey]];
1277 _fetcher->Start();
1278
1279 self.downloadStartedTime = [NSDate date];
1280 self.fractionDownloaded = 0.0;
1281 [self setProgressBarHeightWithAnimation:NO withCompletionAnimation:NO];
1282 [self setTimeLeft:@"" withAnimation:NO];
1283 [_progressBar setHidden:NO];
1284 [_cancelButton setHidden:NO];
1285 [_cancelButton setAlpha:1];
1286 }
1287
1288 - (void)setProgressBarHeightWithAnimation:(BOOL)animated
1289 withCompletionAnimation:(BOOL)completionAnimation {
1290 CGRect documentIconFrame = [_documentIcon frame];
1291 CGRect oldProgressFrame = [_progressBar frame];
1292 CGRect newProgressFrame = oldProgressFrame;
1293 newProgressFrame.size.height =
1294 self.fractionDownloaded * documentIconFrame.size.height;
1295 newProgressFrame.origin.y =
1296 CGRectGetMaxY(documentIconFrame) - newProgressFrame.size.height;
1297 if (animated &&
1298 newProgressFrame.size.height - oldProgressFrame.size.height > 1) {
1299 base::WeakNSObject<UIView> weakProgressBar(_progressBar);
1300 if (completionAnimation) {
1301 base::WeakNSObject<DownloadManagerController> weakSelf(self);
1302 [UIView animateWithDuration:kProgressBarAnimationDuration
1303 animations:^{
1304 [weakProgressBar setFrame:newProgressFrame];
1305 }
1306 completion:^(BOOL) {
1307 [weakSelf runDownloadCompleteAnimation];
1308 }];
1309 } else {
1310 [UIView animateWithDuration:kProgressBarAnimationDuration
1311 animations:^{
1312 [weakProgressBar setFrame:newProgressFrame];
1313 }];
1314 }
1315 } else {
1316 [_progressBar setFrame:newProgressFrame];
1317 if (completionAnimation) {
1318 [self runDownloadCompleteAnimation];
1319 }
1320 }
1321 }
1322
1323 - (void)runDownloadCompleteAnimation {
1324 [_documentContainer
1325 setBackgroundColor:UIColorFromRGB(kDownloadedDocumentColor)];
1326
1327 // If Google Drive is not installed and the metadata is present that knows how
1328 // to install it, display the "Install Google Drive" button.
1329 NSString* driveAppId = base::SysUTF8ToNSString(kIOSAppStoreGoogleDrive);
1330 NSArray* matchingApps =
1331 [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager()
1332 filteredAppsUsingBlock:^BOOL(const id<NativeAppMetadata> app,
1333 BOOL* stop) {
1334 if ([[app appId] isEqualToString:driveAppId]) {
1335 *stop = YES;
1336 return YES;
1337 }
1338 return NO;
1339 }];
1340 BOOL showGoogleDriveButton = NO;
1341 if (matchingApps.count == 1U) {
1342 self.googleDriveMetadata = matchingApps[0];
1343 showGoogleDriveButton = ![self.googleDriveMetadata isInstalled];
1344 }
1345
1346 CAKeyframeAnimation* animation =
1347 [CAKeyframeAnimation animationWithKeyPath:@"transform"];
1348 NSArray* vals = @[
1349 [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)],
1350 [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.95, 0.95, 1.0)],
1351 [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1.0)],
1352 [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)]
1353 ];
1354 NSArray* times = @[ @0.0, @0.33, @0.66, @1.0 ];
1355 [animation setValues:vals];
1356 [animation setKeyTimes:times];
1357 [animation setDuration:kDownloadCompleteAnimationDuration];
1358 [_documentContainer.layer addAnimation:animation
1359 forKey:kDocumentPopAnimationKey];
1360
1361 base::WeakNSObject<UIImageView> weakFoldIcon(_foldIcon);
1362 [UIView transitionWithView:_foldIcon
1363 duration:kDownloadCompleteAnimationDuration
1364 options:UIViewAnimationOptionTransitionCrossDissolve
1365 animations:^{
1366 [weakFoldIcon setImage:[UIImage imageNamed:@"file_icon_fold_complete"]];
1367 if (_totalFileSize == kNoFileSizeGiven) {
1368 _fileTypeLabel.textColor =
1369 [UIColor colorWithWhite:1 alpha:kFileTypeLabelWhiteAlpha];
1370 }
1371 }
1372 completion:^(BOOL finished) {
1373 if (showGoogleDriveButton) {
1374 [self showGoogleDriveButton];
1375 }
1376 }];
1377 }
1378
1379 - (void)setFractionDownloaded:(double)fractionDownloaded {
1380 _fractionDownloaded = fractionDownloaded;
1381 if (_fractionDownloaded == 1) {
1382 [_documentContainer
1383 setAccessibilityLabel:l10n_util::GetNSString(
1384 IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_COMPLETE)];
1385 } else {
1386 base::string16 percentDownloaded = base::SysNSStringToUTF16([NSString
1387 stringWithFormat:@"%i", static_cast<int>(100 * _fractionDownloaded)]);
1388 [_documentContainer
1389 setAccessibilityLabel:l10n_util::GetNSStringF(
1390 IDS_IOS_DOWNLOAD_MANAGER_PERCENT_DOWNLOADED,
1391 percentDownloaded)];
1392 }
1393 }
1394
1395 - (IBAction)cancelButtonTapped:(id)sender {
1396 // No-op if the file has finished downloading, which occurs if the download
1397 // completes while the user is pressing down on the Cancel button.
1398 if (self.fractionDownloaded == 1)
1399 return;
1400
1401 self.fractionDownloaded = 0;
1402 [_cancelButton setHidden:YES];
1403 [[NetworkActivityIndicatorManager sharedInstance]
1404 stopNetworkTaskForGroup:[self getNetworkActivityKey]];
1405 if (_totalFileSize == kNoFileSizeGiven) {
1406 [_activityIndicator stopAnimating];
1407 [self animateFileTypeLabelDown:NO withCompletion:nil];
1408 }
1409 _fetcher.reset();
1410 [_progressBar setHidden:YES];
1411 [_timeLeftLabel setHidden:YES];
1412 [_downloadButton setHidden:NO];
1413 if (_recordDownloadResultHistogram) {
1414 _recordDownloadResultHistogram = NO;
1415 UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_CANCELED,
1416 DOWNLOAD_FILE_RESULT_COUNT);
1417 }
1418 }
1419
1420 - (void)onContentFetchProgress:(long long)bytesDownloaded {
1421 if (_totalFileSize != kNoFileSizeGiven) {
1422 self.fractionDownloaded =
1423 static_cast<double>(bytesDownloaded) / _totalFileSize;
1424 BOOL showFinishingAnimation = (bytesDownloaded == _totalFileSize);
1425 [self setProgressBarHeightWithAnimation:YES
1426 withCompletionAnimation:showFinishingAnimation];
1427
1428 NSTimeInterval elapsedSeconds =
1429 -[self.downloadStartedTime timeIntervalSinceNow];
1430 long long bytesRemaining = _totalFileSize - bytesDownloaded;
1431 DCHECK(bytesDownloaded > 0);
1432 double secondsRemaining =
1433 static_cast<double>(bytesRemaining) / bytesDownloaded * elapsedSeconds;
1434 double minutesRemaining = secondsRemaining / 60;
1435 int minutesRemainingInt = static_cast<int>(ceil(minutesRemaining));
1436 base::string16 minutesRemainingString = base::SysNSStringToUTF16(
1437 [NSString stringWithFormat:@"%d", minutesRemainingInt]);
1438 NSString* minutesRemainingText = l10n_util::GetNSStringF(
1439 IDS_IOS_DOWNLOAD_MANAGER_TIME_LEFT, minutesRemainingString);
1440 [self setTimeLeft:minutesRemainingText withAnimation:YES];
1441 [_timeLeftLabel setHidden:NO];
1442 } else {
1443 [_documentContainer
1444 setAccessibilityLabel:l10n_util::GetNSString(
1445 IDS_IOS_DOWNLOAD_MANAGER_DOWNLOADING)];
1446 [_activityIndicator startAnimating];
1447 if (_isFileTypeLabelCentered) {
1448 [self animateFileTypeLabelDown:YES withCompletion:nil];
1449 }
1450 }
1451 }
1452
1453 - (void)setTimeLeft:(NSString*)text withAnimation:(BOOL)animated {
1454 if (![text isEqualToString:[_timeLeftLabel text]]) {
1455 if (animated) {
1456 CATransition* animation = [CATransition animation];
1457 animation.duration = kTimeLeftAnimationDuration;
1458 animation.type = kCATransitionPush;
1459 animation.subtype = kCATransitionFromBottom;
1460 animation.timingFunction = [CAMediaTimingFunction
1461 functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
1462 [_timeLeftLabel.layer addAnimation:animation
1463 forKey:kTimeLeftAnimationKey];
1464 }
1465 [_timeLeftLabel setText:text];
1466 }
1467 }
1468
1469 - (void)onContentFetchComplete {
1470 [_cancelButton setHidden:YES];
1471 [_timeLeftLabel setHidden:YES];
1472 [[NetworkActivityIndicatorManager sharedInstance]
1473 stopNetworkTaskForGroup:[self getNetworkActivityKey]];
1474
1475 if (!_fetcher->GetStatus().is_success() ||
1476 _fetcher->GetResponseCode() != 200) {
1477 [self displayError];
1478 return;
1479 }
1480
1481 base::FilePath filePath;
1482 bool success = _fetcher->GetResponseAsFilePath(YES, &filePath);
1483 DCHECK(success);
1484 NSString* filePathString = base::SysUTF8ToNSString(filePath.value());
1485
1486 if (_totalFileSize == kNoFileSizeGiven) {
1487 [_activityIndicator stopAnimating];
1488 _activityIndicator.reset();
1489
1490 // Display the file size.
1491 NSError* error = nil;
1492 NSDictionary* attributes =
1493 [[NSFileManager defaultManager] attributesOfItemAtPath:filePathString
1494 error:&error];
1495 if (!error) {
1496 NSNumber* fileSizeNumber = [attributes objectForKey:NSFileSize];
1497 NSString* fileSizeText = [NSByteCountFormatter
1498 stringFromByteCount:[fileSizeNumber longLongValue]
1499 countStyle:NSByteCountFormatterCountStyleFile];
1500 [_errorOrSizeLabel setText:fileSizeText];
1501 }
1502
1503 [self animateFileTypeLabelDown:NO withCompletion:nil];
1504 // Set the progress bar to 100% to give the document the filled in blue
1505 // background.
1506 self.fractionDownloaded = 1.0;
1507 [self setProgressBarHeightWithAnimation:YES withCompletionAnimation:YES];
1508 }
1509
1510 NSURL* fileURL = [NSURL fileURLWithPath:filePathString];
1511 self.docInteractionController =
1512 [UIDocumentInteractionController interactionControllerWithURL:fileURL];
1513 self.docInteractionController.delegate = self;
1514
1515 [_openInButton setHidden:NO];
1516
1517 _recordFileActionHistogram = YES;
1518 _recordDownloadResultHistogram = NO;
1519 UMA_HISTOGRAM_ENUMERATION(kUMADownloadFileResult, DOWNLOAD_COMPLETED,
1520 DOWNLOAD_FILE_RESULT_COUNT);
1521 }
1522
1523 #pragma mark - Completed download actions
1524
1525 - (void)showGoogleDriveButton {
1526 _googleDriveButton.alpha = 0.0;
1527 _googleDriveButton.hidden = NO;
1528 UIInterfaceOrientation orientation =
1529 [[UIApplication sharedApplication] statusBarOrientation];
1530 if (UIInterfaceOrientationIsPortrait(orientation)) {
1531 [UIView animateWithDuration:kGoogleDriveButtonAnimationDuration
1532 animations:^{
1533 _googleDriveButton.alpha = 1.0;
1534 [self updatePortraitActionBarConstraints];
1535 [self.view layoutIfNeeded];
1536 }];
1537 } else {
1538 [UIView animateWithDuration:kGoogleDriveButtonAnimationDuration
1539 animations:^{
1540 _googleDriveButton.alpha = 1.0;
1541 }];
1542 }
1543 }
1544
1545 - (void)hideGoogleDriveButton {
1546 base::RecordAction(
1547 UserMetricsAction("MobileDownloadFileUIInstallGoogleDrive"));
1548 UIInterfaceOrientation orientation =
1549 [[UIApplication sharedApplication] statusBarOrientation];
1550 if (UIInterfaceOrientationIsPortrait(orientation)) {
1551 [UIView animateWithDuration:kDownloadCompleteAnimationDuration
1552 animations:^{
1553 _googleDriveButton.alpha = 0.0;
1554 [self updatePortraitActionBarConstraints];
1555 [self.view layoutIfNeeded];
1556 }];
1557 } else {
1558 [UIView animateWithDuration:kDownloadCompleteAnimationDuration
1559 animations:^{
1560 _googleDriveButton.alpha = 0.0;
1561 }];
1562 }
1563 }
1564
1565 - (IBAction)openInButtonTapped:(id)sender {
1566 BOOL showedMenu =
1567 [_docInteractionController presentOpenInMenuFromRect:_openInButton.frame
1568 inView:_actionBar
1569 animated:YES];
1570 if (!showedMenu) {
1571 [self displayUnableToOpenFileDialog];
1572 }
1573 }
1574
1575 - (IBAction)googleDriveButtonTapped:(id)sender {
1576 [self openGoogleDriveInAppStore];
1577 }
1578
1579 - (void)openGoogleDriveInAppStore {
1580 [[InstallationNotifier sharedInstance]
1581 registerForInstallationNotifications:self
1582 withSelector:@selector(hideGoogleDriveButton)
1583 forScheme:[_googleDriveMetadata anyScheme]];
1584
1585 [_storeKitLauncher openAppStore:[_googleDriveMetadata appId]];
1586 }
1587
1588 - (NSString*)getNetworkActivityKey {
1589 return
1590 [NSString stringWithFormat:
1591 @"DownloadManagerController.NetworkActivityIndicatorKey.%d",
1592 _downloadManagerId];
1593 }
1594
1595 + (BOOL)fetchDownloadsDirectoryFilePath:(base::FilePath*)path {
1596 if (!GetTempDir(path)) {
1597 return NO;
1598 }
1599 *path = path->Append("downloads");
1600 return YES;
1601 }
1602
1603 + (void)clearDownloadsDirectory {
1604 web::WebThread::PostBlockingPoolTask(
1605 FROM_HERE, base::BindBlock(^{
1606 base::FilePath downloadsDirectory;
1607 if (![DownloadManagerController
1608 fetchDownloadsDirectoryFilePath:&downloadsDirectory]) {
1609 return;
1610 }
1611 DeleteFile(downloadsDirectory, true);
1612 }));
1613 }
1614
1615 #pragma mark - UIDocumentInteractionControllerDelegate
1616
1617 - (void)documentInteractionController:
1618 (UIDocumentInteractionController*)controller
1619 willBeginSendingToApplication:(NSString*)application {
1620 if (_recordFileActionHistogram) {
1621 if ([application isEqualToString:kGoogleDriveBundleId]) {
1622 UMA_HISTOGRAM_ENUMERATION(kUMADownloadedFileAction, OPENED_IN_DRIVE,
1623 DOWNLOADED_FILE_ACTION_COUNT);
1624 } else {
1625 UMA_HISTOGRAM_ENUMERATION(kUMADownloadedFileAction, OPENED_IN_OTHER_APP,
1626 DOWNLOADED_FILE_ACTION_COUNT);
1627 }
1628 _recordFileActionHistogram = NO;
1629 }
1630 }
1631
1632 #pragma mark - Methods for testing
1633
1634 - (long long)totalFileSize {
1635 return _totalFileSize;
1636 }
1637
1638 #pragma mark - CRWNativeContent
1639
1640 - (void)close {
1641 // Makes sure that all outstanding network requests are shut down before
1642 // this controller is closed.
1643 _fetcher.reset();
1644 }
1645
1646 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698