chromium/ios/chrome/browser/shared/ui/util/keyboard_observer_helper.mm

// Copyright 2018 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/shared/ui/util/keyboard_observer_helper.h"

#import "base/check.h"
#import "base/check_op.h"
#import "ios/chrome/common/ui/util/constraints_ui_util.h"
#import "ios/chrome/common/ui/util/ui_util.h"
#import "ui/base/device_form_factor.h"

@interface KeyboardObserverHelper ()

// Flag that indicates if the keyboard is on screen.
@property(nonatomic, readwrite, getter=isKeyboardVisible) BOOL keyboardVisible;

@end

@implementation KeyboardObserverHelper

#pragma mark - Public class methods

+ (instancetype)sharedKeyboardObserver {
  static KeyboardObserverHelper* sharedInstance;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[KeyboardObserverHelper alloc] init];
  });
  return sharedInstance;
}

+ (CGFloat)keyboardHeightInWindow:(UIWindow*)window {
  if (!window) {
    return 0;
  }
  UIView* keyboardView = [KeyboardObserverHelper keyboardViewInWindow:window];
  CGRect keyboardFrame = keyboardView.frame;
  BOOL keyboardCoversFullWidth =
      CGRectGetWidth(keyboardFrame) >= CGRectGetWidth(window.bounds);
  BOOL isDocked = CGRectGetMaxY(keyboardFrame) >= CGRectGetMaxY(window.bounds);
  if (!keyboardCoversFullWidth || !isDocked) {
    return 0;
  }
  CGRect intersection = CGRectIntersection(keyboardFrame, window.bounds);
  return CGRectGetHeight(intersection);
}

#pragma mark - Private instance methods

- (instancetype)init {
  self = [super init];
  if (self) {
    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(keyboardWillShow:)
               name:UIKeyboardWillShowNotification
             object:nil];
    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(keyboardWillHide:)
               name:UIKeyboardWillHideNotification
             object:nil];
    _keyboardVisible = NO;
  }
  return self;
}

#pragma mark - Private class methods

// Returns the keyboard view in `window`.
// keyboard view coordinates {x: keyboard x in window coordinate,
//                            y: keyboard y in window coordinate}
// When the keyboard is docked and software (the keyboard spans across the full
// width of the screen):
// - Fixed window (full screen, split screen):
//     keyboard size is {width: screen width,
//                       height: keyboard height}
// - Floating window (slide over, stage manager):
//     keyboard size is {width: window width,
//                       height: keyboard and window intersection height}
// When the keyboard is hardware:
// - Fixed window (full screen, split screen):
//     keyboard view size is {width: screen width,
//                            height: accessory height}
// - Slide over:
//     keyboard view size is {width: window width,
//                           height: accessory and window intersection height}
// - Stage manager:
//     keyboard view size is {width: window width, height: 0}
+ (UIView*)keyboardViewInWindow:(UIWindow*)window {
  if (!window || !window.windowScene || !window.windowScene.windows.count) {
    return nil;
  }

  // Iterate windows in reverse order from frontmost to back.
  for (UIWindow* w in window.windowScene.windows.reverseObjectEnumerator) {
    for (UIView* subview in w.subviews) {
      if ([NSStringFromClass([subview class]) rangeOfString:@"PeripheralHost"]
              .location != NSNotFound) {
        return subview;
      }
      if ([NSStringFromClass([subview class]) rangeOfString:@"SetContainer"]
              .location != NSNotFound) {
        for (UIView* subsubview in subview.subviews) {
          if ([NSStringFromClass([subsubview class]) rangeOfString:@"SetHost"]
                  .location != NSNotFound) {
            return subsubview;
          }
        }
      }
    }
  }

  return nil;
}

#pragma mark - Keyboard Notifications

- (void)keyboardWillShow:(NSNotification*)notification {
  self.keyboardVisible = YES;
}

- (void)keyboardWillHide:(NSNotification*)notification {
  self.keyboardVisible = NO;
}

@end