chromium/ios/chrome/browser/shared/coordinator/scene/scene_delegate.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/shared/coordinator/scene/scene_delegate.h"

#import "base/apple/foundation_util.h"
#import "base/files/file_path.h"
#import "base/path_service.h"
#import "base/strings/sys_string_conversions.h"
#import "components/breadcrumbs/core/breadcrumb_persistent_storage_util.h"
#import "components/previous_session_info/previous_session_info.h"
#import "ios/chrome/app/chrome_overlay_window.h"
#import "ios/chrome/app/main_application_delegate.h"
#import "ios/chrome/browser/appearance/ui_bundled/appearance_customization.h"
#import "ios/chrome/browser/shared/model/paths/paths.h"

namespace {

NSString* const kOriginDetectedKey = @"OriginDetectedKey";

// Set the breadcrumbs log in PreviousSessionInfo.
void SyncBreadcrumbsLog() {
  static dispatch_once_t once;
  dispatch_once(&once, ^{
    base::FilePath storage_dir;
    bool result = base::PathService::Get(ios::DIR_USER_DATA, &storage_dir);
    DCHECK(result);
    const base::FilePath breadcrumbs_file_path =
        breadcrumbs::GetBreadcrumbPersistentStorageFilePath(storage_dir);
    dispatch_async(
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          NSString* breadcrumbs = [NSString
              stringWithContentsOfFile:base::SysUTF8ToNSString(
                                           breadcrumbs_file_path.value())
                              encoding:NSUTF8StringEncoding
                                 error:NULL];
          [[PreviousSessionInfo sharedInstance] setBreadcrumbsLog:breadcrumbs];
        });
  });
}
}  // namespace

@implementation SceneDelegate

@synthesize sceneState = _sceneState;
@synthesize sceneController = _sceneController;

- (SceneState*)sceneState {
  if (!_sceneState) {
    MainApplicationDelegate* appDelegate =
        base::apple::ObjCCastStrict<MainApplicationDelegate>(
            UIApplication.sharedApplication.delegate);
    _sceneState = [[SceneState alloc] initWithAppState:appDelegate.appState];
    _sceneController = [[SceneController alloc] initWithSceneState:_sceneState];
    _sceneState.controller = _sceneController;
  }
  return _sceneState;
}

#pragma mark - UIWindowSceneDelegate

// This getter is called when the SceneDelegate is created. Returning a
// ChromeOverlayWindow allows UIKit to use that as the main window for this
// scene.
- (UIWindow*)window {
  if (!_window) {
    // With iOS15 pre-warming, this appears to be the first callback after the
    // app is restored. Sync the breadcrumbs log as early as possible, before
    // any MetricKit crash reports may come in.
    SyncBreadcrumbsLog();

    // Sizing of the window is handled by UIKit.
    _window = [[ChromeOverlayWindow alloc] init];
    CustomizeUIWindowAppearance(_window);

    // Assign an a11y identifier for using in EGTest.
    // See comment for [ChromeMatchersAppInterface windowWithNumber:] matcher
    // for context.
    _window.accessibilityIdentifier = [NSString
        stringWithFormat:@"%ld",
                         UIApplication.sharedApplication.connectedScenes.count -
                             1];
  }
  return _window;
}

#pragma mark - UISceneDelegate

- (void)scene:(UIScene*)scene
    willConnectToSession:(UISceneSession*)session
                 options:(UISceneConnectionOptions*)connectionOptions {
  SceneState* sceneState = self.sceneState;
  sceneState.scene = base::apple::ObjCCastStrict<UIWindowScene>(scene);
  sceneState.currentOrigin = [self originFromSession:session
                                             options:connectionOptions];
  sceneState.activationLevel = SceneActivationLevelBackground;
  sceneState.connectionOptions = connectionOptions;
  if (connectionOptions.shortcutItem != nil ||
      connectionOptions.URLContexts.count != 0 ||
      connectionOptions.userActivities.count != 0) {
    sceneState.startupHadExternalIntent = YES;
  }
}

- (void)sceneDidDisconnect:(UIScene*)scene {
  CHECK(_sceneState);
  self.sceneState.activationLevel = SceneActivationLevelDisconnected;
  _sceneState = nil;
  // Setting the level to Disconnected had the side effect of tearing down the
  // controller’s UI.
  _sceneController = nil;
}

#pragma mark - private

- (WindowActivityOrigin)originFromSession:(UISceneSession*)session
                                  options:(UISceneConnectionOptions*)options {
  WindowActivityOrigin origin = WindowActivityUnknownOrigin;

  // When restoring the session, the origin is set to restore to avoid
  // observers treating this as a new request. Also the only time the origin
  // can be correctly detected is on the first observation, because subsequent
  // view are restored, and do not contain the user activities. The key
  // kOriginDetectedKey is set in the session uerInfo to track just that.
  if (session.userInfo[kOriginDetectedKey]) {
    origin = WindowActivityRestoredOrigin;
  } else {
    NSMutableDictionary* userInfo =
        [NSMutableDictionary dictionaryWithDictionary:session.userInfo];
    userInfo[kOriginDetectedKey] = kOriginDetectedKey;
    session.userInfo = userInfo;
    origin = WindowActivityExternalOrigin;
    for (NSUserActivity* activity in options.userActivities) {
      WindowActivityOrigin activityOrigin = OriginOfActivity(activity);
      if (activityOrigin != WindowActivityUnknownOrigin) {
        origin = activityOrigin;
        break;
      }
    }
  }

  return origin;
}

#pragma mark Transitioning to the Foreground

- (void)sceneWillEnterForeground:(UIScene*)scene {
  self.sceneState.currentOrigin = WindowActivityRestoredOrigin;
  self.sceneState.activationLevel = SceneActivationLevelForegroundInactive;
}

- (void)sceneDidBecomeActive:(UIScene*)scene {
  self.sceneState.currentOrigin = WindowActivityRestoredOrigin;
  self.sceneState.activationLevel = SceneActivationLevelForegroundActive;
}

#pragma mark Transitioning to the Background

- (void)sceneWillResignActive:(UIScene*)scene {
  self.sceneState.activationLevel = SceneActivationLevelForegroundInactive;
}

- (void)sceneDidEnterBackground:(UIScene*)scene {
  self.sceneState.activationLevel = SceneActivationLevelBackground;
}

- (void)scene:(UIScene*)scene
    openURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
  DCHECK(!self.sceneState.URLContextsToOpen);
  self.sceneState.startupHadExternalIntent = YES;
  self.sceneState.URLContextsToOpen = URLContexts;
}

- (void)windowScene:(UIWindowScene*)windowScene
    performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
               completionHandler:(void (^)(BOOL succeeded))completionHandler {
  [_sceneController performActionForShortcutItem:shortcutItem
                               completionHandler:completionHandler];
}

- (void)scene:(UIScene*)scene
    continueUserActivity:(NSUserActivity*)userActivity {
  self.sceneState.pendingUserActivity = userActivity;
}

@end