FBDeviceControl/Management/FBDevice.m (306 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "FBDevice.h"
#import "FBDevice+Private.h"
#import <FBControlCore/FBControlCore.h>
#import "FBAMDevice.h"
#import "FBAMRestorableDevice.h"
#import "FBDeviceApplicationCommands.h"
#import "FBDeviceControlError.h"
#import "FBDeviceCrashLogCommands.h"
#import "FBDeviceDebuggerCommands.h"
#import "FBDeviceDeveloperDiskImageCommands.h"
#import "FBDeviceDiagnosticInformationCommands.h"
#import "FBDeviceEraseCommands.h"
#import "FBDeviceFileCommands.h"
#import "FBDeviceFileCommands.h"
#import "FBDeviceLifecycleCommands.h"
#import "FBDeviceLocationCommands.h"
#import "FBDeviceLogCommands.h"
#import "FBDevicePowerCommands.h"
#import "FBDeviceScreenshotCommands.h"
#import "FBDeviceVideoRecordingCommands.h"
#import "FBDeviceXCTestCommands.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wprotocol"
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation FBDevice
@synthesize activationState = _activationState;
@synthesize allValues = _allValues;
@synthesize amDevice = _amDevice;
@synthesize architecture = _architecture;
@synthesize buildVersion = _buildVersion;
@synthesize calls = _calls;
@synthesize deviceType = _deviceType;
@synthesize extendedInformation = _extendedInformation;
@synthesize logger = _logger;
@synthesize name = _name;
@synthesize osVersion = _osVersion;
@synthesize productVersion = _productVersion;
@synthesize restorableDevice = _restorableDevice;
@synthesize state = _state;
@synthesize targetType = _targetType;
@synthesize temporaryDirectory = _temporaryDirectory;
@synthesize udid = _udid;
@synthesize uniqueIdentifier = _uniqueIdentifier;
#pragma mark Initializers
- (instancetype)initWithSet:(FBDeviceSet *)set amDevice:(FBAMDevice *)amDevice restorableDevice:(FBAMRestorableDevice *)restorableDevice logger:(id<FBControlCoreLogger>)logger
{
NSAssert(amDevice || restorableDevice, @"An FBAMDevice or FBAMRestorableDevice must be provided");
self = [super init];
if (!self) {
return nil;
}
_set = set;
_amDevice = amDevice;
_restorableDevice = restorableDevice;
[self cacheValuesFromInfo:(amDevice ?: restorableDevice) overwrite:YES];
_logger = [logger withName:self.udid];
_forwarder = [FBiOSTargetCommandForwarder forwarderWithTarget:self commandClasses:FBDevice.commandResponders statefulCommands:FBDevice.statefulCommands];
return self;
}
#pragma mark FBiOSTarget
- (dispatch_queue_t)workQueue
{
return self.amDevice.workQueue ?: self.restorableDevice.workQueue;
}
- (dispatch_queue_t)asyncQueue
{
return self.amDevice.asyncQueue ?: self.restorableDevice.asyncQueue;
}
- (FBTemporaryDirectory *)temporaryDirectory
{
if (_temporaryDirectory) {
return _temporaryDirectory;
}
_temporaryDirectory = [FBTemporaryDirectory temporaryDirectoryWithLogger:self.logger];
return _temporaryDirectory;
}
- (NSString *)auxillaryDirectory
{
NSString *cwd = NSFileManager.defaultManager.currentDirectoryPath;
return [NSFileManager.defaultManager isWritableFileAtPath:cwd] ? cwd : @"/tmp";
}
- (NSString *)platformRootDirectory
{
return [FBXcodeConfiguration.developerDirectory stringByAppendingPathComponent:@"Platforms/iPhoneOS.platform"];
}
- (NSString *)runtimeRootDirectory
{
return [self platformRootDirectory];
}
- (FBiOSTargetScreenInfo *)screenInfo
{
return nil;
}
- (NSString *)customDeviceSetPath
{
return nil;
}
- (NSComparisonResult)compare:(id<FBiOSTarget>)target
{
return FBiOSTargetComparison(self, target);
}
- (NSDictionary<NSString *, NSString *> *)replacementMapping
{
return NSDictionary.dictionary;
}
- (BOOL) requiresBundlesToBeSigned {
return YES;
}
#pragma mark NSObject
- (NSString *)description
{
return FBiOSTargetDescribe(self);
}
#pragma mark FBDevice Class Properties
- (void)setAmDevice:(FBAMDevice *)amDevice
{
_amDevice = amDevice;
[self cacheValuesFromInfo:amDevice overwrite:YES];
}
- (FBAMDevice *)amDevice
{
return _amDevice;
}
- (void)setRestorableDevice:(FBAMRestorableDevice *)restorableDevice
{
_restorableDevice = restorableDevice;
[self cacheValuesFromInfo:restorableDevice overwrite:NO];
}
- (FBAMRestorableDevice *)restorableDevice
{
return _restorableDevice;
}
- (void)cacheValuesFromInfo:(id<FBiOSTargetInfo, FBDevice>)targetInfo overwrite:(BOOL)overwrite
{
// Don't overwrite with nil values.
if (!targetInfo) {
return;
}
// These values should always be overwitten
_calls = targetInfo.calls;
_state = targetInfo.state;
// Overwrite only if requested (i.e. if is the more information FBAMDevice)
if (!_allValues || overwrite) {
_allValues = targetInfo.allValues;
}
if (!_architecture || overwrite) {
_architecture = targetInfo.architecture;
}
if (!_buildVersion || overwrite) {
_buildVersion = targetInfo.buildVersion;
}
if (!_deviceType || overwrite) {
_deviceType = targetInfo.deviceType;
}
if (!_extendedInformation || overwrite) {
_extendedInformation = targetInfo.extendedInformation;
}
if (!_name || overwrite) {
_name = targetInfo.name;
}
if (!_osVersion || overwrite) {
_osVersion = targetInfo.osVersion;
}
if (_productVersion || overwrite) {
_productVersion = targetInfo.productVersion;
}
if (!_targetType || overwrite) {
_targetType = targetInfo.targetType;
}
if (!_udid || overwrite) {
_udid = targetInfo.udid;
}
if (!_uniqueIdentifier || overwrite) {
_uniqueIdentifier = targetInfo.uniqueIdentifier;
}
if (!_activationState || overwrite) {
_activationState = targetInfo.activationState;
}
}
#pragma mark FBDevice Protocol Implementation
- (AMDeviceRef)amDeviceRef
{
FBAMDevice *amDevice = self.amDevice;
if (!amDevice) {
return NULL;
}
return amDevice.amDeviceRef;
}
- (AMRecoveryModeDeviceRef)recoveryModeDeviceRef
{
FBAMRestorableDevice *restorableDevice = self.restorableDevice;
if (!restorableDevice) {
return NULL;
}
return restorableDevice.recoveryModeDeviceRef;
}
#pragma mark FBDeviceCommands Protocol Implementation
- (FBFutureContext<id<FBDeviceCommands>> *)connectToDeviceWithPurpose:(NSString *)format, ...
{
FBAMDevice *amDevice = self.amDevice;
if (amDevice) {
va_list args;
va_start(args, format);
NSString *string = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
return [amDevice connectToDeviceWithPurpose:@"%@", string];
}
return [[FBDeviceControlError
describeFormat:@"%@ fails when not AMDevice backed.", NSStringFromSelector(_cmd)]
failFutureContext];
}
- (FBFutureContext<FBAMDServiceConnection *> *)startService:(NSString *)service
{
FBAMDevice *amDevice = self.amDevice;
if (amDevice) {
return [amDevice startService:service];
}
return [[FBDeviceControlError
describeFormat:@"%@ fails when not AMDevice backed.", NSStringFromSelector(_cmd)]
failFutureContext];
}
- (FBFutureContext<FBDeviceLinkClient *> *)startDeviceLinkService:(NSString *)service
{
FBAMDevice *amDevice = self.amDevice;
if (amDevice) {
return [amDevice startDeviceLinkService:service];
}
return [[FBDeviceControlError
describeFormat:@"%@ fails when not AMDevice backed.", NSStringFromSelector(_cmd)]
failFutureContext];
}
- (FBFutureContext<FBAFCConnection *> *)startAFCService:(NSString *)service
{
FBAMDevice *amDevice = self.amDevice;
if (amDevice) {
return [amDevice startAFCService:service];
}
return [[FBDeviceControlError
describeFormat:@"%@ fails when not AMDevice backed.", NSStringFromSelector(_cmd)]
failFutureContext];
}
- (FBFutureContext<FBAFCConnection *> *)houseArrestAFCConnectionForBundleID:(NSString *)bundleID afcCalls:(AFCCalls)afcCalls
{
FBAMDevice *amDevice = self.amDevice;
if (amDevice) {
return [amDevice houseArrestAFCConnectionForBundleID:bundleID afcCalls:afcCalls];
}
return [[FBDeviceControlError
describeFormat:@"%@ fails when not AMDevice backed.", NSStringFromSelector(_cmd)]
failFutureContext];
}
#pragma mark Forwarding
+ (NSArray<Class> *)commandResponders
{
static dispatch_once_t onceToken;
static NSArray<Class> *commandClasses;
dispatch_once(&onceToken, ^{
commandClasses = @[
FBDeviceActivationCommands.class,
FBDeviceApplicationCommands.class,
FBDeviceCrashLogCommands.class,
FBDeviceDebuggerCommands.class,
FBDeviceDebugSymbolsCommands.class,
FBDeviceDeveloperDiskImageCommands.class,
FBDeviceDiagnosticInformationCommands.class,
FBDeviceEraseCommands.class,
FBDeviceFileCommands.class,
FBDeviceLifecycleCommands.class,
FBDeviceLocationCommands.class,
FBDeviceLogCommands.class,
FBDevicePowerCommands.class,
FBDeviceRecoveryCommands.class,
FBDeviceScreenshotCommands.class,
FBDeviceSocketForwardingCommands.class,
FBDeviceVideoRecordingCommands.class,
FBDeviceXCTestCommands.class,
FBInstrumentsCommands.class,
FBXCTraceRecordCommands.class,
];
});
return commandClasses;
}
+ (NSSet<Class> *)statefulCommands
{
// All commands are stateful
return [NSSet setWithArray:self.commandResponders];
}
- (id)forwardingTargetForSelector:(SEL)selector
{
// Try the underling FBAMDevice instance>
if ([self.amDevice respondsToSelector:selector]) {
return self.amDevice;
}
// Try the forwarder.
id command = [self.forwarder forwardingTargetForSelector:selector];
if (command) {
return command;
}
// Nothing left.
return [super forwardingTargetForSelector:selector];
}
- (BOOL)conformsToProtocol:(Protocol *)protocol
{
if ([super conformsToProtocol:protocol]) {
return YES;
}
if ([self.forwarder conformsToProtocol:protocol]) {
return YES;
}
return NO;
}
@end
#pragma clang diagnostic pop