OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 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/app/safe_mode/safe_mode_view_controller.h" |
| 6 |
| 7 #import <QuartzCore/QuartzCore.h> |
| 8 |
| 9 #include "base/strings/sys_string_conversions.h" |
| 10 #import "ios/chrome/app/safe_mode_crashing_modules_config.h" |
| 11 #import "ios/chrome/app/safe_mode_util.h" |
| 12 #include "ios/chrome/browser/crash_report/breakpad_helper.h" |
| 13 #import "ios/chrome/browser/ui/fancy_ui/primary_action_button.h" |
| 14 #include "ios/chrome/browser/ui/ui_util.h" |
| 15 #import "ios/chrome/browser/ui/uikit_ui_util.h" |
| 16 #include "ios/chrome/grit/ios_chromium_strings.h" |
| 17 #import "ui/gfx/ios/NSString+CrStringDrawing.h" |
| 18 |
| 19 namespace { |
| 20 const CGFloat kVerticalSpacing = 20; |
| 21 const CGFloat kUploadProgressSpacing = 5; |
| 22 const NSTimeInterval kUploadPumpInterval = 0.1; |
| 23 const NSTimeInterval kUploadTotalTime = 5; |
| 24 } // anonymous namespace |
| 25 |
| 26 @interface SafeModeViewController () |
| 27 // Returns |YES| if any third-party modifications are detected. |
| 28 + (BOOL)detectedThirdPartyMods; |
| 29 // Returns |YES| if there are crash reports to upload. |
| 30 + (BOOL)hasReportToUpload; |
| 31 // Returns a message explaining which, if any, 3rd party modules were detected |
| 32 // that may cause Chrome to crash. |
| 33 - (NSString*)startupCrashModuleText; |
| 34 // Starts timer to update progress bar for crash report upload. |
| 35 - (void)startUploadProgress; |
| 36 // Updates progress bar for crash report upload. |
| 37 - (void)pumpUploadProgress; |
| 38 // Called when user taps on "Resume Chrome" button. Restores the default |
| 39 // Breakpad configuration and notifies the delegate to attempt to start the |
| 40 // browser. |
| 41 - (void)startBrowserFromSafeMode; |
| 42 @end |
| 43 |
| 44 @implementation SafeModeViewController |
| 45 |
| 46 - (id)initWithDelegate:(id<SafeModeViewControllerDelegate>)delegate { |
| 47 self = [super init]; |
| 48 if (self) { |
| 49 delegate_.reset(delegate); |
| 50 } |
| 51 return self; |
| 52 } |
| 53 |
| 54 + (BOOL)hasSuggestions { |
| 55 if ([SafeModeViewController detectedThirdPartyMods]) |
| 56 return YES; |
| 57 return [SafeModeViewController hasReportToUpload]; |
| 58 } |
| 59 |
| 60 + (BOOL)detectedThirdPartyMods { |
| 61 std::vector<std::string> thirdPartyMods = safe_mode_util::GetLoadedImages( |
| 62 "/Library/MobileSubstrate/DynamicLibraries/"); |
| 63 return (thirdPartyMods.size() > 0); |
| 64 } |
| 65 |
| 66 + (BOOL)hasReportToUpload { |
| 67 // If uploading is enabled and more than one report has stacked up, then we |
| 68 // assume that the app may be in a state that is preventing crash report |
| 69 // uploads before crashing again. |
| 70 return breakpad_helper::IsUploadingEnabled() && |
| 71 breakpad_helper::GetCrashReportCount() > 1; |
| 72 } |
| 73 |
| 74 // Return any jailbroken library that appears in SafeModeCrashingModulesConfig. |
| 75 - (NSArray*)startupCrashModules { |
| 76 std::vector<std::string> modules = safe_mode_util::GetLoadedImages( |
| 77 "/Library/MobileSubstrate/DynamicLibraries/"); |
| 78 NSMutableArray* array = [NSMutableArray arrayWithCapacity:modules.size()]; |
| 79 SafeModeCrashingModulesConfig* config = |
| 80 [SafeModeCrashingModulesConfig sharedInstance]; |
| 81 for (size_t i = 0; i < modules.size(); i++) { |
| 82 NSString* path = base::SysUTF8ToNSString(modules[i]); |
| 83 NSString* friendlyName = [config startupCrashModuleFriendlyName:path]; |
| 84 if (friendlyName != nil) |
| 85 [array addObject:friendlyName]; |
| 86 } |
| 87 return array; |
| 88 } |
| 89 |
| 90 // Since we are still supporting iOS5, this is a helper for basic flow layout. |
| 91 - (void)centerView:(UIView*)view afterView:(UIView*)afterView { |
| 92 CGPoint center = [view center]; |
| 93 center.x = [innerView_ frame].size.width / 2; |
| 94 [view setCenter:center]; |
| 95 |
| 96 if (afterView) { |
| 97 CGRect frame = view.frame; |
| 98 frame.origin.y = CGRectGetMaxY(afterView.frame) + kVerticalSpacing; |
| 99 view.frame = frame; |
| 100 } |
| 101 } |
| 102 |
| 103 - (NSString*)startupCrashModuleText { |
| 104 NSArray* knownModules = [self startupCrashModules]; |
| 105 if ([knownModules count]) { |
| 106 NSString* wrongText = |
| 107 NSLocalizedString(@"IDS_IOS_SAFE_MODE_NAMED_TWEAKS_FOUND", @""); |
| 108 NSMutableString* text = [NSMutableString stringWithString:wrongText]; |
| 109 [text appendString:@"\n"]; |
| 110 for (NSString* module in knownModules) { |
| 111 [text appendFormat:@"\n %@", module]; |
| 112 } |
| 113 return text; |
| 114 } else if ([SafeModeViewController detectedThirdPartyMods]) { |
| 115 return NSLocalizedString(@"IDS_IOS_SAFE_MODE_TWEAKS_FOUND", @""); |
| 116 } else { |
| 117 return NSLocalizedString(@"IDS_IOS_SAFE_MODE_UNKNOWN_CAUSE", @""); |
| 118 } |
| 119 } |
| 120 |
| 121 - (void)viewDidLoad { |
| 122 [super viewDidLoad]; |
| 123 |
| 124 // Width of the inner view on iPhone. |
| 125 const CGFloat kIPhoneWidth = 250; |
| 126 // Width of the inner view on iPad. |
| 127 const CGFloat kIPadWidth = 350; |
| 128 // Horizontal buffer. |
| 129 const CGFloat kHorizontalSpacing = 20; |
| 130 |
| 131 self.view.autoresizesSubviews = YES; |
| 132 CGRect mainBounds = [[UIScreen mainScreen] bounds]; |
| 133 // SafeModeViewController only supports portrait orientation (see |
| 134 // implementation of supportedInterfaceOrientations: below) but if the app is |
| 135 // launched from landscape mode (e.g. iPad or iPhone 6+) then the mainScreen's |
| 136 // bounds will still be landscape at this point. Swap the height and width |
| 137 // here so that the dimensions will be correct once the app rotates to |
| 138 // portrait. |
| 139 if (IsLandscape()) { |
| 140 mainBounds.size = CGSizeMake(mainBounds.size.height, mainBounds.size.width); |
| 141 } |
| 142 base::scoped_nsobject<UIScrollView> scrollView( |
| 143 [[UIScrollView alloc] initWithFrame:mainBounds]); |
| 144 self.view = scrollView; |
| 145 [self.view setBackgroundColor:[UIColor colorWithWhite:0.902 alpha:1.0]]; |
| 146 const CGFloat kIPadInset = |
| 147 (mainBounds.size.width - kIPadWidth - kHorizontalSpacing) / 2; |
| 148 const CGFloat widthInset = IsIPadIdiom() ? kIPadInset : kHorizontalSpacing; |
| 149 innerView_.reset([[UIView alloc] |
| 150 initWithFrame:CGRectInset(mainBounds, widthInset, kVerticalSpacing * 2)]); |
| 151 [innerView_ setBackgroundColor:[UIColor whiteColor]]; |
| 152 [innerView_ layer].cornerRadius = 3; |
| 153 [innerView_ layer].borderWidth = 1; |
| 154 [innerView_ layer].borderColor = |
| 155 [UIColor colorWithWhite:0.851 alpha:1.0].CGColor; |
| 156 [innerView_ layer].masksToBounds = YES; |
| 157 [scrollView addSubview:innerView_]; |
| 158 |
| 159 UIImage* fatalImage = [UIImage imageNamed:@"fatal_error.png"]; |
| 160 base::scoped_nsobject<UIImageView> imageView( |
| 161 [[UIImageView alloc] initWithImage:fatalImage]); |
| 162 // Shift the image down a bit. |
| 163 CGRect imageFrame = [imageView frame]; |
| 164 imageFrame.origin.y = kVerticalSpacing; |
| 165 [imageView setFrame:imageFrame]; |
| 166 [self centerView:imageView afterView:nil]; |
| 167 [innerView_ addSubview:imageView]; |
| 168 |
| 169 base::scoped_nsobject<UILabel> awSnap([[UILabel alloc] init]); |
| 170 [awSnap setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_AW_SNAP", @"")]; |
| 171 [awSnap setBackgroundColor:[UIColor clearColor]]; |
| 172 [awSnap setTextColor:[UIColor blackColor]]; |
| 173 [awSnap setFont:[UIFont boldSystemFontOfSize:21]]; |
| 174 [awSnap sizeToFit]; |
| 175 [self centerView:awSnap afterView:imageView]; |
| 176 [innerView_ addSubview:awSnap]; |
| 177 |
| 178 base::scoped_nsobject<UILabel> description([[UILabel alloc] init]); |
| 179 [description setText:[self startupCrashModuleText]]; |
| 180 [description setBackgroundColor:[UIColor clearColor]]; |
| 181 [description setTextColor:[UIColor colorWithWhite:0.31 alpha:1.0]]; |
| 182 [description setTextAlignment:NSTextAlignmentCenter]; |
| 183 [description setNumberOfLines:0]; |
| 184 [description setLineBreakMode:NSLineBreakByWordWrapping]; |
| 185 CGRect frame = [description frame]; |
| 186 frame.size.width = IsIPadIdiom() ? kIPadWidth : kIPhoneWidth; |
| 187 CGSize maxSize = CGSizeMake(frame.size.width, 999999.0f); |
| 188 frame.size.height = |
| 189 [[description text] cr_boundingSizeWithSize:maxSize |
| 190 font:[description font]] |
| 191 .height; |
| 192 [description setFrame:frame]; |
| 193 [self centerView:description afterView:awSnap]; |
| 194 [innerView_ addSubview:description]; |
| 195 |
| 196 startButton_.reset([[PrimaryActionButton alloc] init]); |
| 197 NSString* startText = |
| 198 NSLocalizedString(@"IDS_IOS_SAFE_MODE_RELOAD_CHROME", @""); |
| 199 [startButton_ setTitle:startText forState:UIControlStateNormal]; |
| 200 [startButton_ titleLabel].textAlignment = NSTextAlignmentCenter; |
| 201 [startButton_ titleLabel].lineBreakMode = NSLineBreakByWordWrapping; |
| 202 frame = [startButton_ frame]; |
| 203 frame.size.width = IsIPadIdiom() ? kIPadWidth : kIPhoneWidth; |
| 204 maxSize = CGSizeMake(frame.size.width, 999999.0f); |
| 205 const CGFloat kButtonBuffer = kVerticalSpacing / 2; |
| 206 CGSize startTextBoundingSize = |
| 207 [startText cr_boundingSizeWithSize:maxSize |
| 208 font:[startButton_ titleLabel].font]; |
| 209 frame.size.height = startTextBoundingSize.height + kButtonBuffer; |
| 210 [startButton_ setFrame:frame]; |
| 211 [startButton_ addTarget:self |
| 212 action:@selector(startBrowserFromSafeMode) |
| 213 forControlEvents:UIControlEventTouchUpInside]; |
| 214 [self centerView:startButton_ afterView:description]; |
| 215 [innerView_ addSubview:startButton_]; |
| 216 |
| 217 UIView* lastView = startButton_; |
| 218 if ([SafeModeViewController hasReportToUpload]) { |
| 219 breakpad_helper::StartUploadingReportsInRecoveryMode(); |
| 220 |
| 221 // If there are no jailbreak modifications, then present the "Sending crash |
| 222 // report..." UI. |
| 223 if (![SafeModeViewController detectedThirdPartyMods]) { |
| 224 [startButton_ setEnabled:NO]; |
| 225 |
| 226 uploadDescription_.reset([[UILabel alloc] init]); |
| 227 [uploadDescription_ |
| 228 setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_SENDING_CRASH_REPORT", |
| 229 @"")]; |
| 230 [uploadDescription_ setBackgroundColor:[UIColor clearColor]]; |
| 231 [uploadDescription_ setFont:[UIFont systemFontOfSize:13]]; |
| 232 [uploadDescription_ setTextColor:[UIColor colorWithWhite:0.31 alpha:1.0]]; |
| 233 [uploadDescription_ sizeToFit]; |
| 234 [self centerView:uploadDescription_ afterView:startButton_]; |
| 235 [innerView_ addSubview:uploadDescription_]; |
| 236 |
| 237 uploadProgress_.reset([[UIProgressView alloc] |
| 238 initWithProgressViewStyle:UIProgressViewStyleDefault]); |
| 239 [self centerView:uploadProgress_ afterView:nil]; |
| 240 frame = [uploadProgress_ frame]; |
| 241 frame.origin.y = |
| 242 CGRectGetMaxY([uploadDescription_ frame]) + kUploadProgressSpacing; |
| 243 [uploadProgress_ setFrame:frame]; |
| 244 [innerView_ addSubview:uploadProgress_]; |
| 245 |
| 246 lastView = uploadProgress_; |
| 247 [self startUploadProgress]; |
| 248 } |
| 249 } |
| 250 |
| 251 CGSize scrollSize = |
| 252 CGSizeMake(mainBounds.size.width, |
| 253 CGRectGetMaxY([lastView frame]) + kVerticalSpacing); |
| 254 frame = [innerView_ frame]; |
| 255 frame.size.height = scrollSize.height; |
| 256 [innerView_ setFrame:frame]; |
| 257 scrollSize.height += frame.origin.y; |
| 258 [scrollView setContentSize:scrollSize]; |
| 259 } |
| 260 |
| 261 - (NSUInteger)supportedInterfaceOrientations { |
| 262 return UIInterfaceOrientationMaskPortrait; |
| 263 } |
| 264 |
| 265 #pragma mark - Private |
| 266 |
| 267 - (void)startUploadProgress { |
| 268 uploadStartTime_.reset([[NSDate date] retain]); |
| 269 uploadTimer_.reset( |
| 270 [[NSTimer scheduledTimerWithTimeInterval:kUploadPumpInterval |
| 271 target:self |
| 272 selector:@selector(pumpUploadProgress) |
| 273 userInfo:nil |
| 274 repeats:YES] retain]); |
| 275 } |
| 276 |
| 277 - (void)pumpUploadProgress { |
| 278 NSTimeInterval elapsed = |
| 279 [[NSDate date] timeIntervalSinceDate:uploadStartTime_]; |
| 280 // Theoretically we could stop early when the value returned by |
| 281 // ios_internal::breakpad::GetCrashReportCount() changes, but this is |
| 282 // simpler. If we decide to look for a change in crash report count, then we |
| 283 // also probably want to replace the UIProgressView with a |
| 284 // UIActivityIndicatorView. |
| 285 if (elapsed <= kUploadTotalTime) { |
| 286 [uploadProgress_ setProgress:elapsed / kUploadTotalTime animated:YES]; |
| 287 } else { |
| 288 [uploadTimer_ invalidate]; |
| 289 |
| 290 [startButton_ setEnabled:YES]; |
| 291 [uploadDescription_ |
| 292 setText:NSLocalizedString(@"IDS_IOS_SAFE_MODE_CRASH_REPORT_SENT", |
| 293 @"")]; |
| 294 [uploadDescription_ sizeToFit]; |
| 295 [self centerView:uploadDescription_ afterView:startButton_]; |
| 296 [uploadProgress_ setHidden:YES]; |
| 297 } |
| 298 } |
| 299 |
| 300 - (void)startBrowserFromSafeMode { |
| 301 breakpad_helper::RestoreDefaultConfiguration(); |
| 302 [delegate_ startBrowserFromSafeMode]; |
| 303 } |
| 304 |
| 305 @end |
OLD | NEW |