2021-02-11 13:47:14 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-02-10 09:04:41 -04:00
# include "IOSDeviceHelper.h"
2021-02-10 07:28:32 -04:00
# include "IOSTargetPlatform.h"
# include "IOSTargetDeviceOutput.h"
# include "HAL/PlatformProcess.h"
# include "HAL/Runnable.h"
# include "HAL/RunnableThread.h"
# include "Interfaces/ITargetPlatformManagerModule.h"
# include "Interfaces/ITargetPlatform.h"
# include "Interfaces/IProjectManager.h"
2021-09-17 05:01:42 -04:00
# include "Misc/MessageDialog.h"
# define LOCTEXT_NAMESPACE "MiscIOSMessages"
2021-02-10 07:28:32 -04:00
DEFINE_LOG_CATEGORY_STATIC ( LogIOSDeviceHelper , Log , All ) ;
2021-02-17 09:08:31 -04:00
enum DeviceConnectionInterface
{
NoValue = 0 ,
USB = 1 ,
Network = 2 ,
Max = 4
} ;
struct FDeviceNotificationCallbackInformation
{
2023-05-24 09:39:39 -04:00
FString DeviceID ;
2021-02-17 09:08:31 -04:00
FString DeviceName ;
2023-05-24 09:39:39 -04:00
FString DeviceUDID ;
2021-02-17 09:08:31 -04:00
FString ProductType ;
2023-05-24 09:39:39 -04:00
FString DeviceOSVersion ;
2021-02-17 09:08:31 -04:00
DeviceConnectionInterface DeviceInterface ;
uint32 msgType ;
2023-05-24 09:39:39 -04:00
bool IsAuthorized ;
2021-02-17 09:08:31 -04:00
} ;
2021-02-10 07:28:32 -04:00
struct LibIMobileDevice
{
FString DeviceID ;
2023-05-24 09:39:39 -04:00
FString DeviceName ;
FString DeviceUDID ;
2021-02-10 07:28:32 -04:00
FString DeviceType ;
2023-05-24 09:39:39 -04:00
FString DeviceOSVersion ;
2021-02-17 09:08:31 -04:00
DeviceConnectionInterface DeviceInterface ;
2023-05-24 09:39:39 -04:00
bool IsAuthorized ;
bool IsDealtWith ;
2021-02-10 07:28:32 -04:00
} ;
static TArray < LibIMobileDevice > GetLibIMobileDevices ( )
{
FString OutStdOut ;
FString OutStdErr ;
FString LibimobileDeviceId = GetLibImobileDeviceExe ( " idevice_id " ) ;
int ReturnCode ;
// get the list of devices UDID
2021-03-05 05:26:15 -04:00
FPlatformProcess : : ExecProcess ( * LibimobileDeviceId , TEXT ( " " ) , & ReturnCode , & OutStdOut , & OutStdErr , NULL , true ) ;
2021-02-10 07:28:32 -04:00
TArray < LibIMobileDevice > ToReturn ;
// separate out each line
2021-02-17 09:08:31 -04:00
TArray < FString > OngoingDeviceIds ;
OutStdOut . ParseIntoArray ( OngoingDeviceIds , TEXT ( " \n " ) , true ) ;
2021-02-10 07:28:32 -04:00
TArray < FString > DeviceStrings ;
2021-02-17 09:08:31 -04:00
for ( int32 StringIndex = 0 ; StringIndex < OngoingDeviceIds . Num ( ) ; + + StringIndex )
2021-02-10 07:28:32 -04:00
{
2023-05-24 09:39:39 -04:00
const FString & DeviceUDID = OngoingDeviceIds [ StringIndex ] ;
2021-02-17 09:08:31 -04:00
DeviceConnectionInterface OngoingDeviceInterface = DeviceConnectionInterface : : NoValue ;
2021-02-10 07:28:32 -04:00
FString OutStdOutInfo ;
FString OutStdErrInfo ;
FString LibimobileDeviceInfo = GetLibImobileDeviceExe ( " ideviceinfo " ) ;
int ReturnCodeInfo ;
2021-02-17 09:08:31 -04:00
FString Arguments ;
2021-02-10 07:28:32 -04:00
2021-02-17 09:08:31 -04:00
if ( OngoingDeviceIds [ StringIndex ] . Contains ( " USB " ) )
{
OngoingDeviceInterface = DeviceConnectionInterface : : USB ;
}
else if ( OngoingDeviceIds [ StringIndex ] . Contains ( " Network " ) )
{
OngoingDeviceInterface = DeviceConnectionInterface : : Network ;
}
OngoingDeviceIds [ StringIndex ] . Split ( TEXT ( " " ) , & OngoingDeviceIds [ StringIndex ] , nullptr , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
if ( OngoingDeviceInterface = = DeviceConnectionInterface : : USB )
{
2023-05-24 09:39:39 -04:00
Arguments = " -u " + DeviceUDID ;
2021-02-17 09:08:31 -04:00
}
else if ( OngoingDeviceInterface = = DeviceConnectionInterface : : Network )
{
2023-05-24 09:39:39 -04:00
Arguments = " -n -u " + DeviceUDID ;
2021-02-17 09:08:31 -04:00
}
2021-03-05 05:26:15 -04:00
FPlatformProcess : : ExecProcess ( * LibimobileDeviceInfo , * Arguments , & ReturnCodeInfo , & OutStdOutInfo , & OutStdErrInfo , NULL , true ) ;
2021-09-17 05:01:42 -04:00
LibIMobileDevice ToAdd ;
// ideviceinfo can fail when the connected device is untrusted. It can be "Pairing dialog response pending (-19)", "Invalid HostID (-21)" or "User denied pairing (-18)"
// the only thing we can do is to make sure the Trust popup is correctly displayed.
if ( OutStdErrInfo . Contains ( " ERROR: " ) )
{
if ( OutStdErrInfo . Contains ( " Could not connect to lockdownd " ) )
{
2023-06-12 23:56:36 -04:00
// UE_LOG(LogIOSDeviceHelper, Warning, TEXT("Could not pair with connected iOS/tvOS device. Trust this computer by accepting the popup on device."));
2021-09-17 05:01:42 -04:00
FString LibimobileDevicePair = GetLibImobileDeviceExe ( " idevicepair " ) ;
2023-05-24 09:39:39 -04:00
FString PairArguments = " -u " + DeviceUDID + " pair " ;
2021-09-17 05:01:42 -04:00
FPlatformProcess : : ExecProcess ( * LibimobileDevicePair , * PairArguments , & ReturnCodeInfo , & OutStdOutInfo , & OutStdErrInfo , NULL , true ) ;
}
else
{
UE_LOG ( LogIOSDeviceHelper , Warning , TEXT ( " Libimobile call failed : %s " ) , * OutStdErrInfo ) ;
}
OutStdOutInfo . Empty ( ) ;
OutStdErrInfo . Empty ( ) ;
2023-05-24 09:39:39 -04:00
ToAdd . IsAuthorized = false ;
2021-09-17 05:01:42 -04:00
}
else
{
2023-05-24 09:39:39 -04:00
ToAdd . IsAuthorized = true ;
2021-09-17 05:01:42 -04:00
}
// parse product type and device name
FString DeviceName ;
2023-05-24 09:39:39 -04:00
OutStdOutInfo . Split ( TEXT ( " DeviceName: " ) , nullptr , & DeviceName , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
2021-09-17 05:01:42 -04:00
DeviceName . Split ( LINE_TERMINATOR , & DeviceName , nullptr , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
2023-05-24 09:39:39 -04:00
if ( ! ToAdd . IsAuthorized )
{
2021-09-17 05:01:42 -04:00
DeviceName = LOCTEXT ( " IosTvosUnauthorizedDevice " , " iOS / tvOS (Unauthorized) " ) . ToString ( ) ;
2023-05-24 09:39:39 -04:00
}
else
{
if ( OngoingDeviceInterface = = DeviceConnectionInterface : : Network )
{
DeviceName + = " [Wifi] " ;
}
}
FString ProductType ;
OutStdOutInfo . Split ( TEXT ( " ProductType: " ) , nullptr , & ProductType , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
ProductType . Split ( LINE_TERMINATOR , & ProductType , nullptr , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
FString OSVersion ; // iOS/iPad OS Version
OutStdOutInfo . Split ( TEXT ( " ProductVersion: " ) , nullptr , & OSVersion , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
OSVersion . Split ( LINE_TERMINATOR , & OSVersion , nullptr , ESearchCase : : CaseSensitive , ESearchDir : : FromStart ) ;
FString DeviceID = FString : : Printf ( TEXT ( " %s@%s " ) ,
ProductType . Contains ( TEXT ( " AppleTV " ) ) ? TEXT ( " TVOS " ) : TEXT ( " IOS " ) ,
* DeviceUDID ) ;
ToAdd . DeviceID = DeviceID ;
ToAdd . DeviceUDID = DeviceUDID ;
ToAdd . DeviceName = DeviceName ;
ToAdd . DeviceType = ProductType ;
ToAdd . DeviceOSVersion = OSVersion ;
ToAdd . DeviceInterface = OngoingDeviceInterface ;
ToAdd . IsDealtWith = false ;
ToReturn . Add ( ToAdd ) ;
}
return ToReturn ;
}
2021-02-10 07:28:32 -04:00
class FIOSDevice
{
public :
2023-05-24 09:39:39 -04:00
FIOSDevice ( FString InID , FString InName , DeviceConnectionInterface InConnectionType )
: UDID ( InID )
, Name ( InName )
, ConnectionType ( InConnectionType )
{
}
~ FIOSDevice ( )
{
}
FString SerialNumber ( ) const
{
return UDID ;
}
DeviceConnectionInterface ConnectionInterface ( ) const
{
return ConnectionType ;
}
2021-02-10 07:28:32 -04:00
private :
2023-05-24 09:39:39 -04:00
FString UDID ;
FString Name ;
DeviceConnectionInterface ConnectionType ;
2021-02-10 07:28:32 -04:00
} ;
/**
* Delegate type for devices being connected or disconnected from the machine
*
* The first parameter is newly added or removed device
*/
DECLARE_MULTICAST_DELEGATE_OneParam ( FDeviceNotification , void * )
// recheck once per minute
# define RECHECK_COUNTER_RESET 12
class FDeviceQueryTask
: public FRunnable
{
public :
FDeviceQueryTask ( )
: Stopping ( false )
, bCheckDevices ( true )
, NeedSDKCheck ( true )
, RetryQuery ( 5 )
{ }
virtual bool Init ( ) override
{
return true ;
}
virtual uint32 Run ( ) override
{
while ( ! Stopping )
{
if ( IsEngineExitRequested ( ) )
{
break ;
}
if ( GetTargetPlatformManager ( ) )
{
FString OutTutorialPath ;
const ITargetPlatform * Platform = GetTargetPlatformManager ( ) - > FindTargetPlatform ( TEXT ( " IOS " ) ) ;
if ( Platform )
{
if ( Platform - > IsSdkInstalled ( false , OutTutorialPath ) )
{
break ;
}
}
Enable ( false ) ;
return 0 ;
}
else
{
FPlatformProcess : : Sleep ( 1.0f ) ;
}
}
int RecheckCounter = RECHECK_COUNTER_RESET ;
while ( ! Stopping )
{
if ( IsEngineExitRequested ( ) )
{
break ;
}
if ( bCheckDevices )
{
# if WITH_EDITOR
if ( ! IsRunningCommandlet ( ) )
{
//if (NeedSDKCheck)
//{
// NeedSDKCheck = false;
// FProjectStatus ProjectStatus;
// if (!IProjectManager::Get().QueryStatusForCurrentProject(ProjectStatus) || (!ProjectStatus.IsTargetPlatformSupported(TEXT("IOS")) && !ProjectStatus.IsTargetPlatformSupported(TEXT("TVOS"))))
// {
// Enable(false);
// }
//}
//else
{
// BHP - Turning off device check to prevent it from interfering with packaging
QueryDevices ( ) ;
}
}
2021-04-13 10:11:56 -04:00
# else
QueryDevices ( ) ;
2021-02-10 07:28:32 -04:00
# endif
}
RecheckCounter - - ;
if ( RecheckCounter < 0 )
{
RecheckCounter = RECHECK_COUNTER_RESET ;
bCheckDevices = true ;
NeedSDKCheck = true ;
}
FPlatformProcess : : Sleep ( 5.0f ) ;
}
return 0 ;
}
virtual void Stop ( ) override
{
Stopping = true ;
}
virtual void Exit ( ) override
{ }
FDeviceNotification & OnDeviceNotification ( )
{
return DeviceNotification ;
}
void Enable ( bool bInCheckDevices )
{
bCheckDevices = bInCheckDevices ;
}
private :
2021-09-17 05:01:42 -04:00
void NotifyDeviceChange ( LibIMobileDevice & Device , bool bAdd )
{
FDeviceNotificationCallbackInformation CallbackInfo ;
2021-02-10 07:28:32 -04:00
2021-09-17 05:01:42 -04:00
if ( bAdd )
{
2023-05-24 09:39:39 -04:00
CallbackInfo . DeviceID = Device . DeviceID ;
2021-09-17 05:01:42 -04:00
CallbackInfo . DeviceName = Device . DeviceName ;
2023-05-24 09:39:39 -04:00
CallbackInfo . DeviceUDID = Device . DeviceUDID ;
2021-09-17 05:01:42 -04:00
CallbackInfo . DeviceInterface = Device . DeviceInterface ;
CallbackInfo . ProductType = Device . DeviceType ;
2023-05-24 09:39:39 -04:00
CallbackInfo . DeviceOSVersion = Device . DeviceOSVersion ;
2021-09-17 05:01:42 -04:00
CallbackInfo . msgType = 1 ;
2023-05-24 09:39:39 -04:00
CallbackInfo . IsAuthorized = Device . IsAuthorized ;
2021-09-17 05:01:42 -04:00
}
else
{
2023-05-24 09:39:39 -04:00
CallbackInfo . DeviceID = Device . DeviceID ;
CallbackInfo . DeviceUDID = Device . DeviceUDID ;
CallbackInfo . DeviceInterface = Device . DeviceInterface ;
2021-09-17 05:01:42 -04:00
CallbackInfo . msgType = 2 ;
DeviceNotification . Broadcast ( & CallbackInfo ) ;
}
DeviceNotification . Broadcast ( & CallbackInfo ) ;
}
2021-02-10 07:28:32 -04:00
void QueryDevices ( )
{
FString OutStdOut ;
FString OutStdErr ;
2022-07-07 07:47:30 -04:00
FString LibimobileDeviceId = GetLibImobileDeviceExe ( " idevice_id " ) ;
int ReturnCode ;
if ( LibimobileDeviceId . Len ( ) = = 0 )
{
UE_LOG ( LogIOSDeviceHelper , Log , TEXT ( " idevice_id (iOS device detection) executable missing. Turning off iOS/tvOS device detection. " ) ) ;
Enable ( false ) ;
return ;
}
2021-02-10 07:28:32 -04:00
// get the list of devices UDID
2021-03-05 05:26:15 -04:00
FPlatformProcess : : ExecProcess ( * LibimobileDeviceId , TEXT ( " " ) , & ReturnCode , & OutStdOut , & OutStdErr , NULL , true ) ;
2021-02-10 07:28:32 -04:00
if ( OutStdOut . Len ( ) = = 0 )
{
RetryQuery - - ;
if ( RetryQuery < 0 )
{
UE_LOG ( LogIOSDeviceHelper , Verbose , TEXT ( " IOS device listing is disabled for 1 minute (too many failed attempts)! " ) ) ;
Enable ( false ) ;
}
2021-09-17 05:01:42 -04:00
for ( LibIMobileDevice device : CachedDevices )
{
NotifyDeviceChange ( device , false ) ;
}
CachedDevices . Empty ( ) ;
2021-02-10 07:28:32 -04:00
return ;
}
RetryQuery = 5 ;
TArray < LibIMobileDevice > ParsedDevices = GetLibIMobileDevices ( ) ;
for ( int32 Index = 0 ; Index < ParsedDevices . Num ( ) ; + + Index )
{
2022-11-02 18:04:03 -04:00
LibIMobileDevice * Found = CachedDevices . FindByPredicate (
2021-09-17 05:01:42 -04:00
[ & ] ( LibIMobileDevice Element ) {
2023-05-24 09:39:39 -04:00
return ( Element . DeviceUDID = = ParsedDevices [ Index ] . DeviceUDID & &
Element . DeviceInterface = = ParsedDevices [ Index ] . DeviceInterface ) ;
2021-09-17 05:01:42 -04:00
} ) ;
if ( Found ! = nullptr )
{
2023-05-24 09:39:39 -04:00
if ( Found - > IsAuthorized ! = ParsedDevices [ Index ] . IsAuthorized )
2021-09-17 05:01:42 -04:00
{
NotifyDeviceChange ( ParsedDevices [ Index ] , false ) ;
NotifyDeviceChange ( ParsedDevices [ Index ] , true ) ;
}
2023-05-24 09:39:39 -04:00
Found - > IsDealtWith = true ;
2021-09-17 05:01:42 -04:00
}
else
{
NotifyDeviceChange ( ParsedDevices [ Index ] , true ) ;
}
}
2021-02-10 07:28:32 -04:00
2021-09-17 05:01:42 -04:00
for ( int32 Index = 0 ; Index < CachedDevices . Num ( ) ; + + Index )
2021-02-10 07:28:32 -04:00
{
2023-05-24 09:39:39 -04:00
if ( ! CachedDevices [ Index ] . IsDealtWith )
2021-09-17 05:01:42 -04:00
{
NotifyDeviceChange ( CachedDevices [ Index ] , false ) ;
}
2021-02-10 07:28:32 -04:00
}
2021-09-17 05:01:42 -04:00
CachedDevices . Empty ( ) ;
CachedDevices = ParsedDevices ;
2021-02-10 07:28:32 -04:00
}
bool Stopping ;
bool bCheckDevices ;
bool NeedSDKCheck ;
int RetryQuery ;
2021-09-17 05:01:42 -04:00
TArray < LibIMobileDevice > CachedDevices ;
2021-02-10 07:28:32 -04:00
FDeviceNotification DeviceNotification ;
} ;
/* FIOSDeviceHelper structors
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static TMap < FIOSDevice * , FIOSLaunchDaemonPong > ConnectedDevices ;
static FDeviceQueryTask * QueryTask = NULL ;
static FRunnableThread * QueryThread = NULL ;
static TArray < FDeviceNotificationCallbackInformation > NotificationMessages ;
static FTickerDelegate TickDelegate ;
bool FIOSDeviceHelper : : MessageTickDelegate ( float DeltaTime )
{
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FIOSDeviceHelper_MessageTickDelegate ) ;
for ( int Index = 0 ; Index < NotificationMessages . Num ( ) ; + + Index )
{
FDeviceNotificationCallbackInformation cbi = NotificationMessages [ Index ] ;
FIOSDeviceHelper : : DeviceCallback ( & cbi ) ;
}
NotificationMessages . Empty ( ) ;
return true ;
}
void FIOSDeviceHelper : : Initialize ( bool bIsTVOS )
{
if ( ! bIsTVOS )
{
// add the message pump
TickDelegate = FTickerDelegate : : CreateStatic ( MessageTickDelegate ) ;
2021-08-16 11:09:22 -04:00
FTSTicker : : GetCoreTicker ( ) . AddTicker ( TickDelegate , 5.0f ) ;
2021-02-10 07:28:32 -04:00
// kick off a thread to query for connected devices
QueryTask = new FDeviceQueryTask ( ) ;
QueryTask - > OnDeviceNotification ( ) . AddStatic ( FIOSDeviceHelper : : DeviceCallback ) ;
static int32 QueryTaskCount = 1 ;
if ( QueryTaskCount = = 1 )
{
// create the socket subsystem (loadmodule in game thread)
ISocketSubsystem * SSS = ISocketSubsystem : : Get ( ) ;
QueryThread = FRunnableThread : : Create ( QueryTask , * FString : : Printf ( TEXT ( " FIOSDeviceHelper.QueryTask_%d " ) , QueryTaskCount + + ) , 128 * 1024 , TPri_Normal ) ;
}
}
}
void FIOSDeviceHelper : : DeviceCallback ( void * CallbackInfo )
{
struct FDeviceNotificationCallbackInformation * cbi = ( FDeviceNotificationCallbackInformation * ) CallbackInfo ;
if ( ! IsInGameThread ( ) )
{
NotificationMessages . Add ( * cbi ) ;
}
else
{
switch ( cbi - > msgType )
{
case 1 :
FIOSDeviceHelper : : DoDeviceConnect ( CallbackInfo ) ;
break ;
case 2 :
FIOSDeviceHelper : : DoDeviceDisconnect ( CallbackInfo ) ;
break ;
}
}
}
void FIOSDeviceHelper : : DoDeviceConnect ( void * CallbackInfo )
{
// connect to the device
struct FDeviceNotificationCallbackInformation * cbi = ( FDeviceNotificationCallbackInformation * ) CallbackInfo ;
2023-05-24 09:39:39 -04:00
FIOSDevice * Device = new FIOSDevice ( cbi - > DeviceUDID , cbi - > DeviceName , cbi - > DeviceInterface ) ;
2021-02-10 07:28:32 -04:00
// fire the event
FIOSLaunchDaemonPong Event ;
2023-05-24 09:39:39 -04:00
Event . DeviceID = cbi - > DeviceID ;
Event . DeviceUDID = cbi - > DeviceUDID ;
2021-02-10 07:28:32 -04:00
Event . DeviceName = cbi - > DeviceName ;
Event . DeviceType = cbi - > ProductType ;
2023-05-24 09:39:39 -04:00
Event . DeviceOSVersion = cbi - > DeviceOSVersion ;
Event . DeviceModelId = cbi - > ProductType ;
Event . DeviceConnectionType = ( cbi - > DeviceInterface = = DeviceConnectionInterface : : Network ) ? " Network " : " USB " ;
Event . bIsAuthorized = cbi - > IsAuthorized ;
2021-02-10 07:28:32 -04:00
Event . bCanReboot = false ;
Event . bCanPowerOn = false ;
Event . bCanPowerOff = false ;
FIOSDeviceHelper : : OnDeviceConnected ( ) . Broadcast ( Event ) ;
// add to the device list
ConnectedDevices . Add ( Device , Event ) ;
}
void FIOSDeviceHelper : : DoDeviceDisconnect ( void * CallbackInfo )
{
2023-05-24 09:39:39 -04:00
struct FDeviceNotificationCallbackInformation * cbi = ( FDeviceNotificationCallbackInformation * ) CallbackInfo ;
FIOSDevice * device = NULL ;
for ( auto DeviceIterator = ConnectedDevices . CreateIterator ( ) ; DeviceIterator ; + + DeviceIterator )
{
if ( DeviceIterator . Key ( ) - > SerialNumber ( ) = = cbi - > DeviceUDID & &
DeviceIterator . Key ( ) - > ConnectionInterface ( ) = = cbi - > DeviceInterface )
{
device = DeviceIterator . Key ( ) ;
break ;
}
}
if ( device ! = NULL )
{
// extract the device id from the connected list
FIOSLaunchDaemonPong Event = ConnectedDevices . FindAndRemoveChecked ( device ) ;
// fire the event
FIOSDeviceHelper : : OnDeviceDisconnected ( ) . Broadcast ( Event ) ;
// delete the device
delete device ;
}
2021-02-10 07:28:32 -04:00
}
bool FIOSDeviceHelper : : InstallIPAOnDevice ( const FTargetDeviceId & DeviceId , const FString & IPAPath )
{
return false ;
}
void FIOSDeviceHelper : : EnableDeviceCheck ( bool OnOff )
{
QueryTask - > Enable ( OnOff ) ;
}
2021-09-22 06:23:52 -04:00
# undef LOCTEXT_NAMESPACE