Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/Android/AndroidPlatform.Automation.cs
Bob Tellez 33c9ba59f2 Copying //UE4/Fortnite-Staging to //UE4/Dev-Main (Source: //Fortnite/Main @ 3284787)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3284469 on 2017/02/02 by Saul.Abreu

	Fixed comment on Slate Brush for getting the resource object and what kind of object it may be.

Change 3284410 on 2017/02/02 by Ben.Zeigler

	Add FSlateBrush subclass constructors that take resource objects, to create static brushes at startup time that refer to already loaded UTextures

Change 3284381 on 2017/02/02 by Saul.Abreu

	#fortnite
	Added functionality to common button to actually use the single material brush setting and automatically create and expose access to a material instance dynamic.
	Also fixed some potential object lifetime issues in CommonButton.

Change 3282423 on 2017/02/01 by Ben.Zeigler

	Fix issue with synchronous load of objects not invalidating cached nulls for asset ptrs. This was always an issue but my recent change to assetptr SynchronousLoad made it manifest more often.
	In the future we may want to invalidate cached nulls on object creation to handle cases like save games

Change 3282265 on 2017/02/01 by Ian.Fox

	#UE4, #XMPP - Reduce verbosity of strophe receive-stanza log

Change 3282159 on 2017/02/01 by Ben.Zeigler

	Fix issue where async loading null string reference would deadlock

Change 3282054 on 2017/02/01 by James.Longstreet

	#fortnite #jira FORT-28234 Fix sceImeDialogTerm() warning

	We were calling sceImeDialogTerm() in cases where the dialog wasn't active.  The only state it's legitimate to call sceImeDialogTerm() in is SCE_IME_DIALOG_STATUS_FINISHED.

Change 3282051 on 2017/02/01 by James.Longstreet

	#fortnite #jira FORT-30021 Respect bVirtualKeyboardSendsTextChanged on PS4

Change 3281799 on 2017/02/01 by Nick.Cooper

	#Fortnite - Removed code on PS4 that was adding a EMouseButtons::Left press when the right shoulder button was pressed

	#jira FORT-35821

Change 3281771 on 2017/02/01 by Lukasz.Furman

	fixed end of path conditions breaking movement when path starts with a navlink and turn back below
	#jira FORT-36375

Change 3280579 on 2017/01/31 by Ben.Zeigler

	Remove ForEachProperty and switch AssetManager to use TPropertyValueIterator instead. Code review changes to TPropertyValueIterator.
	Add InitializeAssetBundlesFromMetadata to AssetManager which parses AssetBundles metadata at load/save time and uses that to assign asset references to specific bundles.
	Add RecursivelyExpandBundleData to allow recursively acquiring bundle dependencies.
	Add AssetBundles to the PropertyMetadata list. Sorted list and fixed some comment typos.
	Change it so the DataAsset factory won't show blueprintable native classes as valid, the editor doesn't like it when you have both blueprinted and non-blueprinted assets of the same base class.
	Change AssetPtr.LoadSynchronous to be const and to behave the same as Get(). This was a licensee complaint and the old behavior to null out the reference because of a transient load error can cause data loss.

Change 3280176 on 2017/01/31 by Lukasz.Furman

	fixed missing navmesh update on replacing existing floor/roof
	#jira FORT-36369

Change 3279761 on 2017/01/31 by Saul.Abreu

	#fortnite
	Added ability to keep common buttons interactable even when they're selected, so they can still be clicked. Old default behavior is preserved.

Change 3279678 on 2017/01/31 by Saul.Abreu

	#fortnite
	Moved GetCurrentInputType from UCommonActionWidget to UCommonUIContext for reuse.

Change 3278593 on 2017/01/30 by Ben.Zeigler

	Delay initialization of asset registry tag filter list until it is needed, to make sure it catches modules that were loaded after the asset registry module. Fixes issues with tags not inherting to child classes

Change 3278592 on 2017/01/30 by Ben.Zeigler

	Add TPropertyValueIterator to replace ForEachProperty. This is a recursive iterator instead of a predicate search, and also allows extracting the property chain, which I need for metadata parsing.
	AssetManager has an example of both uses, will delete ForEachProperty tomorrow.

Change 3277859 on 2017/01/30 by Lukasz.Furman

	fixed navigation path postprocessing not working with vertical navlinks near start point
	required by taker portal up move
	#jira FORT-36570

Change 3277580 on 2017/01/30 by Ben.Zeigler

	#jira FORT-36662 Return streamable manager to always use hard references, weak references do not work during AddReferencedObjects at all, because of the unreachable flag that is set during GC. This returns the behavior of forcing assets to be "Force Deleted" if they were loaded by asset manager.

Change 3276903 on 2017/01/29 by Bob.Tellez

	#BlueprintContext Updated copyright notice for 2017

Change 3276902 on 2017/01/29 by Bob.Tellez

	#CommonUI Updated some missed copyright notices for 2017

Change 3276731 on 2017/01/29 by Jeff.Campeau

	Forward modified warning messages

Change 3276642 on 2017/01/28 by Jeff.Campeau

	Change specific errors from packaging multiple configurations into a single package into warnings.

Change 3276228 on 2017/01/27 by Ben.Zeigler

	Fix crash/data loss when a Pin's DefaultObject points to an ObjectRedirector. FixObject will return redirectors so you need to correct for that

Change 3276073 on 2017/01/27 by Ben.Zeigler

	Add some utility functions to core asset manager and streamablemanager and fix various editor interactions.
	Add startup and cook interaction hooks to AssetManager directly and call them form Engine in addition to the game-specific hooks.
	Add concept of BulkScanning to AssetManager, use this when scanning many directories.
	Fix issues with string asset references being wrong for blueprint classes, this would be easier if the assetdata pointed to the class and not the possibly-missing blueprint.
	Change StreamableManager to keep referenced objects as weak pointers. These pointers are effectively soft in the editor, hard outside of the editor. By storing them as raw pointers it was difficult to guarantee their safety without causing issues with deleting assets.

Change 3276058 on 2017/01/27 by Ben.Zeigler

	Add UStruct::ForEachProperty, which recursively iterates properties of a struct and calls a lambda.
	Add some new BaseStructure accessors

Change 3275981 on 2017/01/27 by Alex.Thurman

	Move Item Management Screen input handling into child activatable panels.
	#JIRA FORT-35759
	#JIRA FORT-35758

Change 3275626 on 2017/01/27 by John.Pollard

	First pass at implementing net relevancy for replays

	* All connections are considered when determing if an actor is relevant
	* Enable by setting demo.UseNetRelevancy to 1
	* Override cull distance with demo.CullDistanceOverride

Change 3275221 on 2017/01/27 by John.Pollard

	FORT-36482 - Fix issue with using wrong serializer for re-mapping objects

Change 3274149 on 2017/01/26 by Alex.Thurman

	Expose the Common Widget Switcher as part of the common UI plugin library.
	#fort

Change 3274103 on 2017/01/26 by Michael.Trepka

	Fixed a crash on startup on Mac when using a shader cache populated during cooking and re-enabled cook time cache generation in Fortnite

Change 3273867 on 2017/01/26 by James.Hopkin

	Enabled Stomp for PS4

	#jira FORT-35517

Change 3273749 on 2017/01/26 by James.Hopkin

	Added libwebsocket libraries for PS4

	#jira FORT-35517

Change 3273105 on 2017/01/26 by James.Hopkin

	Moved libwebsockets.h into platform-specific folders (in preparation for PS4 building against slightly more recent version)

Change 3273020 on 2017/01/26 by Jeff.Campeau

	Stage manifest files from loose folder, not binary folder (appdata.bin is no longer created in the binary directory)

Change 3272825 on 2017/01/25 by Saad.Nader

	#fort
	Added function to set the auto activation on or off on a common widget switcher.
	Updated CommonTabListWidget to temporarily turn off activation when setting a linked switcher.
	Added some missing delegate cleanup code.

Change 3272598 on 2017/01/25 by Justin.Augspurger

	#fortnite
	Add activatable panel function that returns if an input handler is set.

Change 3272411 on 2017/01/25 by Michael.Trepka

	Increased g.TimeoutForBlockOnRenderFence on Mac to 5 minutes

Change 3271913 on 2017/01/25 by Lukasz.Furman

	fixed conditions of movement's destinaiton oveshot check to work with setup in FTest maps
	#jira FORT-36375

Change 3271723 on 2017/01/25 by Bob.Tellez

	#UE4 Disabled MfMedia on windows.

Change 3271223 on 2017/01/25 by Jeff.Campeau

	Allow packaging to fall back to the engine directory for the lastchunk file if it's not present in the staged version.

Change 3271066 on 2017/01/24 by Chris.Gagnon

	- Properly clean up the UIManager and Analog Cursor when game/pie exists.
	- FortAnalogCursor now sequesters the mouse during InputSuspension.
	- Change the callback order for activate and deactivate so the classes can chain activate.

Change 3271064 on 2017/01/24 by Chris.Gagnon

	- NavigateToWidget() added to SlateApplication
	- SlistView and it's descendants now have the ability to navigate to the widget scrolled into view

Change 3270778 on 2017/01/24 by Michael.Trepka

	Fixed and enabled cook time binary shader cache generation for Mac

Change 3270645 on 2017/01/24 by Jeff.Campeau

	- New manifest generation (backward compatible)
	- True support for multiconfig packages
	- Settings all based in target settings in editor
	- Localized package resources with support to reduce redundancies
	- Resource table generation and manifest generation combined
	- Further reduced unneeded deploy copies

	#jira FORT-36413

Change 3270191 on 2017/01/24 by Lukasz.Furman

	reverted file unrelated to DecoyDistance fix

Change 3270133 on 2017/01/24 by Lukasz.Furman

	fixed item scoring in DecoyDistance test
	#jira FORT-36034

Change 3269363 on 2017/01/24 by James.Hopkin

	#online #stomp Added error logging for loss of heartbeat

	#jira FORT-34763

Change 3268921 on 2017/01/23 by Saul.Abreu

	#fortnite
	Renamed CommonWidgetGroup to emphasize that it is an abstract base class. Added ability to get the selected button out of CommonButtonGroup.

Change 3268913 on 2017/01/23 by Saul.Abreu

	#fortnite
	Recreated some minor changes to UEnumProperty so that TMap properties don't assert with enums as keys.

Change 3268436 on 2017/01/23 by Michael.Trepka

	Added rhi.Metal.AllowRHIThread to allow games to disable RHI thread on Mac to be able to use shader cache, which currently is incompatible with RHI thread. Disabled RHI thread on Mac in Fortnite.

	Also, temporarily disabled Metal validation layer in Fortnite until I have more information on the cost of various levels of validation.

Change 3266945 on 2017/01/20 by Bob.Tellez

	#UE4 Allowing more configurations to generate debug symbols

Change 3266814 on 2017/01/20 by Bob.Tellez

	#UE4 Moved MfMedia video track handling to the render thread, which removes the need for some buffer management in MediaTextureResource.cpp. Also, Mfmedia now emits the PlaybackEndReached event.

	#JIRA FORT-31753

Change 3266541 on 2017/01/20 by Lukasz.Furman

	moved navigation export of building actor's static mesh into owning actor data instead of using parent chain,
	attempt to fix husks passing through walls that lost navigation data at some point during game
	#jira FORT-35741

Change 3266269 on 2017/01/20 by Fred.Kimberley

	Change async loading of gameplay cues so that the gameplay cue manager maintains ownership of the assets and can control their lifetime.

Change 3266053 on 2017/01/20 by Michael.Trepka

	Fixed issues with shader cache not working properly with Mac Metal (but it still requires -norhithread to work at all). Enabled the shader cache by default if RHI thread is disabled.

Change 3265585 on 2017/01/20 by Bart.Hawthorne

	Enable Oodle in Fortnite

Change 3264678 on 2017/01/19 by Lukasz.Furman

	fixed crash on opening behavior trees with invalid decorator class (empty redirectors)
	#ue4

Change 3264473 on 2017/01/19 by Fred.Kimberley

	Tell the streamable manager to manage gameplay cue assets that are async loaded.

	#jira FORT-35171

Change 3262846 on 2017/01/18 by John.Pollard

	FORT-30352 - Fix by lowering network logging verbosity for benign condition

Change 3262535 on 2017/01/18 by Michael.Trepka

	Fix for FORT-35776

	Make sure to set reasterizer state when rendering with a material in FSlateRHIRenderingPolicy::DrawElements

Change 3262386 on 2017/01/18 by John.Pollard

	Deprecate bPendingNetUpdate, NetUpdateTime and LastNetUpdateTime

Change 3262375 on 2017/01/18 by Ian.Fox

	#UE4, #XMPP - Handle Message stanza errors
	#JIRA OGS-505

Change 3262262 on 2017/01/18 by John.Pollard

	Turn on adaptive network updates by default

Change 3262215 on 2017/01/18 by Rob.Cannaday

	Fix for returned XMPP messages (to invalid recipient) triggering on message received delegates

Change 3262094 on 2017/01/18 by Jamie.Dale

	Cook on the fly builds now resolve string asset references

Change 3262091 on 2017/01/18 by Jamie.Dale

	Guarding against potentially invalid call to FString::Mid

Change 3262089 on 2017/01/18 by Jamie.Dale

	Fixing RedirectCollector issues with projects outside the UE4 directory

	It was storing relative paths, but MakeStandardFilename wouldn't make a relative path for anything outside of the UE4 directory. In addition to this, some code was testing filters using package style paths, so I converted all the code to use package style paths instead.

Change 3261201 on 2017/01/17 by Ben.Zeigler

	Perf improvements to PackageName that improve cooked load times by around a second. These string functions get called very often and Split is very slow, especially backwards searching or case insensitive.

Change 3261098 on 2017/01/17 by John.Pollard

	Fix for FORT-35711 - Edited buildings do not always replicate correctly

	We were removing the actor from the network object list too soon

Change 3260515 on 2017/01/17 by John.Abercrombie

	Fix MoveTo task ending with success when it's interrupted
	- Default the task to an invalid status rather than assuming we are successful

	#jira FORT-35497 - Defender can pick up a weapon from far away as they get knocked DBNO

Change 3260343 on 2017/01/17 by Lukasz.Furman

	fixed end of path conditions for crowd simulation when using string pulled path
	#jira FORT-35713

Change 3259419 on 2017/01/16 by John.Pollard

	Network actor list fixes:

	* Don't add add actor to network list if it will just immediately get removed
	* Remove destroyed actors from actor list on clients
	* Make sure actor Role is set to correct value before adding to network actor list

Change 3259104 on 2017/01/16 by Michael.Trepka

	Change the default for rhi.Metal.RuntimeDebugLevel  to 2, as 3 is too expensive for Development builds and disable METAL_DEBUG_OPTIONS in Test builds

Change 3259017 on 2017/01/16 by Saad.Nader

	#fort
	Added a missing remove delegate handler when widget is destructed.

Change 3258901 on 2017/01/16 by Saad.Nader

	#fort
	Added the ability to remove an input action from the list of actions we are listening for in an activatable panel.

Change 3258844 on 2017/01/16 by Ryan.Rauschkolb

	#fortnite
	Fixed issue where UUMGSequencePlayer:Tick would broadcast OnAnimationFinished before the final frame of the animation plays

Change 3258734 on 2017/01/16 by Michael.Trepka

	Fixed a crash on exit on Mac in FCocoaWindow's windowWillResize:

	#jira FORT-35720

Change 3258353 on 2017/01/16 by James.Hopkin

	#xmpp Fixed UserJid constructor to be constructed by value - same efficiency, less code and allows any combination of rvalues and lvalues.

Change 3257640 on 2017/01/13 by Saul.Abreu

	#fortnite
	#jira FORT-35387
	Item Quantity List widget, not yet complete.
	Minor tweak to widget factory (for pooling) to support player controllers as "outer"s.
	In progress refactor of list of resources given for a mulch operation, using the item quantity list widget.

Change 3257310 on 2017/01/13 by Bob.Tellez

	#UE4 Default stack size for windows is now configurable. There is a different number for windows editor targets than non-editor targets.

Change 3257094 on 2017/01/13 by John.Pollard

	Refactor network actor list management to be more efficient

	* Move dormancy list management to FNetworkObjectList
	* Optimize actor network dormancy by removing actors from the active list that are dormant on all connections
	* Removed NetUpdateTime on actor, and now use the NextUpdateTime on FNetworkObjectInfo (these values are more hot in the cache too)
	* We now early out of the consider logic faster when possible
	* Remove other misc unused network state/code and general cleanup

Change 3255891 on 2017/01/12 by Chris.Gagnon

	Added "Back" action to squads screens and armory landing.
	Added activation widget centering for squads screens.
	Added a couple Explicit navigations to get a better navigation experience.
	Added a bunch of Fkeys to the input binding table.
	Added PanelButton Widget.

Change 3254809 on 2017/01/11 by Bob.Tellez

	#UE4 Crash fix for shader views that get destroyed but still have pointers to them in the SRV cache.

Change 3254651 on 2017/01/11 by Bob.Tellez

	#UE4 Changed MfMedia track sync mode to Unbuffered since buffered causes a crash shortly after playing.

	#JIRA FORT-35566

Change 3254307 on 2017/01/11 by Lukasz.Furman

	fixed "Ftest start" command interfering with automation passes
	#jira FORT-35459

Change 3253625 on 2017/01/11 by Lukasz.Furman

	more accurate overshot detection for crowd simulation trying to reach last path corner
	#jira FORT-35502

Change 3252864 on 2017/01/10 by Lina.Halper

	fix for invalid anim curve issue when duplicating curves.

	#jira: FORT-35151

Change 3252427 on 2017/01/10 by Ben.Zeigler

	#jira UE-40390 Fix crash saving blueprint with an inherited DataTable/CurveTable reference. Delta serialization meant that the necessary name wasn't in the name table, so adding it manually now.
	Copied from CL #3252418

Change 3252344 on 2017/01/10 by Lukasz.Furman

	added navmesh tile observation to hotspots
	now they will be able to reevaluate unreachable slots if nearby navmesh is updated (active only when more than half melee slots is unreachable)
	#jira FORT-35450

Change 3251644 on 2017/01/09 by Saul.Abreu

	#fortnite
	#jira FORT-35388
	Refactored common input so that the actions hold the per-platform key mappings. A config file holds the mapping of individual keys to their per-platform display data (icon-only for now). ALL ENTRIES IN THE INPUT ACTION DATA TABLE ARE NOW MISSING THEIR KEYS. RE-ADD THEM. I did test that it works.

Change 3251118 on 2017/01/09 by David.Hamm

	Corrected ability system logging messages that are turning up in bug reports.

Change 3250932 on 2017/01/09 by Bob.Tellez

	#UE4 Unshelved from DanielW. Fix for memory usage during map save for large maps

Change 3250093 on 2017/01/06 by Jeff.Campeau

	libstrophe UE4 modifications

Change 3249787 on 2017/01/06 by John.Pollard

	Add some replay/network stats

Change 3248808 on 2017/01/05 by Chris.Gagnon

	Fix for ensuring Main Tabs properly activates it's content.
	Includes a pretty hacky delay, will need to deal with that at somepoint.

Change 3248693 on 2017/01/05 by Chris.Gagnon

	NavigationEvent now gets populated with the modifier keys so that Shift  and Ctrl behaviors of the list work.

Change 3248647 on 2017/01/05 by Saul.Abreu

	Fixed shadowed variable warning in Create Event node.

Change 3248358 on 2017/01/05 by Saul.Abreu

	Added return type/outputs to the signature displayed in the CreateEvent node. Also added tooltip describing the syntax for display since it's non-standard.

Change 3247937 on 2017/01/05 by Chris.Gagnon

	- Refactored the Custom Navigation Event to be a part of the Viewport so that it functions properly with Multi PIE and doesn't interfere with the Editor while PIE is running.
	- Added the ability for an FReply to specify an explict navigation attempt directly.
	- Added ENavigationGenesis to the navigation system allowing SListView and STileView's bHandleGamepadEvents functionality to be hooked up again.

Change 3247887 on 2017/01/05 by Bob.Tellez

	#UE4 UpdateExistingPackagePriorities does not work in EDL. It is now disabled.

	#JIRA FORT-35193

Change 3247770 on 2017/01/05 by Fred.Kimberley

	Fix an issue where PreAttributeBaseChange was not always being called and sometimes called after the attribute base value had changed.

Change 3247133 on 2017/01/04 by Saul.Abreu

	UWidget designer method renaming to avoid extremely likely naming collisions.

Change 3246507 on 2017/01/04 by Chris.Gagnon

	Created CommonBorder and UCommonBorderStyle very simple but will allow consistent sharing of styles.
	Cleaned up palette category usage and a few misc things.
	Updated the UI test material, and created a UI Test BorderStyle to utilize it.

Change 3245517 on 2017/01/03 by Chris.Gagnon

	Copying over slate material changes to provide more functionalit.
	Added a UITest Material as an example

Change 3245371 on 2017/01/03 by Lukasz.Furman

	fixed husks attacking props from far away
	#jira FORT-34655

Change 3245363 on 2017/01/03 by Justin.Sargent

	Tracked down a CEF viewport scaling issue to some changes made for supporting high DPI. After talking it over with Trepka, we decided to revert the specific change causing the CEF viewport regression. Trepka will be following up with a proper fix.

	#jira OPP-6513

Change 3244525 on 2017/01/02 by Chris.Gagnon

	Activatable panels now clear out action handlers when the slate widgets are released.

Change 3244517 on 2017/01/02 by Chris.Gagnon

	New frontend major refactors.
	- New content api for UI States
	- New intro / outro functionality for activatable panels
	- New CommonWidgetStack widget
	- Landing pages
	- Navigation suport for SListView, STileView
	- Navigation changes
	- Lots of New UI layout changes and functionality changes
	- More things that I'm forgetting

Change 3242434 on 2016/12/21 by Ben.Zeigler

	Improve package saving time by stopping export sorting from recursing into dependencies outside of the package. It has no control over them so it doesn't care about their load order.

Change 3242433 on 2016/12/21 by Ben.Zeigler

	Small perf improvement for quad tree, stop it from constantly reallocating memory when removing nodes as they will likely get filled again or the node will get deleted

Change 3242294 on 2016/12/21 by Bob.Tellez

	#UE4 Re-applying the fix for rendering editor primitives when r.EarlyZPassOnlyMaterialMasking is enabled

Change 3241034 on 2016/12/20 by John.Abercrombie

	Add or UpdateBlueprintSearchMetadata when we don't have a TargetPlatform
	- Better fix for issue mentioned in CL 3241023

Change 3241023 on 2016/12/20 by John.Abercrombie

	Fixed UBlueprint::PreSave crashing when there is no TargetPlatform (default behavior)

Change 3240988 on 2016/12/20 by Lukasz.Furman

	fixed melee defenders not finishing move then their goal is outside tether range
	#jira FORT-34673

Change 3240966 on 2016/12/20 by Ben.Zeigler

	Disable find in blueprint query when cooking for non editor platforms, saves around 50 seconds off of a Fortnite fast cook.
	UBlueprint::PreSave gets called even though they get filtered out of cooked builds, as the filtering is after PreSave.

Change 3240898 on 2016/12/20 by Lukasz.Furman

	fixed memory corruption in template A* solver
	#fortnite

Change 3239920 on 2016/12/19 by Ben.Zeigler

	Fix warning display for string asset references while cooking, now that failed to find errors add to KnownMissing, we need to check KnownMissing before doing the find, and turn off the internal warnings as the redirect collector has more context info

Change 3239819 on 2016/12/19 by Lukasz.Furman

	fixed uninitialized debug draw delegate pointers
	#ue4

Change 3238789 on 2016/12/16 by Ben.Zeigler

	Fix issue where spawned cues triggered from async loads wouldn't have a proper world
	Fix issue where bShouldSyncLoad/bShouldAsyncLoad were backwards

Change 3238782 on 2016/12/16 by Ben.Zeigler

	#jira FORT-34825 Fix issue where Macro CDOs had corrupted persistent ubergraph frames during blueprint compile on load, by changing it so no CDOs have persistent frames.
	This also saves memory as using persistent frames is incorrect for CDOs, things like latent functions do not make sense.
	Fix from Dan O'Connor

Change 3238685 on 2016/12/16 by Bob.Tellez

	#UE4 Graceful recovery for actors that changed mobility between frames in TextureInstanceManager.

	#JIRA FORT-34833

Change 3238671 on 2016/12/16 by Ben.Zeigler

	Fix ensure opening widget palette view, it was trying to create asset data for trash classes becuase it was just doing a raw class iterator, which is no longer supported.

Change 3238606 on 2016/12/16 by Rob.Cannaday

	Fix crash in FInternetAddrBSD::SetIp when InAddr is an empty string.
	#jira FORT-34826

Change 3238594 on 2016/12/16 by Ben.Zeigler

	#jira FORT-34704 Fix bNetTemporary actors to be created with reliable packets, to keep sending until their initial send is done.
	The code that used to resend incomplete net temporary actors appears to have stopped working sometime during UE4 networking refactors. Remove unused flags related to that code

Change 3238315 on 2016/12/16 by Lukasz.Furman

	fixed composite navigation path usage for husks not controlled by crowd simulation
	#jira FORT-34509

Change 3238145 on 2016/12/16 by Lukasz.Furman

	fixed crash in EQS profiler
	#jira FORT-34831

Change 3237479 on 2016/12/15 by Ben.Zeigler

	Don't crash if cue manager has no world, not sure how it got into this state

Change 3236992 on 2016/12/15 by Michael.Trepka

	Don't fall back to SM4 on Intel GPUs on Mac any more

Change 3236929 on 2016/12/15 by Bob.Tellez

	#UE4 Fixed an ensure that was caused by an FResourceSizeEx being initialized with the wrong type

Change 3236867 on 2016/12/15 by Bob.Tellez

	#Fortinte Submitted change from Gil to fix EDL crash loading into Outpost on PS4

	#JIRA FORT-34794

Change 3236747 on 2016/12/15 by Ben.Zeigler

	Fortnite fixes for asset manager/async loading changes
	FortItemDefinitions now async load needed assets on demand. Currently this is only loading AttributeTemplate, which may not even be in use
	The blueprints needed for weapons are now async loaded when the player puts them on their quickbar, instead of being loaded once and staying in memory forever
	FortAssetManager is now being used in parallel to the loading code in FortGlobals, I will remove the FortGlobals code in the next checkin once I know things are working
	Change it so the MissionEventNames are loaded asynchronously when clicking the picker, this code was half completed already so I finished it up
	Change it so GameplayCueNotifies get async loaded on demand instead of async loaded at startup, this improves startup load times
	Change it so the CommonUIModule uses the global assetmanager instead of a passed in StreamableManagerHandler
	Item json changed because it got resorted, no actual json changes other than a few cosmetics that were added yesterday
	Change it so FortItem implements the mcp item interface directly, instead of FortWorldItem and FortAccountItem implementing it separately

Change 3236746 on 2016/12/15 by Ben.Zeigler

	Add ProcessAsyncLoadingUntilComplete which will process async loading until a predicate is true or time runs out
	Change streamable manager to return a handle structure, that can be used to block or poll as needed. Active handles will keep objects in memory even after the load finishes
	FStreamableManager::SynchronousLoad now does high-priority-async-load-and-wait instead of doing a full async flush/static load object if asynch loading is in progress, this should make stalls much shorter when sync loading a single asset
	Deprecate some of the StreamableManager functions now that handles exist. The fact that SynchronousLoad kept an object from ever GCing was not expected behavior by most users

	Add Experimental feature AssetManager, which is a global singleton that supports loading assets on demand. It is disabled by default
	Add concept of PrimaryAssetID which is a Type:Name pair that globally identifies an asset. This is returned by GetPrimaryAssetId on UObject and is needed for the asset manager to work
	Add PrimaryAssetData class, which supports the primary asset and bundle concepts natively
	Add concept of an AssetBundleEntry/Data, which is a scoped map from name -> list of assets. If you modify an AssetBundleData it will get baked into the asset registry at runtime

	Fix KismetSystemLibrary and GameplayCueManager to use the new streaming functionality

Change 3234031 on 2016/12/13 by Ian.Fox

	#UE4, #XMPP - Finish libstrophe MUC (Multi-User Chat) implementation
	- Pull history when joining channels
	- Handle configuring of XMPP channels we create (and global chat rooms if we managed to create them)

[CL 3291644 by Bob Tellez in Main branch]
2017-02-07 23:55:24 -05:00

1562 lines
62 KiB
C#

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Net.NetworkInformation;
using System.Threading;
using AutomationTool;
using UnrealBuildTool;
using Ionic.Zip;
public class AndroidPlatform : Platform
{
private const int DeployMaxParallelCommands = 6;
private const string TargetAndroidLocation = "obb/";
public AndroidPlatform()
: base(UnrealTargetPlatform.Android)
{
}
private static string GetSONameWithoutArchitecture(ProjectParams Params, string DecoratedExeName)
{
return Path.Combine(Path.GetDirectoryName(Params.ProjectGameExeFilename), DecoratedExeName) + ".so";
}
private static string GetFinalApkName(ProjectParams Params, string DecoratedExeName, bool bRenameUE4Game, string Architecture, string GPUArchitecture)
{
string ProjectDir = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(Params.RawProjectPath.FullName)), "Binaries/Android");
if (Params.Prebuilt)
{
ProjectDir = Path.Combine(Params.BaseStageDirectory, "Android");
}
// Apk's go to project location, not necessarily where the .so is (content only packages need to output to their directory)
string ApkName = Path.Combine(ProjectDir, DecoratedExeName) + Architecture + GPUArchitecture + ".apk";
// if the source binary was UE4Game, handle using it or switching to project name
if (Path.GetFileNameWithoutExtension(Params.ProjectGameExeFilename) == "UE4Game")
{
if (bRenameUE4Game)
{
// replace UE4Game with project name (only replace in the filename part)
ApkName = Path.Combine(Path.GetDirectoryName(ApkName), Path.GetFileName(ApkName).Replace("UE4Game", Params.ShortProjectName));
}
else
{
// if we want to use UE4 directly then use it from the engine directory not project directory
ApkName = ApkName.Replace(ProjectDir, Path.Combine(CmdEnv.LocalRoot, "Engine/Binaries/Android"));
}
}
return ApkName;
}
private static string GetFinalObbName(string ApkName)
{
// calculate the name for the .obb file
string PackageName = GetPackageInfo(ApkName, false);
if (PackageName == null)
{
throw new AutomationException(ExitCode.Error_FailureGettingPackageInfo, "Failed to get package name from " + ApkName);
}
string PackageVersion = GetPackageInfo(ApkName, true);
if (PackageVersion == null || PackageVersion.Length == 0)
{
throw new AutomationException(ExitCode.Error_FailureGettingPackageInfo, "Failed to get package version from " + ApkName);
}
if (PackageVersion.Length > 0)
{
int IntVersion = int.Parse(PackageVersion);
PackageVersion = IntVersion.ToString("0");
}
string ObbName = string.Format("main.{0}.{1}.obb", PackageVersion, PackageName);
// plop the .obb right next to the executable
ObbName = Path.Combine(Path.GetDirectoryName(ApkName), ObbName);
return ObbName;
}
private static string GetDeviceObbName(string ApkName)
{
string ObbName = GetFinalObbName(ApkName);
string PackageName = GetPackageInfo(ApkName, false);
return TargetAndroidLocation + PackageName + "/" + Path.GetFileName(ObbName);
}
private static string GetStorageQueryCommand()
{
if (Utils.IsRunningOnMono)
{
return "shell 'echo $EXTERNAL_STORAGE'";
}
else
{
return "shell \"echo $EXTERNAL_STORAGE\"";
}
}
private static string GetFinalBatchName(string ApkName, ProjectParams Params, string Architecture, string GPUArchitecture, bool bNoOBBInstall, bool bUninstall, UnrealTargetPlatform Target)
{
string Extension = ".bat";
switch (Target)
{
default:
case UnrealTargetPlatform.Win64:
Extension = ".bat";
break;
case UnrealTargetPlatform.Linux:
Extension = ".sh";
break;
case UnrealTargetPlatform.Mac:
Extension = ".command";
break;
}
return Path.Combine(Path.GetDirectoryName(ApkName), (bUninstall ? "Uninstall_" : "Install_") + Params.ShortProjectName + (!bNoOBBInstall ? "_" : "_NoOBBInstall_") + Params.ClientConfigsToBuild[0].ToString() + Architecture + GPUArchitecture + Extension);
}
private List<string> CollectPluginDataPaths(DeploymentContext SC)
{
// collect plugin extra data paths from target receipts
List<string> PluginExtras = new List<string>();
foreach (StageTarget Target in SC.StageTargets)
{
TargetReceipt Receipt = Target.Receipt;
var Results = Receipt.AdditionalProperties.Where(x => x.Name == "AndroidPlugin");
foreach (var Property in Results)
{
// Keep only unique paths
string PluginPath = Property.Value;
if (PluginExtras.FirstOrDefault(x => x == PluginPath) == null)
{
PluginExtras.Add(PluginPath);
Log("AndroidPlugin: {0}", PluginPath);
}
}
}
return PluginExtras;
}
public override void Package(ProjectParams Params, DeploymentContext SC, int WorkingCL)
{
IAndroidToolChain ToolChain = AndroidExports.CreateToolChain(Params.RawProjectPath);
var Architectures = ToolChain.GetAllArchitectures();
var GPUArchitectures = ToolChain.GetAllGPUArchitectures();
bool bMakeSeparateApks = UnrealBuildTool.AndroidExports.ShouldMakeSeparateApks();
var Deploy = AndroidExports.CreateDeploymentHandler(Params.RawProjectPath);
bool bPackageDataInsideApk = Deploy.PackageDataInsideApk(false);
string BaseApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, "", "");
Log("BaseApkName = {0}", BaseApkName);
// Create main OBB with entire contents of staging dir. This
// includes any PAK files, movie files, etc.
string LocalObbName = SC.StageDirectory.TrimEnd(new char[] {'/', '\\'})+".obb";
// Always delete the target OBB file if it exists
if (File.Exists(LocalObbName))
{
File.Delete(LocalObbName);
}
// Now create the OBB as a ZIP archive.
Log("Creating {0} from {1}", LocalObbName, SC.StageDirectory);
using (ZipFile ObbFile = new ZipFile(LocalObbName))
{
ObbFile.CompressionMethod = CompressionMethod.None;
ObbFile.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
ObbFile.UseZip64WhenSaving = Ionic.Zip.Zip64Option.Never;
int ObbFileCount = 0;
ObbFile.AddProgress +=
delegate(object sender, AddProgressEventArgs e)
{
if (e.EventType == ZipProgressEventType.Adding_AfterAddEntry)
{
ObbFileCount += 1;
Log("[{0}/{1}] Adding {2} to OBB",
ObbFileCount, e.EntriesTotal,
e.CurrentEntry.FileName);
}
};
ObbFile.AddDirectory(SC.StageDirectory+"/"+SC.ShortProjectName, SC.ShortProjectName);
try
{
ObbFile.Save();
}
catch (Exception)
{
Log("Failed to build OBB: " + LocalObbName);
throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not build OBB {0}. The file may be too big to fit in an OBB (2 GiB limit)", LocalObbName);
}
}
// collect plugin extra data paths from target receipts
Deploy.SetAndroidPluginData(Architectures, CollectPluginDataPaths(SC));
foreach (string Architecture in Architectures)
{
foreach (string GPUArchitecture in GPUArchitectures)
{
string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
if (!SC.IsCodeBasedProject)
{
string UE4SOName = GetFinalApkName(Params, SC.StageExecutables[0], false, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
UE4SOName = UE4SOName.Replace(".apk", ".so");
if (FileExists_NoExceptions(UE4SOName) == false)
{
Log("Failed to find game .so " + UE4SOName);
throw new AutomationException(ExitCode.Error_MissingExecutable, "Stage Failed. Could not find .so {0}. You may need to build the UE4 project with your target configuration and platform.", UE4SOName);
}
}
if (!Params.Prebuilt)
{
string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
string SOName = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
Deploy.PrepForUATPackageOrDeploy(Params.RawProjectPath, Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, false);
}
// Create APK specific OBB in case we have a detached OBB.
string DeviceObbName = "";
string ObbName = "";
if (!bPackageDataInsideApk)
{
DeviceObbName = GetDeviceObbName(ApkName);
ObbName = GetFinalObbName(ApkName);
CopyFile(LocalObbName, ObbName);
}
//figure out which platforms we need to create install files for
bool bNeedsPCInstall = false;
bool bNeedsMacInstall = false;
bool bNeedsLinuxInstall = false;
GetPlatformInstallOptions(SC, out bNeedsPCInstall, out bNeedsMacInstall, out bNeedsLinuxInstall);
//helper delegate to prevent code duplication but allow us access to all the local variables we need
var CreateInstallFilesAction = new Action<UnrealTargetPlatform>(Target =>
{
bool bIsPC = (Target == UnrealTargetPlatform.Win64);
// Write install batch file(s).
string BatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", false, false, Target);
string PackageName = GetPackageInfo(ApkName, false);
// make a batch file that can be used to install the .apk and .obb files
string[] BatchLines = GenerateInstallBatchFile(bPackageDataInsideApk, PackageName, ApkName, Params, ObbName, DeviceObbName, false, bIsPC);
File.WriteAllLines(BatchName, BatchLines);
// make a batch file that can be used to uninstall the .apk and .obb files
string UninstallBatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", false, true, Target);
BatchLines = GenerateUninstallBatchFile(bPackageDataInsideApk, PackageName, ApkName, Params, ObbName, DeviceObbName, false, bIsPC);
File.WriteAllLines(UninstallBatchName, BatchLines);
if (Utils.IsRunningOnMono)
{
CommandUtils.FixUnixFilePermissions(BatchName);
CommandUtils.FixUnixFilePermissions(UninstallBatchName);
//if(File.Exists(NoInstallBatchName))
//{
// CommandUtils.FixUnixFilePermissions(NoInstallBatchName);
//}
}
}
);
if (bNeedsPCInstall)
{
CreateInstallFilesAction.Invoke(UnrealTargetPlatform.Win64);
}
if (bNeedsMacInstall)
{
CreateInstallFilesAction.Invoke(UnrealTargetPlatform.Mac);
}
if (bNeedsLinuxInstall)
{
CreateInstallFilesAction.Invoke(UnrealTargetPlatform.Linux);
}
// If we aren't packaging data in the APK then lets write out a bat file to also let us test without the OBB
// on the device.
//String NoInstallBatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", true, false);
// if(!bPackageDataInsideApk)
//{
// BatchLines = GenerateInstallBatchFile(bPackageDataInsideApk, PackageName, ApkName, Params, ObbName, DeviceObbName, true);
// File.WriteAllLines(NoInstallBatchName, BatchLines);
//}
}
}
PrintRunTime();
}
private string[] GenerateInstallBatchFile(bool bPackageDataInsideApk, string PackageName, string ApkName, ProjectParams Params, string ObbName, string DeviceObbName, bool bNoObbInstall, bool bIsPC)
{
string[] BatchLines = null;
string ReadPermissionGrantCommand = "shell pm grant " + PackageName + " android.permission.READ_EXTERNAL_STORAGE";
string WritePermissionGrantCommand = "shell pm grant " + PackageName + " android.permission.WRITE_EXTERNAL_STORAGE";
if (!bIsPC)
{
string OBBInstallCommand = bNoObbInstall ? "shell 'rm -r $EXTERNAL_STORAGE/" + DeviceObbName + "'" : "push " + Path.GetFileName(ObbName) + " $STORAGE/" + DeviceObbName;
Log("Writing shell script for install with {0}", bPackageDataInsideApk ? "data in APK" : "separate obb");
BatchLines = new string[] {
"#!/bin/sh",
"cd \"`dirname \"$0\"`\"",
"ADB=",
"if [ \"$ANDROID_HOME\" != \"\" ]; then ADB=$ANDROID_HOME/platform-tools/adb; else ADB=" +Environment.GetEnvironmentVariable("ANDROID_HOME") + "/platform-tools/adb; fi",
"DEVICE=",
"if [ \"$1\" != \"\" ]; then DEVICE=\"-s $1\"; fi",
"echo",
"echo Uninstalling existing application. Failures here can almost always be ignored.",
"$ADB $DEVICE uninstall " + PackageName,
"echo",
"echo Installing existing application. Failures here indicate a problem with the device \\(connection or storage permissions\\) and are fatal.",
"$ADB $DEVICE install " + Path.GetFileName(ApkName),
"if [ $? -eq 0 ]; then",
"\techo",
bPackageDataInsideApk ? "" : "\techo Grant READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE to the apk for reading OBB file.",
bPackageDataInsideApk ? "" : "\t$ADB $DEVICE " + ReadPermissionGrantCommand,
bPackageDataInsideApk ? "" : "\t$ADB $DEVICE " + WritePermissionGrantCommand,
"\techo",
"\techo Removing old data. Failures here are usually fine - indicating the files were not on the device.",
"\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/" + Params.ShortProjectName + "'",
"\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/UE4CommandLine.txt" + "'",
"\t$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/" + TargetAndroidLocation + PackageName + "'",
bPackageDataInsideApk ? "" : "\techo",
bPackageDataInsideApk ? "" : "\techo Installing new data. Failures here indicate storage problems \\(missing SD card or bad permissions\\) and are fatal.",
bPackageDataInsideApk ? "" : "\tSTORAGE=$(echo \"`$ADB $DEVICE shell 'echo $EXTERNAL_STORAGE'`\" | cat -v | tr -d '^M')",
bPackageDataInsideApk ? "" : "\t$ADB $DEVICE " + OBBInstallCommand,
bPackageDataInsideApk ? "if [ 1 ]; then" : "\tif [ $? -eq 0 ]; then",
"\t\techo",
"\t\techo Installation successful",
"\t\texit 0",
"\tfi",
"fi",
"echo",
"echo There was an error installing the game or the obb file. Look above for more info.",
"echo",
"echo Things to try:",
"echo Check that the device (and only the device) is listed with \\\"$ADB devices\\\" from a command prompt.",
"echo Make sure all Developer options look normal on the device",
"echo Check that the device has an SD card.",
"exit 1"
};
}
else
{
string OBBInstallCommand = bNoObbInstall ? "shell rm -r %STORAGE%/" + DeviceObbName : "push " + Path.GetFileName(ObbName) + " %STORAGE%/" + DeviceObbName;
Log("Writing bat for install with {0}", bPackageDataInsideApk ? "data in APK" : "separate OBB");
BatchLines = new string[] {
"setlocal",
"set ANDROIDHOME=%ANDROID_HOME%",
"if \"%ANDROIDHOME%\"==\"\" set ANDROIDHOME="+Environment.GetEnvironmentVariable("ANDROID_HOME"),
"set ADB=%ANDROIDHOME%\\platform-tools\\adb.exe",
"set DEVICE=",
"if not \"%1\"==\"\" set DEVICE=-s %1",
"for /f \"delims=\" %%A in ('%ADB% %DEVICE% " + GetStorageQueryCommand() +"') do @set STORAGE=%%A",
"@echo.",
"@echo Uninstalling existing application. Failures here can almost always be ignored.",
"%ADB% %DEVICE% uninstall " + PackageName,
"@echo.",
"@echo Installing existing application. Failures here indicate a problem with the device (connection or storage permissions) and are fatal.",
"%ADB% %DEVICE% install " + Path.GetFileName(ApkName),
"@if \"%ERRORLEVEL%\" NEQ \"0\" goto Error",
"%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/" + Params.ShortProjectName,
"%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt", // we need to delete the commandline in UE4Game or it will mess up loading
"%ADB% %DEVICE% shell rm -r %STORAGE%/" + TargetAndroidLocation + PackageName,
bPackageDataInsideApk ? "" : "@echo.",
bPackageDataInsideApk ? "" : "@echo Installing new data. Failures here indicate storage problems (missing SD card or bad permissions) and are fatal.",
bPackageDataInsideApk ? "" : "%ADB% %DEVICE% " + OBBInstallCommand,
bPackageDataInsideApk ? "" : "if \"%ERRORLEVEL%\" NEQ \"0\" goto Error",
"@echo.",
bPackageDataInsideApk ? "" : "@echo Grant READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE to the apk for reading OBB file.",
bPackageDataInsideApk ? "" : "%ADB% %DEVICE% " + ReadPermissionGrantCommand,
bPackageDataInsideApk ? "" : "%ADB% %DEVICE% " + WritePermissionGrantCommand,
"@echo.",
"@echo Installation successful",
"goto:eof",
":Error",
"@echo.",
"@echo There was an error installing the game or the obb file. Look above for more info.",
"@echo.",
"@echo Things to try:",
"@echo Check that the device (and only the device) is listed with \"%ADB$ devices\" from a command prompt.",
"@echo Make sure all Developer options look normal on the device",
"@echo Check that the device has an SD card.",
"@pause"
};
}
return BatchLines;
}
private string[] GenerateUninstallBatchFile(bool bPackageDataInsideApk, string PackageName, string ApkName, ProjectParams Params, string ObbName, string DeviceObbName, bool bNoObbInstall, bool bIsPC)
{
string[] BatchLines = null;
if (!bIsPC)
{
Log("Writing shell script for uninstall with {0}", bPackageDataInsideApk ? "data in APK" : "separate obb");
BatchLines = new string[] {
"#!/bin/sh",
"cd \"`dirname \"$0\"`\"",
"ADB=",
"if [ \"$ANDROID_HOME\" != \"\" ]; then ADB=$ANDROID_HOME/platform-tools/adb; else ADB=" +Environment.GetEnvironmentVariable("ANDROID_HOME") + "/platform-tools/adb; fi",
"DEVICE=",
"if [ \"$1\" != \"\" ]; then DEVICE=\"-s $1\"; fi",
"echo",
"echo Uninstalling existing application. Failures here can almost always be ignored.",
"$ADB $DEVICE uninstall " + PackageName,
"echo",
"echo Removing old data. Failures here are usually fine - indicating the files were not on the device.",
"$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/" + Params.ShortProjectName + "'",
"$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/UE4Game/UE4CommandLine.txt" + "'",
"$ADB $DEVICE shell 'rm -r $EXTERNAL_STORAGE/" + TargetAndroidLocation + PackageName + "'",
"echo",
"echo Uninstall completed",
"exit 0",
};
}
else
{
Log("Writing bat for uninstall with {0}", bPackageDataInsideApk ? "data in APK" : "separate OBB");
BatchLines = new string[] {
"setlocal",
"set ANDROIDHOME=%ANDROID_HOME%",
"if \"%ANDROIDHOME%\"==\"\" set ANDROIDHOME="+Environment.GetEnvironmentVariable("ANDROID_HOME"),
"set ADB=%ANDROIDHOME%\\platform-tools\\adb.exe",
"set DEVICE=",
"if not \"%1\"==\"\" set DEVICE=-s %1",
"for /f \"delims=\" %%A in ('%ADB% %DEVICE% " + GetStorageQueryCommand() +"') do @set STORAGE=%%A",
"@echo.",
"@echo Uninstalling existing application. Failures here can almost always be ignored.",
"%ADB% %DEVICE% uninstall " + PackageName,
"@echo.",
"echo Removing old data. Failures here are usually fine - indicating the files were not on the device.",
"%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/" + Params.ShortProjectName,
"%ADB% %DEVICE% shell rm -r %STORAGE%/UE4Game/UE4CommandLine.txt", // we need to delete the commandline in UE4Game or it will mess up loading
"%ADB% %DEVICE% shell rm -r %STORAGE%/" + TargetAndroidLocation + PackageName,
"@echo.",
"@echo Uninstall completed",
};
}
return BatchLines;
}
public override void GetFilesToArchive(ProjectParams Params, DeploymentContext SC)
{
if (SC.StageTargetConfigurations.Count != 1)
{
throw new AutomationException(ExitCode.Error_OnlyOneTargetConfigurationSupported, "Android is currently only able to package one target configuration at a time, but StageTargetConfigurations contained {0} configurations", SC.StageTargetConfigurations.Count);
}
IAndroidToolChain ToolChain = AndroidExports.CreateToolChain(Params.RawProjectPath);
var Architectures = ToolChain.GetAllArchitectures();
var GPUArchitectures = ToolChain.GetAllGPUArchitectures();
bool bMakeSeparateApks = UnrealBuildTool.AndroidExports.ShouldMakeSeparateApks();
bool bPackageDataInsideApk = UnrealBuildTool.AndroidExports.CreateDeploymentHandler(Params.RawProjectPath).PackageDataInsideApk(false);
bool bAddedOBB = false;
foreach (string Architecture in Architectures)
{
foreach (string GPUArchitecture in GPUArchitectures)
{
string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "");
string ObbName = GetFinalObbName(ApkName);
//string NoOBBBatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", true, false);
// verify the files exist
if (!FileExists(ApkName))
{
throw new AutomationException(ExitCode.Error_AppNotFound, "ARCHIVE FAILED - {0} was not found", ApkName);
}
if (!bPackageDataInsideApk && !FileExists(ObbName))
{
throw new AutomationException(ExitCode.Error_ObbNotFound, "ARCHIVE FAILED - {0} was not found", ObbName);
}
SC.ArchiveFiles(Path.GetDirectoryName(ApkName), Path.GetFileName(ApkName));
if (!bPackageDataInsideApk && !bAddedOBB)
{
bAddedOBB = true;
SC.ArchiveFiles(Path.GetDirectoryName(ObbName), Path.GetFileName(ObbName));
}
bool bNeedsPCInstall = false;
bool bNeedsMacInstall = false;
bool bNeedsLinuxInstall = false;
GetPlatformInstallOptions(SC, out bNeedsPCInstall, out bNeedsMacInstall, out bNeedsLinuxInstall);
//helper delegate to prevent code duplication but allow us access to all the local variables we need
var CreateBatchFilesAndArchiveAction = new Action<UnrealTargetPlatform>(Target =>
{
string BatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", false, false, Target);
string UninstallBatchName = GetFinalBatchName(ApkName, Params, bMakeSeparateApks ? Architecture : "", bMakeSeparateApks ? GPUArchitecture : "", false, true, Target);
SC.ArchiveFiles(Path.GetDirectoryName(BatchName), Path.GetFileName(BatchName));
SC.ArchiveFiles(Path.GetDirectoryName(UninstallBatchName), Path.GetFileName(UninstallBatchName));
//SC.ArchiveFiles(Path.GetDirectoryName(NoOBBBatchName), Path.GetFileName(NoOBBBatchName));
}
);
//it's possible we will need both PC and Mac/Linux install files, do both
if (bNeedsPCInstall)
{
CreateBatchFilesAndArchiveAction(UnrealTargetPlatform.Win64);
}
if (bNeedsMacInstall)
{
CreateBatchFilesAndArchiveAction(UnrealTargetPlatform.Mac);
}
if (bNeedsLinuxInstall)
{
CreateBatchFilesAndArchiveAction(UnrealTargetPlatform.Linux);
}
}
}
}
private void GetPlatformInstallOptions(DeploymentContext SC, out bool bNeedsPCInstall, out bool bNeedsMacInstall, out bool bNeedsLinuxInstall)
{
ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, DirectoryReference.FromFile(SC.RawProjectPath), SC.StageTargetPlatform.PlatformType);
bool bGenerateAllPlatformInstall = false;
Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bCreateAllPlatformsInstall", out bGenerateAllPlatformInstall);
bNeedsPCInstall = bNeedsMacInstall = bNeedsLinuxInstall = false;
if (bGenerateAllPlatformInstall)
{
bNeedsPCInstall = bNeedsMacInstall = bNeedsLinuxInstall = true;
}
else
{
if (HostPlatform.Current.HostEditorPlatform == UnrealTargetPlatform.Mac)
{
bNeedsMacInstall = true;
}
else if (HostPlatform.Current.HostEditorPlatform == UnrealTargetPlatform.Linux)
{
bNeedsLinuxInstall = true;
}
else
{
bNeedsPCInstall = true;
}
}
}
private string GetAdbCommandLine(ProjectParams Params, string SerialNumber, string Args)
{
if (SerialNumber != "")
{
SerialNumber = "-s " + SerialNumber;
}
return string.Format("{0} {1}", SerialNumber, Args);
}
static string LastSpewFilename = "";
public string ADBSpewFilter(string Message)
{
if (Message.StartsWith("[") && Message.Contains("%]"))
{
int LastIndex = Message.IndexOf(":");
LastIndex = LastIndex == -1 ? Message.Length : LastIndex;
if (Message.Length > 7)
{
string Filename = Message.Substring(7, LastIndex - 7);
if (Filename == LastSpewFilename)
{
return null;
}
LastSpewFilename = Filename;
}
return Message;
}
return Message;
}
private IProcessResult RunAdbCommand(ProjectParams Params, string SerialNumber, string Args, string Input = null, ERunOptions Options = ERunOptions.Default)
{
string AdbCommand = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/platform-tools/adb" + (Utils.IsRunningOnMono ? "" : ".exe"));
if (Options.HasFlag(ERunOptions.AllowSpew) || Options.HasFlag(ERunOptions.SpewIsVerbose))
{
LastSpewFilename = "";
return Run(AdbCommand, GetAdbCommandLine(Params, SerialNumber, Args), Input, Options, SpewFilterCallback: new ProcessResult.SpewFilterCallbackType(ADBSpewFilter));
}
return Run(AdbCommand, GetAdbCommandLine(Params, SerialNumber, Args), Input, Options);
}
private string RunAndLogAdbCommand(ProjectParams Params, string SerialNumber, string Args, out int SuccessCode)
{
string AdbCommand = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%/platform-tools/adb" + (Utils.IsRunningOnMono ? "" : ".exe"));
LastSpewFilename = "";
return RunAndLog(CmdEnv, AdbCommand, GetAdbCommandLine(Params, SerialNumber, Args), out SuccessCode, SpewFilterCallback: new ProcessResult.SpewFilterCallbackType(ADBSpewFilter));
}
public override void GetConnectedDevices(ProjectParams Params, out List<string> Devices)
{
Devices = new List<string>();
IProcessResult Result = RunAdbCommand(Params, "", "devices");
if (Result.Output.Length > 0)
{
string[] LogLines = Result.Output.Split(new char[] { '\n', '\r' });
bool FoundList = false;
for (int i = 0; i < LogLines.Length; ++i)
{
if (FoundList == false)
{
if (LogLines[i].StartsWith("List of devices attached"))
{
FoundList = true;
}
continue;
}
string[] DeviceLine = LogLines[i].Split(new char[] { '\t' });
if (DeviceLine.Length == 2)
{
// the second param should be "device"
// if it's not setup correctly it might be "unattached" or "powered off" or something like that
// warning in that case
if (DeviceLine[1] == "device")
{
Devices.Add("@" + DeviceLine[0]);
}
else
{
CommandUtils.LogWarning("Device attached but in bad state {0}:{1}", DeviceLine[0], DeviceLine[1]);
}
}
}
}
}
/*
private class TimeRegion : System.IDisposable
{
private System.DateTime StartTime { get; set; }
private string Format { get; set; }
private System.Collections.Generic.List<object> FormatArgs { get; set; }
public TimeRegion(string format, params object[] format_args)
{
Format = format;
FormatArgs = new List<object>(format_args);
StartTime = DateTime.UtcNow;
}
public void Dispose()
{
double total_time = (DateTime.UtcNow - StartTime).TotalMilliseconds / 1000.0;
FormatArgs.Insert(0, total_time);
CommandUtils.Log(Format, FormatArgs.ToArray());
}
}
*/
public override bool RetrieveDeployedManifests(ProjectParams Params, DeploymentContext SC, string DeviceName, out List<string> UFSManifests, out List<string> NonUFSManifests)
{
UFSManifests = null;
NonUFSManifests = null;
// Query the storage path from the device
string DeviceStorageQueryCommand = GetStorageQueryCommand();
IProcessResult StorageResult = RunAdbCommand(Params, DeviceName, DeviceStorageQueryCommand, null, ERunOptions.AppMustExist);
String StorageLocation = StorageResult.Output.Trim();
string RemoteDir = StorageLocation + "/UE4Game/" + Params.ShortProjectName;
// Note: appends the device name to make the filename unique; these files will be deleted later during delta manifest generation
// Replace colon with underscore for legal filename (colon may be present for wifi connected devices)
string SanitizedDeviceName = DeviceName.Replace(":", "_");
// Try retrieving the UFS files manifest files from the device
string UFSManifestFileName = CombinePaths(SC.StageDirectory, SC.UFSDeployedManifestFileName + "_" + SanitizedDeviceName);
IProcessResult UFSResult = RunAdbCommand(Params, DeviceName, " pull " + RemoteDir + "/" + SC.UFSDeployedManifestFileName + " \"" + UFSManifestFileName + "\"", null, ERunOptions.AppMustExist);
if (!(UFSResult.Output.Contains("bytes") || UFSResult.Output.Contains("[100%]")))
{
return false;
}
// Try retrieving the non UFS files manifest files from the device
string NonUFSManifestFileName = CombinePaths(SC.StageDirectory, SC.NonUFSDeployedManifestFileName + "_" + SanitizedDeviceName);
IProcessResult NonUFSResult = RunAdbCommand(Params, DeviceName, " pull " + RemoteDir + "/" + SC.NonUFSDeployedManifestFileName + " \"" + NonUFSManifestFileName + "\"", null, ERunOptions.AppMustExist);
if (!(NonUFSResult.Output.Contains("bytes") || NonUFSResult.Output.Contains("[100%]")))
{
// Did not retrieve both so delete one we did retrieve
File.Delete(UFSManifestFileName);
return false;
}
// Return the manifest files
UFSManifests = new List<string>();
UFSManifests.Add(UFSManifestFileName);
NonUFSManifests = new List<string>();
NonUFSManifests.Add(NonUFSManifestFileName);
return true;
}
internal class LongestFirst : IComparer<string>
{
public int Compare(string a, string b)
{
if (a.Length == b.Length) return a.CompareTo(b);
else return b.Length - a.Length;
}
}
public override void Deploy(ProjectParams Params, DeploymentContext SC)
{
foreach (var DeviceName in Params.DeviceNames)
{
string DeviceArchitecture = GetBestDeviceArchitecture(Params, DeviceName);
string GPUArchitecture = GetBestGPUArchitecture(Params, DeviceName);
string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, DeviceArchitecture, GPUArchitecture);
// make sure APK is up to date (this is fast if so)
var Deploy = AndroidExports.CreateDeploymentHandler(Params.RawProjectPath);
if (!Params.Prebuilt)
{
string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
string SOName = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
List<string> Architectures = new List<string>();
Architectures.Add(DeviceArchitecture);
Deploy.SetAndroidPluginData(Architectures, CollectPluginDataPaths(SC));
Deploy.PrepForUATPackageOrDeploy(Params.RawProjectPath, Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, true);
}
// now we can use the apk to get more info
string PackageName = GetPackageInfo(ApkName, false);
// Setup the OBB name and add the storage path (queried from the device) to it
string DeviceStorageQueryCommand = GetStorageQueryCommand();
IProcessResult Result = RunAdbCommand(Params, DeviceName, DeviceStorageQueryCommand, null, ERunOptions.AppMustExist);
String StorageLocation = Result.Output.Trim(); // "/mnt/sdcard";
string DeviceObbName = StorageLocation + "/" + GetDeviceObbName(ApkName);
string RemoteDir = StorageLocation + "/UE4Game/" + Params.ShortProjectName;
// determine if APK out of date
string APKLastUpdateTime = new FileInfo(ApkName).LastWriteTime.ToString();
bool bNeedAPKInstall = true;
if (Params.IterativeDeploy)
{
// Check for apk installed with this package name on the device
IProcessResult InstalledResult = RunAdbCommand(Params, DeviceName, "shell pm list packages " + PackageName, null, ERunOptions.AppMustExist);
if (InstalledResult.Output.Contains(PackageName))
{
// See if apk is up to date on device
InstalledResult = RunAdbCommand(Params, DeviceName, "shell cat " + RemoteDir + "/APKFileStamp.txt", null, ERunOptions.AppMustExist);
if (InstalledResult.Output.StartsWith("APK: "))
{
if (InstalledResult.Output.Substring(5).Trim() == APKLastUpdateTime)
bNeedAPKInstall = false;
// Stop the previously running copy (uninstall/install did this before)
InstalledResult = RunAdbCommand(Params, DeviceName, "shell am force-stop " + PackageName, null, ERunOptions.AppMustExist);
if (InstalledResult.Output.Contains("Error"))
{
// force-stop not supported (Android < 3.0) so check if package is actually running
// Note: cannot use grep here since it may not be installed on device
InstalledResult = RunAdbCommand(Params, DeviceName, "shell ps", null, ERunOptions.AppMustExist);
if (InstalledResult.Output.Contains(PackageName))
{
// it is actually running so use the slow way to kill it (uninstall and reinstall)
bNeedAPKInstall = true;
}
}
}
}
}
// install new APK if needed
if (bNeedAPKInstall)
{
// try uninstalling an old app with the same identifier.
int SuccessCode = 0;
string UninstallCommandline = "uninstall " + PackageName;
RunAndLogAdbCommand(Params, DeviceName, UninstallCommandline, out SuccessCode);
// install the apk
string InstallCommandline = "install \"" + ApkName + "\"";
string InstallOutput = RunAndLogAdbCommand(Params, DeviceName, InstallCommandline, out SuccessCode);
int FailureIndex = InstallOutput.IndexOf("Failure");
// adb install doesn't always return an error code on failure, and instead prints "Failure", followed by an error code.
if (SuccessCode != 0 || FailureIndex != -1)
{
string ErrorMessage = string.Format("Installation of apk '{0}' failed", ApkName);
if (FailureIndex != -1)
{
string FailureString = InstallOutput.Substring(FailureIndex + 7).Trim();
if (FailureString != "")
{
ErrorMessage += ": " + FailureString;
}
}
if (ErrorMessage.Contains("OLDER_SDK"))
{
LogError("minSdkVersion is higher than Android version installed on device, possibly due to NDK API Level");
}
throw new AutomationException(ExitCode.Error_AppInstallFailed, ErrorMessage);
}
else
{
// giving EXTERNAL_STORAGE_WRITE permission to the apk for API23+
// without this permission apk can't access to the assets put into the device
string ReadPermissionCommandLine = "shell pm grant " + PackageName + " android.permission.READ_EXTERNAL_STORAGE";
string WritePermissionCommandLine = "shell pm grant " + PackageName + " android.permission.WRITE_EXTERNAL_STORAGE";
RunAndLogAdbCommand(Params, DeviceName, ReadPermissionCommandLine, out SuccessCode);
RunAndLogAdbCommand(Params, DeviceName, WritePermissionCommandLine, out SuccessCode);
}
}
// update the ue4commandline.txt
// update and deploy ue4commandline.txt
// always delete the existing commandline text file, so it doesn't reuse an old one
string IntermediateCmdLineFile = CombinePaths(SC.StageDirectory, "UE4CommandLine.txt");
Project.WriteStageCommandline(IntermediateCmdLineFile, Params, SC);
// copy files to device if we were staging
if (SC.Stage)
{
// cache some strings
string BaseCommandline = "push";
HashSet<string> EntriesToDeploy = new HashSet<string>();
if (Params.IterativeDeploy)
{
// always send UE4CommandLine.txt (it was written above after delta checks applied)
EntriesToDeploy.Add(IntermediateCmdLineFile);
// Add non UFS files if any to deploy
String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath(DeviceName);
if (File.Exists(NonUFSManifestPath))
{
string NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
foreach (string Filename in NonUFSFiles.Split('\n'))
{
if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename))
{
EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim()));
}
}
}
// Add UFS files if any to deploy
String UFSManifestPath = SC.GetUFSDeploymentDeltaPath(DeviceName);
if (File.Exists(UFSManifestPath))
{
string UFSFiles = File.ReadAllText(UFSManifestPath);
foreach (string Filename in UFSFiles.Split('\n'))
{
if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename))
{
EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim()));
}
}
}
// For now, if too many files may be better to just push them all
if (EntriesToDeploy.Count > 500)
{
// make sure device is at a clean state
RunAdbCommand(Params, DeviceName, "shell rm -r " + RemoteDir);
EntriesToDeploy.Clear();
EntriesToDeploy.TrimExcess();
EntriesToDeploy.Add(SC.StageDirectory);
}
}
else
{
// make sure device is at a clean state
RunAdbCommand(Params, DeviceName, "shell rm -r " + RemoteDir);
// Copy UFS files..
string[] Files = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories);
System.Array.Sort(Files);
// Find all the files we exclude from copying. And include
// the directories we need to individually copy.
HashSet<string> ExcludedFiles = new HashSet<string>();
SortedSet<string> IndividualCopyDirectories
= new SortedSet<string>((IComparer<string>)new LongestFirst());
foreach (string Filename in Files)
{
bool Exclude = false;
// Don't push the apk, we install it
Exclude |= Path.GetExtension(Filename).Equals(".apk", StringComparison.InvariantCultureIgnoreCase);
// For excluded files we add the parent dirs to our
// tracking of stuff to individually copy.
if (Exclude)
{
ExcludedFiles.Add(Filename);
// We include all directories up to the stage root in having
// to individually copy the files.
for (string FileDirectory = Path.GetDirectoryName(Filename);
!FileDirectory.Equals(SC.StageDirectory);
FileDirectory = Path.GetDirectoryName(FileDirectory))
{
if (!IndividualCopyDirectories.Contains(FileDirectory))
{
IndividualCopyDirectories.Add(FileDirectory);
}
}
if (!IndividualCopyDirectories.Contains(SC.StageDirectory))
{
IndividualCopyDirectories.Add(SC.StageDirectory);
}
}
}
// The directories are sorted above in "deepest" first. We can
// therefore start copying those individual dirs which will
// recreate the tree. As the subtrees will get copied at each
// possible individual level.
foreach (string DirectoryName in IndividualCopyDirectories)
{
string[] Entries
= Directory.GetFileSystemEntries(DirectoryName, "*", SearchOption.TopDirectoryOnly);
foreach (string Entry in Entries)
{
// We avoid excluded files and the individual copy dirs
// (the individual copy dirs will get handled as we iterate).
if (ExcludedFiles.Contains(Entry) || IndividualCopyDirectories.Contains(Entry))
{
continue;
}
else
{
EntriesToDeploy.Add(Entry);
}
}
}
if (EntriesToDeploy.Count == 0)
{
EntriesToDeploy.Add(SC.StageDirectory);
}
}
// We now have a minimal set of file & dir entries we need
// to deploy. Files we deploy will get individually copied
// and dirs will get the tree copies by default (that's
// what ADB does).
HashSet<IProcessResult> DeployCommands = new HashSet<IProcessResult>();
foreach (string Entry in EntriesToDeploy)
{
string FinalRemoteDir = RemoteDir;
string RemotePath = Entry.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, Entry, RemotePath);
// We run deploy commands in parallel to maximize the connection
// throughput.
DeployCommands.Add(
RunAdbCommand(Params, DeviceName, Commandline, null,
ERunOptions.Default | ERunOptions.NoWaitForExit));
// But we limit the parallel commands to avoid overwhelming
// memory resources.
if (DeployCommands.Count == DeployMaxParallelCommands)
{
while (DeployCommands.Count > DeployMaxParallelCommands / 2)
{
Thread.Sleep(1);
DeployCommands.RemoveWhere(
delegate (IProcessResult r)
{
return r.HasExited;
});
}
}
}
foreach (IProcessResult deploy_result in DeployCommands)
{
deploy_result.WaitForExit();
}
// delete the .obb file, since it will cause nothing we just deployed to be used
RunAdbCommand(Params, DeviceName, "shell rm " + DeviceObbName);
}
else if (SC.Archive)
{
// deploy the obb if there is one
string ObbPath = Path.Combine(SC.StageDirectory, GetFinalObbName(ApkName));
if (File.Exists(ObbPath))
{
// cache some strings
string BaseCommandline = "push";
string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, ObbPath, DeviceObbName);
RunAdbCommand(Params, DeviceName, Commandline);
}
}
else
{
// cache some strings
string BaseCommandline = "push";
string FinalRemoteDir = RemoteDir;
/*
// handle the special case of the UE4Commandline.txt when using content only game (UE4Game)
if (!Params.IsCodeBasedProject)
{
FinalRemoteDir = "/mnt/sdcard/UE4Game";
}
*/
string RemoteFilename = IntermediateCmdLineFile.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, IntermediateCmdLineFile, RemoteFilename);
RunAdbCommand(Params, DeviceName, Commandline);
}
// write new timestamp for APK (do it here since RemoteDir will now exist)
if (bNeedAPKInstall)
{
int SuccessCode = 0;
RunAndLogAdbCommand(Params, DeviceName, "shell \"echo 'APK: " + APKLastUpdateTime + "' > " + RemoteDir + "/APKFileStamp.txt\"", out SuccessCode);
}
}
}
/** Internal usage for GetPackageName */
private static string PackageLine = null;
private static Mutex PackageInfoMutex = new Mutex();
private static string LaunchableActivityLine = null;
/** Run an external exe (and capture the output), given the exe path and the commandline. */
private static string GetPackageInfo(string ApkName, bool bRetrieveVersionCode)
{
// we expect there to be one, so use the first one
string AaptPath = GetAaptPath();
PackageInfoMutex.WaitOne();
var ExeInfo = new ProcessStartInfo(AaptPath, "dump badging \"" + ApkName + "\"");
ExeInfo.UseShellExecute = false;
ExeInfo.RedirectStandardOutput = true;
using (var GameProcess = Process.Start(ExeInfo))
{
PackageLine = null;
LaunchableActivityLine = null;
GameProcess.BeginOutputReadLine();
GameProcess.OutputDataReceived += ParsePackageName;
GameProcess.WaitForExit();
}
PackageInfoMutex.ReleaseMutex();
string ReturnValue = null;
if (PackageLine != null)
{
// the line should look like: package: name='com.epicgames.qagame' versionCode='1' versionName='1.0'
string[] Tokens = PackageLine.Split("'".ToCharArray());
int TokenIndex = bRetrieveVersionCode ? 3 : 1;
if (Tokens.Length >= TokenIndex + 1)
{
ReturnValue = Tokens[TokenIndex];
}
}
return ReturnValue;
}
/** Returns the launch activity name to launch (must call GetPackageInfo first), returns "com.epicgames.ue4.SplashActivity" default if not found */
private static string GetLaunchableActivityName()
{
string ReturnValue = "com.epicgames.ue4.SplashActivity";
if (LaunchableActivityLine != null)
{
// the line should look like: launchable-activity: name='com.epicgames.ue4.SplashActivity' label='TappyChicken' icon=''
string[] Tokens = LaunchableActivityLine.Split("'".ToCharArray());
if (Tokens.Length >= 2)
{
ReturnValue = Tokens[1];
}
}
return ReturnValue;
}
/** Simple function to pipe output asynchronously */
private static void ParsePackageName(object Sender, DataReceivedEventArgs Event)
{
// DataReceivedEventHandler is fired with a null string when the output stream is closed. We don't want to
// print anything for that event.
if (!String.IsNullOrEmpty(Event.Data))
{
if (PackageLine == null)
{
string Line = Event.Data;
if (Line.StartsWith("package:"))
{
PackageLine = Line;
}
}
if (LaunchableActivityLine == null)
{
string Line = Event.Data;
if (Line.StartsWith("launchable-activity:"))
{
LaunchableActivityLine = Line;
}
}
}
}
static private string CachedAaptPath = null;
static private string LastAndroidHomePath = null;
private static uint GetRevisionValue(string VersionString)
{
// read up to 4 sections (ie. 20.0.3.5), first section most significant
// each section assumed to be 0 to 255 range
uint Value = 0;
try
{
string[] Sections= VersionString.Split(".".ToCharArray());
Value |= (Sections.Length > 0) ? (uint.Parse(Sections[0]) << 24) : 0;
Value |= (Sections.Length > 1) ? (uint.Parse(Sections[1]) << 16) : 0;
Value |= (Sections.Length > 2) ? (uint.Parse(Sections[2]) << 8) : 0;
Value |= (Sections.Length > 3) ? uint.Parse(Sections[3]) : 0;
}
catch (Exception)
{
// ignore poorly formed version
}
return Value;
}
private static string GetAaptPath()
{
// return cached path if ANDROID_HOME has not changed
string HomePath = Environment.ExpandEnvironmentVariables("%ANDROID_HOME%");
if (CachedAaptPath != null && LastAndroidHomePath == HomePath)
{
return CachedAaptPath;
}
// get a list of the directories in build-tools.. may be more than one set installed (or none which is bad)
string[] Subdirs = Directory.GetDirectories(Path.Combine(HomePath, "build-tools"));
if (Subdirs.Length == 0)
{
throw new AutomationException(ExitCode.Error_AndroidBuildToolsPathNotFound, "Failed to find %ANDROID_HOME%/build-tools subdirectory. Run SDK manager and install build-tools.");
}
// valid directories will have a source.properties with the Pkg.Revision (there is no guarantee we can use the directory name as revision)
string BestToolPath = null;
uint BestVersion = 0;
foreach (string CandidateDir in Subdirs)
{
string AaptFilename = Path.Combine(CandidateDir, Utils.IsRunningOnMono ? "aapt" : "aapt.exe");
uint RevisionValue = 0;
if (File.Exists(AaptFilename))
{
string SourcePropFilename = Path.Combine(CandidateDir, "source.properties");
if (File.Exists(SourcePropFilename))
{
string[] PropertyContents = File.ReadAllLines(SourcePropFilename);
foreach (string PropertyLine in PropertyContents)
{
if (PropertyLine.StartsWith("Pkg.Revision="))
{
RevisionValue = GetRevisionValue(PropertyLine.Substring(13));
break;
}
}
}
}
// remember it if newer version or haven't found one yet
if (RevisionValue > BestVersion || BestToolPath == null)
{
BestVersion = RevisionValue;
BestToolPath = AaptFilename;
}
}
if (BestToolPath == null)
{
throw new AutomationException(ExitCode.Error_AndroidBuildToolsPathNotFound, "Failed to find %ANDROID_HOME%/build-tools subdirectory with aapt. Run SDK manager and install build-tools.");
}
CachedAaptPath = BestToolPath;
LastAndroidHomePath = HomePath;
Log("Using this aapt: {0}", CachedAaptPath);
return CachedAaptPath;
}
private string GetBestDeviceArchitecture(ProjectParams Params, string DeviceName)
{
bool bMakeSeparateApks = UnrealBuildTool.AndroidExports.ShouldMakeSeparateApks();
// if we are joining all .so's into a single .apk, there's no need to find the best one - there is no other one
if (!bMakeSeparateApks)
{
return "";
}
var AppArchitectures = AndroidExports.CreateToolChain(Params.RawProjectPath).GetAllArchitectures();
// ask the device
IProcessResult ABIResult = RunAdbCommand(Params, DeviceName, " shell getprop ro.product.cpu.abi", null, ERunOptions.AppMustExist);
// the output is just the architecture
string DeviceArch = UnrealBuildTool.AndroidExports.GetUE4Arch(ABIResult.Output.Trim());
// if the architecture wasn't built, look for a backup
if (!AppArchitectures.Contains(DeviceArch))
{
// go from 64 to 32-bit
if (DeviceArch == "-arm64")
{
DeviceArch = "-armv7";
}
// go from 64 to 32-bit
else if (DeviceArch == "-x64")
{
if (!AppArchitectures.Contains("-x86"))
{
DeviceArch = "-x86";
}
// if it didn't have 32-bit x86, look for 64-bit arm for emulation
// @todo android 64-bit: x86_64 most likely can't emulate arm64 at this ponit
// else if (Array.IndexOf(AppArchitectures, "-arm64") == -1)
// {
// DeviceArch = "-arm64";
// }
// finally try for 32-bit arm emulation (Houdini)
else
{
DeviceArch = "-armv7";
}
}
// use armv7 (with Houdini emulation)
else if (DeviceArch == "-x86")
{
DeviceArch = "-armv7";
}
else
{
// future-proof by dropping back to armv7 for unknown
DeviceArch = "-armv7";
}
}
// if after the fallbacks, we still don't have it, we can't continue
if (!AppArchitectures.Contains(DeviceArch))
{
throw new AutomationException(ExitCode.Error_NoApkSuitableForArchitecture, "Unable to run because you don't have an apk that is usable on {0}. Looked for {1}", DeviceName, DeviceArch);
}
return DeviceArch;
}
private string GetBestGPUArchitecture(ProjectParams Params, string DeviceName)
{
bool bMakeSeparateApks = UnrealBuildTool.AndroidExports.ShouldMakeSeparateApks();
// if we are joining all .so's into a single .apk, there's no need to find the best one - there is no other one
if (!bMakeSeparateApks)
{
return "";
}
var AppGPUArchitectures = AndroidExports.CreateToolChain(Params.RawProjectPath).GetAllGPUArchitectures();
// get the device extensions
IProcessResult ExtensionsResult = RunAdbCommand(Params, DeviceName, "shell dumpsys SurfaceFlinger", null, ERunOptions.AppMustExist);
string Extensions = ExtensionsResult.Output.Trim();
// look for AEP support (on device and in project)
if (Extensions.Contains("GL_ANDROID_extension_pack_es31a") && Extensions.Contains("GL_EXT_color_buffer_half_float"))
{
if (AppGPUArchitectures.Contains("-esdeferred"))
{
return "-esdeferred";
}
}
return "-es2";
}
public override IProcessResult RunClient(ERunOptions ClientRunFlags, string ClientApp, string ClientCmdLine, ProjectParams Params)
{
//make a copy of the device names, we'll be working through them
List<string> DeviceNames = new List<string>();
//same with the package names
List<string> PackageNames = new List<string>();
foreach (string DeviceName in Params.DeviceNames)
{
//save the device name
DeviceNames.Add(DeviceName);
//get the package name and save that
string DeviceArchitecture = GetBestDeviceArchitecture(Params, DeviceName);
string GPUArchitecture = GetBestGPUArchitecture(Params, DeviceName);
string ApkName = ClientApp + DeviceArchitecture + ".apk";
if (!File.Exists(ApkName))
{
ApkName = GetFinalApkName(Params, Path.GetFileNameWithoutExtension(ClientApp), true, DeviceArchitecture, GPUArchitecture);
}
Console.WriteLine("Apk='{0}', ClientApp='{1}', ExeName='{2}'", ApkName, ClientApp, Params.ProjectGameExeFilename);
// run aapt to get the name of the intent
string PackageName = GetPackageInfo(ApkName, false);
if (PackageName == null)
{
throw new AutomationException(ExitCode.Error_FailureGettingPackageInfo, "Failed to get package name from " + ClientApp);
}
PackageNames.Add(PackageName);
// clear the log for the device
RunAdbCommand(Params, DeviceName, "logcat -c");
// start the app on device!
string CommandLine = "shell am start -n " + PackageName + "/" + GetLaunchableActivityName();
RunAdbCommand(Params, DeviceName, CommandLine, null, ClientRunFlags);
// save the output to the staging directory
string LogPath = Path.Combine(Params.BaseStageDirectory, "Android\\logs");
Directory.CreateDirectory(LogPath);
}
//now check if each device still has the game running, and time out if it's taking too long
DateTime StartTime = DateTime.Now;
int TimeOutSeconds = Params.RunTimeoutSeconds;
while (DeviceNames.Count > 0)
{
for(int DeviceIndex = 0; DeviceIndex < DeviceNames.Count; DeviceIndex++)
{
string DeviceName = DeviceNames[DeviceIndex];
//replace the port name in the case of deploy while adb is using wifi
string SanitizedDeviceName = DeviceName.Replace(":", "_");
bool FinishedRunning = false;
IProcessResult ProcessesResult = RunAdbCommand(Params, DeviceName, "shell ps", null, ERunOptions.SpewIsVerbose);
string RunningProcessList = ProcessesResult.Output;
if (!RunningProcessList.Contains(PackageNames[DeviceIndex]))
{
FinishedRunning = true;
}
Thread.Sleep(10);
if(!FinishedRunning)
{
TimeSpan DeltaRunTime = DateTime.Now - StartTime;
if ((DeltaRunTime.TotalSeconds > TimeOutSeconds) && (TimeOutSeconds != 0))
{
Log("Device: " + DeviceName + " timed out while waiting for run to finish");
FinishedRunning = true;
}
}
//log the results, then clear out the device from our list
if(FinishedRunning)
{
// this is just to get the ue4 log to go to the output
RunAdbCommand(Params, DeviceName, "logcat -d -s UE4 -s Debug");
// get the log we actually want to save
IProcessResult LogFileProcess = RunAdbCommand(Params, DeviceName, "logcat -d", null, ERunOptions.AppMustExist);
string LogPath = Path.Combine(Params.BaseStageDirectory, "Android\\logs");
string LogFilename = Path.Combine(LogPath, "devicelog" + SanitizedDeviceName + ".log");
string ServerLogFilename = Path.Combine(CmdEnv.LogFolder, "devicelog" + SanitizedDeviceName + ".log");
File.WriteAllText(LogFilename, LogFileProcess.Output);
File.WriteAllText(ServerLogFilename, LogFileProcess.Output);
DeviceNames.RemoveAt(DeviceIndex);
PackageNames.RemoveAt(DeviceIndex);
--DeviceIndex;
}
}
}
return null;
}
public override void GetFilesToDeployOrStage(ProjectParams Params, DeploymentContext SC)
{
// Add any Android shader cache files
string ProjectShaderDir = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(Params.RawProjectPath.ToString())), "Build/ShaderCaches/Android");
SC.StageFiles(StagedFileType.UFS, ProjectShaderDir, "*.*", true, null, null, true);
}
/// <summary>
/// Gets cook platform name for this platform.
/// </summary>
/// <returns>Cook platform string.</returns>
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android";
}
public override bool DeployPakInternalLowerCaseFilenames()
{
return false;
}
public override bool DeployLowerCaseFilenames(bool bUFSFile)
{
return false;
}
public override string LocalPathToTargetPath(string LocalPath, string LocalRoot)
{
return LocalPath.Replace("\\", "/").Replace(LocalRoot, "../../..");
}
public override bool IsSupported { get { return true; } }
public override string Remap(string Dest)
{
return Dest;
}
public override PakType RequiresPak(ProjectParams Params)
{
// if packaging is enabled, always create a pak, otherwise use the Params.Pak value
return Params.Package ? PakType.Always : PakType.DontCare;
}
public override bool SupportsMultiDeviceDeploy
{
get
{
return true;
}
}
/*
public override bool RequiresPackageToDeploy
{
get { return true; }
}
*/
public override List<string> GetDebugFileExtentions()
{
return new List<string> { };
}
public override void StripSymbols(string SourceFileName, string TargetFileName)
{
AndroidExports.StripSymbols(SourceFileName, TargetFileName);
}
}
public class AndroidPlatformMulti : AndroidPlatform
{
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android_Multi";
}
public override TargetPlatformDescriptor GetTargetPlatformDescriptor()
{
return new TargetPlatformDescriptor(TargetPlatformType, "Multi");
}
}
public class AndroidPlatformATC : AndroidPlatform
{
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android_ATC";
}
public override TargetPlatformDescriptor GetTargetPlatformDescriptor()
{
return new TargetPlatformDescriptor(TargetPlatformType, "ATC");
}
}
public class AndroidPlatformDXT : AndroidPlatform
{
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android_DXT";
}
public override TargetPlatformDescriptor GetTargetPlatformDescriptor()
{
return new TargetPlatformDescriptor(TargetPlatformType, "DXT");
}
}
public class AndroidPlatformETC1 : AndroidPlatform
{
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android_ETC1";
}
public override TargetPlatformDescriptor GetTargetPlatformDescriptor()
{
return new TargetPlatformDescriptor(TargetPlatformType, "ETC1");
}
}
public class AndroidPlatformETC2 : AndroidPlatform
{
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android_ETC2";
}
public override TargetPlatformDescriptor GetTargetPlatformDescriptor()
{
return new TargetPlatformDescriptor(TargetPlatformType, "ETC2");
}
}
public class AndroidPlatformPVRTC : AndroidPlatform
{
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android_PVRTC";
}
public override TargetPlatformDescriptor GetTargetPlatformDescriptor()
{
return new TargetPlatformDescriptor(TargetPlatformType, "PVRTC");
}
}
public class AndroidPlatformASTC : AndroidPlatform
{
public override string GetCookPlatform(bool bDedicatedServer, bool bIsClientOnly)
{
return "Android_ASTC";
}
public override TargetPlatformDescriptor GetTargetPlatformDescriptor()
{
return new TargetPlatformDescriptor(TargetPlatformType, "ASTC");
}
}