OLD | NEW |
(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 |
OLD | NEW |