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

Side by Side Diff: ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.mm

Issue 2589583003: Upstream Chrome on iOS source code [7/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
« no previous file with comments | « no previous file | ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 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/qr_scanner/qr_scanner_view_controller.h"
6
7 #import <AVFoundation/AVFoundation.h>
8
9 #include "base/logging.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/metrics/user_metrics.h"
12 #include "base/metrics/user_metrics_action.h"
13 #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_alerts.h"
14 #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_transitioning_delegate.h"
15 #include "ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h"
16 #include "ios/chrome/grit/ios_strings.h"
17 #include "ui/base/l10n/l10n_util.h"
18
19 using base::UserMetricsAction;
20
21 namespace {
22
23 // The reason why the QRScannerViewController was dismissed. Used for collecting
24 // metrics.
25 enum DismissalReason {
26 CLOSE_BUTTON,
27 ERROR_DIALOG,
28 SCANNED_CODE,
29 // Not reported. Should be kept last of enum.
30 IMPOSSIBLY_UNLIKELY_AUTHORIZATION_CHANGE
31 };
32
33 } // namespace
34
35 @interface QRScannerViewController ()<QRScannerViewDelegate> {
36 // The CameraController managing the camera connection.
37 base::scoped_nsobject<CameraController> _cameraController;
38 // The view displaying the QR scanner.
39 base::scoped_nsobject<QRScannerView> _qrScannerView;
40 // The scanned result.
41 base::scoped_nsobject<NSString> _result;
42 // Whether the scanned result should be immediately loaded.
43 BOOL _loadResultImmediately;
44 // The transitioning delegate used for presenting and dismissing the QR
45 // scanner.
46 base::scoped_nsobject<QRScannerTransitioningDelegate> _transitioningDelegate;
47 }
48
49 // Dismisses the QRScannerViewController and runs |completion| on completion.
50 // Logs metrics according to the |reason| for dismissal.
51 - (void)dismissForReason:(DismissalReason)reason
52 withCompletion:(void (^)(void))completion;
53 // Starts receiving notifications about the UIApplication going to background.
54 - (void)startReceivingNotifications;
55 // Stops receiving all notificatins.
56 - (void)stopReceivingNotifications;
57 // Requests the torch mode to be set to |mode| by the |_cameraController| and
58 // the icon of the torch button to be changed by the |_qrScannerView|.
59 - (void)setTorchMode:(AVCaptureTorchMode)mode;
60
61 // Stops recording when the application resigns active.
62 - (void)handleUIApplicationWillResignActiveNotification;
63 // Dismisses the QR scanner and passes the scanned result to the delegate when
64 // the accessibility announcement for scanned QR code finishes.
65 - (void)handleUIAccessibilityAnnouncementDidFinishNotification:
66 (NSNotification*)notification;
67
68 @end
69
70 @implementation QRScannerViewController
71
72 @synthesize delegate = _delegate;
73
74 #pragma mark lifecycle
75
76 - (instancetype)initWithDelegate:(id<QRScannerViewControllerDelegate>)delegate {
77 self = [super initWithNibName:nil bundle:nil];
78 if (self) {
79 DCHECK(delegate);
80 _delegate = delegate;
81 _cameraController.reset([[CameraController alloc] initWithDelegate:self]);
82 }
83 return self;
84 }
85
86 - (instancetype)initWithNibName:(NSString*)name bundle:(NSBundle*)bundle {
87 NOTREACHED();
88 return nil;
89 }
90
91 - (instancetype)initWithCoder:(NSCoder*)coder {
92 NOTREACHED();
93 return nil;
94 }
95
96 #pragma mark UIAccessibilityAction
97
98 - (BOOL)accessibilityPerformEscape {
99 [self dismissForReason:CLOSE_BUTTON withCompletion:nil];
100 return YES;
101 }
102
103 #pragma mark UIViewController
104
105 - (void)viewDidLoad {
106 [super viewDidLoad];
107 DCHECK(_cameraController);
108
109 _qrScannerView.reset(
110 [[QRScannerView alloc] initWithFrame:self.view.frame delegate:self]);
111 [self.view addSubview:_qrScannerView];
112
113 // Constraints for |_qrScannerView|.
114 [_qrScannerView setTranslatesAutoresizingMaskIntoConstraints:NO];
115 [NSLayoutConstraint activateConstraints:@[
116 [[_qrScannerView leadingAnchor]
117 constraintEqualToAnchor:[self.view leadingAnchor]],
118 [[_qrScannerView trailingAnchor]
119 constraintEqualToAnchor:[self.view trailingAnchor]],
120 [[_qrScannerView topAnchor] constraintEqualToAnchor:[self.view topAnchor]],
121 [[_qrScannerView bottomAnchor]
122 constraintEqualToAnchor:[self.view bottomAnchor]],
123 ]];
124
125 AVCaptureVideoPreviewLayer* previewLayer = [_qrScannerView getPreviewLayer];
126 switch ([_cameraController getAuthorizationStatus]) {
127 case AVAuthorizationStatusNotDetermined:
128 [_cameraController
129 requestAuthorizationAndLoadCaptureSession:previewLayer];
130 break;
131 case AVAuthorizationStatusAuthorized:
132 [_cameraController loadCaptureSession:previewLayer];
133 break;
134 case AVAuthorizationStatusRestricted:
135 case AVAuthorizationStatusDenied:
136 // If this happens, then the user is really unlucky:
137 // The authorization status changed in between the moment this VC was
138 // instantiated and presented, and the moment viewDidLoad was called.
139 [self dismissForReason:IMPOSSIBLY_UNLIKELY_AUTHORIZATION_CHANGE
140 withCompletion:nil];
141 break;
142 }
143 }
144
145 - (void)viewWillAppear:(BOOL)animated {
146 [super viewWillAppear:animated];
147 [self startReceivingNotifications];
148 [_cameraController startRecording];
149
150 // Reset torch.
151 [self setTorchMode:AVCaptureTorchModeOff];
152 }
153
154 - (void)viewWillTransitionToSize:(CGSize)size
155 withTransitionCoordinator:
156 (id<UIViewControllerTransitionCoordinator>)coordinator {
157 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
158 CGFloat epsilon = 0.0001;
159 // Note: targetTransform is always either identity or a 90, -90, or 180 degree
160 // rotation.
161 CGAffineTransform targetTransform = coordinator.targetTransform;
162 CGFloat angle = atan2f(targetTransform.b, targetTransform.a);
163 if (fabs(angle) > epsilon) {
164 // Rotate the preview in the opposite direction of the interface rotation
165 // and add a small value to the angle to force the rotation to occur in the
166 // correct direction when rotating by 180 degrees.
167 void (^animationBlock)(id<UIViewControllerTransitionCoordinatorContext>) =
168 ^void(id<UIViewControllerTransitionCoordinatorContext> context) {
169 [_qrScannerView rotatePreviewByAngle:(epsilon - angle)];
170 };
171 // Note: The completion block is called even if the animation is
172 // interrupted, for example by pressing the home button, with the same
173 // target transform as the animation block.
174 void (^completionBlock)(id<UIViewControllerTransitionCoordinatorContext>) =
175 ^void(id<UIViewControllerTransitionCoordinatorContext> context) {
176 [_qrScannerView finishPreviewRotation];
177 };
178 [coordinator animateAlongsideTransition:animationBlock
179 completion:completionBlock];
180 } else if (!CGSizeEqualToSize(self.view.frame.size, size)) {
181 // Reset the size of the preview if the bounds of the view controller
182 // changed. This can happen if entering or leaving Split View mode on iPad.
183 [_qrScannerView resetPreviewFrame:size];
184 [_cameraController resetVideoOrientation:[_qrScannerView getPreviewLayer]];
185 }
186 }
187
188 - (void)viewDidDisappear:(BOOL)animated {
189 [super viewDidDisappear:animated];
190 [_cameraController stopRecording];
191 [self stopReceivingNotifications];
192
193 // Reset torch.
194 [self setTorchMode:AVCaptureTorchModeOff];
195 }
196
197 - (BOOL)prefersStatusBarHidden {
198 return YES;
199 }
200
201 #pragma mark public methods
202
203 - (UIViewController*)getViewControllerToPresent {
204 DCHECK(_cameraController);
205 switch ([_cameraController getAuthorizationStatus]) {
206 case AVAuthorizationStatusNotDetermined:
207 case AVAuthorizationStatusAuthorized:
208 _transitioningDelegate.reset(
209 [[QRScannerTransitioningDelegate alloc] init]);
210 [self setTransitioningDelegate:_transitioningDelegate];
211 return self;
212 case AVAuthorizationStatusRestricted:
213 case AVAuthorizationStatusDenied:
214 return qr_scanner::DialogForCameraState(
215 qr_scanner::CAMERA_PERMISSION_DENIED, nil);
216 }
217 }
218
219 #pragma mark private methods
220
221 - (void)dismissForReason:(DismissalReason)reason
222 withCompletion:(void (^)(void))completion {
223 switch (reason) {
224 case CLOSE_BUTTON:
225 base::RecordAction(UserMetricsAction("MobileQRScannerClose"));
226 break;
227 case ERROR_DIALOG:
228 base::RecordAction(UserMetricsAction("MobileQRScannerError"));
229 break;
230 case SCANNED_CODE:
231 base::RecordAction(UserMetricsAction("MobileQRScannerScannedCode"));
232 break;
233 case IMPOSSIBLY_UNLIKELY_AUTHORIZATION_CHANGE:
234 break;
235 }
236 [[self presentingViewController] dismissViewControllerAnimated:YES
237 completion:completion];
238 }
239
240 - (void)startReceivingNotifications {
241 [[NSNotificationCenter defaultCenter]
242 addObserver:self
243 selector:@selector(handleUIApplicationWillResignActiveNotification)
244 name:UIApplicationWillResignActiveNotification
245 object:nil];
246
247 [[NSNotificationCenter defaultCenter]
248 addObserver:self
249 selector:@selector(
250 handleUIAccessibilityAnnouncementDidFinishNotification:)
251 name:UIAccessibilityAnnouncementDidFinishNotification
252 object:nil];
253 }
254
255 - (void)stopReceivingNotifications {
256 [[NSNotificationCenter defaultCenter] removeObserver:self];
257 }
258
259 - (void)setTorchMode:(AVCaptureTorchMode)mode {
260 [_cameraController setTorchMode:mode];
261 }
262
263 #pragma mark notification handlers
264
265 - (void)handleUIApplicationWillResignActiveNotification {
266 [self setTorchMode:AVCaptureTorchModeOff];
267 }
268
269 - (void)handleUIAccessibilityAnnouncementDidFinishNotification:
270 (NSNotification*)notification {
271 NSString* announcement = [[notification userInfo]
272 valueForKey:UIAccessibilityAnnouncementKeyStringValue];
273 if ([announcement
274 isEqualToString:
275 l10n_util::GetNSString(
276 IDS_IOS_QR_SCANNER_CODE_SCANNED_ACCESSIBILITY_ANNOUNCEMENT)]) {
277 DCHECK(_result);
278 [self dismissForReason:SCANNED_CODE
279 withCompletion:^{
280 [[self delegate] receiveQRScannerResult:_result
281 loadImmediately:_loadResultImmediately];
282 }];
283 }
284 }
285
286 #pragma mark CameraControllerDelegate
287
288 - (void)captureSessionIsConnected {
289 [_cameraController setViewport:[_qrScannerView viewportRectOfInterest]];
290 }
291
292 - (void)cameraStateChanged:(qr_scanner::CameraState)state {
293 switch (state) {
294 case qr_scanner::CAMERA_AVAILABLE:
295 // Dismiss any presented alerts.
296 if ([self presentedViewController]) {
297 [self dismissViewControllerAnimated:YES completion:nil];
298 }
299 break;
300 case qr_scanner::CAMERA_IN_USE_BY_ANOTHER_APPLICATION:
301 case qr_scanner::MULTIPLE_FOREGROUND_APPS:
302 case qr_scanner::CAMERA_PERMISSION_DENIED:
303 case qr_scanner::CAMERA_UNAVAILABLE:
304 // Dismiss any presented alerts.
305 if ([self presentedViewController]) {
306 [self dismissViewControllerAnimated:YES completion:nil];
307 }
308 [self presentViewController:qr_scanner::DialogForCameraState(
309 state,
310 ^(UIAlertAction*) {
311 [self dismissForReason:ERROR_DIALOG
312 withCompletion:nil];
313 })
314 animated:YES
315 completion:nil];
316 break;
317 case qr_scanner::CAMERA_NOT_LOADED:
318 NOTREACHED();
319 break;
320 }
321 }
322
323 - (void)torchStateChanged:(BOOL)torchIsOn {
324 [_qrScannerView setTorchButtonTo:torchIsOn];
325 }
326
327 - (void)torchAvailabilityChanged:(BOOL)torchIsAvailable {
328 [_qrScannerView enableTorchButton:torchIsAvailable];
329 }
330
331 - (void)receiveQRScannerResult:(NSString*)result loadImmediately:(BOOL)load {
332 if (UIAccessibilityIsVoiceOverRunning()) {
333 // Post a notification announcing that a code was scanned. QR scanner will
334 // be dismissed when the UIAccessibilityAnnouncementDidFinishNotification is
335 // received.
336 _result.reset([result copy]);
337 _loadResultImmediately = load;
338 UIAccessibilityPostNotification(
339 UIAccessibilityAnnouncementNotification,
340 l10n_util::GetNSString(
341 IDS_IOS_QR_SCANNER_CODE_SCANNED_ACCESSIBILITY_ANNOUNCEMENT));
342 } else {
343 [_qrScannerView animateScanningResultWithCompletion:^void(void) {
344 [self dismissForReason:SCANNED_CODE
345 withCompletion:^{
346 [[self delegate] receiveQRScannerResult:result
347 loadImmediately:load];
348 }];
349 }];
350 }
351 }
352
353 #pragma mark QRScannerViewDelegate
354
355 - (void)dismissQRScannerView:(id)sender {
356 [self dismissForReason:CLOSE_BUTTON withCompletion:nil];
357 }
358
359 - (void)toggleTorch:(id)sender {
360 if ([_cameraController isTorchActive]) {
361 [self setTorchMode:AVCaptureTorchModeOff];
362 } else {
363 base::RecordAction(UserMetricsAction("MobileQRScannerTorchOn"));
364 [self setTorchMode:AVCaptureTorchModeOn];
365 }
366 }
367
368 @end
OLDNEW
« no previous file with comments | « no previous file | ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698