chromium/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.mm

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.h"

#import "base/apple/foundation_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "components/version_info/version_info.h"
#import "ios/chrome/browser/location_bar/ui_bundled/location_bar_url_loader.h"
#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h"
#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
#import "ios/chrome/browser/shared/ui/symbols/chrome_icon.h"
#import "ios/chrome/browser/ui/scanner/camera_controller.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/testing/nserror_util.h"
#import "net/base/apple/url_conversions.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"

using scanner::CameraState;

@implementation QRScannerAppInterface

+ (UIViewController*)currentBrowserViewController {
  SceneState* sceneState = chrome_test_util::GetForegroundActiveScene();
  return sceneState.browserProviderInterface.mainBrowserProvider.viewController;
}

+ (NSString*)closeIconAccessibilityLabel {
  return [ChromeIcon closeIcon].accessibilityLabel;
}

#pragma mark Swizzling

+ (id)cameraControllerSwizzleBlockWithMock:(id)cameraControllerMock {
  QRScannerCameraController* (^swizzleCameraControllerBlock)(
      id<QRScannerCameraControllerDelegate>) =
      ^(id<QRScannerCameraControllerDelegate> delegate) {
        return cameraControllerMock;
      };

  return swizzleCameraControllerBlock;
}

#pragma mark Mocking and Expectations

+ (id)cameraControllerMockWithAuthorizationStatus:
    (AVAuthorizationStatus)authorizationStatus {
  id mock = [OCMockObject mockForClass:[QRScannerCameraController class]];
  [[[mock stub] andReturnValue:OCMOCK_VALUE(authorizationStatus)]
      authorizationStatus];
  return mock;
}

+ (void)addCameraControllerInitializationExpectations:(id)cameraControllerMock {
  [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOff];
  [[cameraControllerMock expect] loadCaptureSession:[OCMArg any]];
  [[cameraControllerMock expect] startRecording];
}

+ (void)addCameraControllerDismissalExpectations:(id)cameraControllerMock {
  [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOff];
  [[cameraControllerMock expect] stopRecording];
}

+ (void)addCameraControllerTorchOnExpectations:(id)cameraControllerMock {
  [[[cameraControllerMock expect] andReturnValue:@NO] isTorchActive];
  [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOn];
}

+ (void)addCameraControllerTorchOffExpectations:(id)cameraControllerMock {
  [[[cameraControllerMock expect] andReturnValue:@YES] isTorchActive];
  [[cameraControllerMock expect] setTorchMode:AVCaptureTorchModeOff];
}

#pragma mark CameraControllerDelegate calls

+ (void)callCameraStateChanged:(CameraState)state {
  QRScannerViewController* vc = (QRScannerViewController*)
      [self.currentBrowserViewController presentedViewController];
  [vc cameraStateChanged:state];
}

+ (void)callTorchStateChanged:(BOOL)torchIsOn {
  QRScannerViewController* vc = (QRScannerViewController*)
      [self.currentBrowserViewController presentedViewController];
  [vc torchStateChanged:torchIsOn];
}

+ (void)callTorchAvailabilityChanged:(BOOL)torchIsAvailable {
  QRScannerViewController* vc = (QRScannerViewController*)
      [self.currentBrowserViewController presentedViewController];
  [vc torchAvailabilityChanged:torchIsAvailable];
}

+ (void)callReceiveQRScannerResult:(NSString*)result {
  QRScannerViewController* vc = (QRScannerViewController*)
      [self.currentBrowserViewController presentedViewController];
  [vc receiveQRScannerResult:result loadImmediately:NO];
}

#pragma mark Modal helpers for dialogs

+ (NSError*)assertModalOfClass:(NSString*)className
                 isPresentedBy:(UIViewController*)viewController {
  Class klass = NSClassFromString(className);
  UIViewController* modal = [viewController presentedViewController];
  NSString* errorString = [NSString
      stringWithFormat:@"A modal of class %@ should be presented by %@.", klass,
                       [viewController class]];
  BOOL condition = modal && [modal isKindOfClass:klass];
  if (!condition) {
    return testing::NSErrorWithLocalizedDescription(errorString);
  }
  return nil;
}

+ (NSError*)assertModalOfClass:(NSString*)className
              isNotPresentedBy:(UIViewController*)viewController {
  Class klass = NSClassFromString(className);
  UIViewController* modal = [viewController presentedViewController];
  NSString* errorString = [NSString
      stringWithFormat:@"A modal of class %@ should not be presented by %@.",
                       klass, [viewController class]];
  BOOL condition = !modal || ![modal isKindOfClass:klass];
  if (!condition) {
    return testing::NSErrorWithLocalizedDescription(errorString);
  }
  return nil;
}

+ (BOOL (^)())blockForWaitingForModalOfClass:(NSString*)className
                        toDisappearFromAbove:(UIViewController*)viewController {
  Class klass = NSClassFromString(className);
  BOOL (^waitingBlock)() = ^BOOL {
    UIViewController* modal = [viewController presentedViewController];
    return !modal || ![modal isKindOfClass:klass];
  };
  return waitingBlock;
}

// Returns the expected title for the dialog which is presented for `state`.
+ (NSString*)dialogTitleForState:(CameraState)state {
  std::u16string appName = base::UTF8ToUTF16(version_info::GetProductName());
  switch (state) {
    case scanner::CAMERA_AVAILABLE:
    case scanner::CAMERA_NOT_LOADED:
      return nil;
    case scanner::CAMERA_IN_USE_BY_ANOTHER_APPLICATION:
      return l10n_util::GetNSString(
          IDS_IOS_QR_SCANNER_CAMERA_IN_USE_ALERT_TITLE);
    case scanner::CAMERA_PERMISSION_DENIED:
      return l10n_util::GetNSString(
          IDS_IOS_SCANNER_CAMERA_PERMISSIONS_HELP_TITLE_GO_TO_SETTINGS);
    case scanner::CAMERA_UNAVAILABLE_DUE_TO_SYSTEM_PRESSURE:
    case scanner::CAMERA_UNAVAILABLE:
      return l10n_util::GetNSString(
          IDS_IOS_QR_SCANNER_CAMERA_UNAVAILABLE_ALERT_TITLE);
    case scanner::MULTIPLE_FOREGROUND_APPS:
      return l10n_util::GetNSString(
          IDS_IOS_QR_SCANNER_MULTIPLE_FOREGROUND_APPS_ALERT_TITLE);
  }
}

#pragma mark VoiceOver Overrides

+ (void)overrideVoiceOverCheckForQRScannerViewController:
            (UIViewController*)qrScanner
                                                    isOn:(BOOL)isOn {
  QRScannerViewController* qrScannerViewController =
      base::apple::ObjCCast<QRScannerViewController>(qrScanner);
  [qrScannerViewController overrideVoiceOverCheck:isOn];
}

+ (void)postScanEndVoiceoverAnnouncement {
  NSString* scannedAnnouncement = l10n_util::GetNSString(
      IDS_IOS_SCANNER_SCANNED_ACCESSIBILITY_ANNOUNCEMENT);
  [[NSNotificationCenter defaultCenter]
      postNotificationName:UIAccessibilityAnnouncementDidFinishNotification
                    object:nil
                  userInfo:@{
                    UIAccessibilityAnnouncementKeyStringValue :
                        scannedAnnouncement
                  }];
}

@end