// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Google.Play.Common;
using Google.Play.Core.Internal;
using UnityEngine;
namespace Google.Play.AssetDelivery.Internal
{
///
/// Internal implementation of methods.
///
internal class PlayAssetDeliveryInternal
{
///
/// The subfolder, inside the "assets" directory, into which an asset pack file is stored.
/// This intermediate folder can be useful for targeting (by texture compression format) at build time.
///
private const string AssetPackFolderName = "assetpack";
private readonly AssetPackManager _assetPackManager;
private readonly AssetDeliveryUpdateHandler _updateHandler;
private readonly PlayRequestRepository _requestRepository = new PlayRequestRepository();
internal PlayAssetDeliveryInternal()
{
_assetPackManager = new AssetPackManager();
_updateHandler = AssetDeliveryUpdateHandler.CreateInScene(_assetPackManager, _requestRepository);
_updateHandler.OnStateUpdateEvent += ProcessPackStateUpdate;
PlayCoreEventHandler.CreateInScene();
}
internal bool IsDownloaded(string assetBundleName)
{
return _assetPackManager.GetPackLocation(assetBundleName) != null;
}
internal PlayAssetBundleRequest RetrieveAssetBundleAsyncInternal(string assetBundleName)
{
var request = InitializeAssetBundleRequest(assetBundleName);
InitiateRequest(request.PackRequest);
return request;
}
internal PlayAssetBundleRequest RetrieveAssetBundleAsyncInternal(string assetBundleName, bool updateIfAvailable)
{
var request = InitializeAssetBundleRequest(assetBundleName);
InitiateRequest(request.PackRequest, updateIfAvailable);
return request;
}
private PlayAssetBundleRequestImpl InitializeAssetBundleRequest(string assetBundleName)
{
if (_requestRepository.ContainsRequest(assetBundleName))
{
throw new ArgumentException(string.Format("There is already an active request for AssetBundle: {0}",
assetBundleName));
}
var request = CreateAssetBundleRequest(assetBundleName);
_requestRepository.AddAssetBundleRequest(request);
request.Completed += req => _requestRepository.RemoveRequest(assetBundleName);
return request;
}
internal PlayAssetPackRequest RetrieveAssetPackAsyncInternal(string assetPackName)
{
var request = GetExistingAssetPackRequest(assetPackName);
if (request != null)
{
return request;
}
request = InitializeAssetPackRequest(assetPackName);
InitiateRequest(request);
return request;
}
internal PlayAssetPackRequest RetrieveAssetPackAsyncInternal(string assetPackName, bool updateIfAvailable)
{
var request = GetExistingAssetPackRequest(assetPackName);
if (request != null)
{
return request;
}
request = InitializeAssetPackRequest(assetPackName);
InitiateRequest(request, updateIfAvailable);
return request;
}
internal PlayAssetPackBatchRequest RetrieveAssetPackBatchAsyncInternal(IList assetPackNames)
{
return CreateAndInitiateBatchRequest(assetPackNames, IsDownloaded);
}
internal PlayAssetPackBatchRequest RetrieveAssetPackBatchAsyncInternal(IList assetPackNames,
bool updateIfAvailable)
{
Func useAvailablePack = packName => IsCurrentVersionGoodEnough(packName, updateIfAvailable);
return CreateAndInitiateBatchRequest(assetPackNames, useAvailablePack);
}
private PlayAssetPackBatchRequest CreateAndInitiateBatchRequest(IList assetPackNames,
Func isPackAvailable)
{
if (assetPackNames.Count != assetPackNames.Distinct().Count())
{
throw new ArgumentException("assetPackNames contains duplicate entries");
}
var activeAssetBundleRequestNames = assetPackNames
.Where(name => _requestRepository.ContainsAssetBundleRequest(name))
.ToArray();
if (activeAssetBundleRequestNames.Length != 0)
{
throw new ArgumentException("Cannot create a new PlayAssetPackBatchRequests because there are " +
"already PlayAssetBundleRequests for AssetBundles: {0}",
string.Join(", ", activeAssetBundleRequestNames));
}
// A subset of assetPackNames of packs that are not available on disk.
var unavailableAssetPackNames = new List();
var requests = new List();
foreach (var assetPackName in assetPackNames)
{
PlayAssetPackRequestImpl request;
if (_requestRepository.TryGetRequest(assetPackName, out request))
{
requests.Add(request);
continue;
}
request = CreateAssetPackRequest(assetPackName);
_requestRepository.AddRequest(request);
request.Completed += req => _requestRepository.RemoveRequest(request.AssetPackName);
requests.Add(request);
if (isPackAvailable.Invoke(assetPackName))
{
request.OnPackAvailable();
}
else
{
unavailableAssetPackNames.Add(assetPackName);
}
}
var batchRequest = new PlayAssetPackBatchRequestImpl(requests);
var fetchTask = _assetPackManager.Fetch(unavailableAssetPackNames.ToArray());
fetchTask.RegisterOnSuccessCallback(javaPackStates =>
{
batchRequest.OnInitializedInPlayCore();
fetchTask.Dispose();
});
fetchTask.RegisterOnFailureCallback((reason, errorCode) =>
{
Debug.LogError("Failed to retrieve asset pack batch: " + reason);
batchRequest.OnInitializationErrorOccurred(PlayCoreTranslator.TranslatePlayCoreErrorCode(errorCode));
fetchTask.Dispose();
});
return batchRequest;
}
internal PlayAsyncOperation
ShowCellularDataConfirmationInternal()
{
var requests = _requestRepository.GetRequestsWithStatus(AssetDeliveryStatus.WaitingForWifi);
if (requests.Count == 0)
{
throw new Exception("There are no active requests waiting for wifi.");
}
var task = _assetPackManager.ShowCellularDataConfirmation();
var operation = new AssetDeliveryAsyncOperation();
task.RegisterOnSuccessCallback(resultCode =>
{
operation.SetResult(ConvertToConfirmationDialogResult(resultCode));
task.Dispose();
});
task.RegisterOnFailureCallback((message, errorCode) =>
{
operation.SetError(PlayCoreTranslator.TranslatePlayCoreErrorCode(errorCode));
task.Dispose();
});
return operation;
}
internal PlayAsyncOperation
ShowConfirmationDialogInternal()
{
var requestsAwaitingWifi = _requestRepository.GetRequestsWithStatus(AssetDeliveryStatus.WaitingForWifi);
var requestsAwaitingConsent = _requestRepository.GetRequestsWithStatus(AssetDeliveryStatus.RequiresUserConfirmation);
if (requestsAwaitingWifi.Count == 0 && requestsAwaitingConsent.Count == 0)
{
throw new Exception("There are no active requests waiting for confirmation.");
}
var task = _assetPackManager.ShowConfirmationDialog();
var operation = new AssetDeliveryAsyncOperation();
task.RegisterOnSuccessCallback(resultCode =>
{
operation.SetResult(ConvertToConfirmationDialogResult(resultCode));
task.Dispose();
});
task.RegisterOnFailureCallback((message, errorCode) =>
{
operation.SetError(PlayCoreTranslator.TranslatePlayCoreErrorCode(errorCode));
task.Dispose();
});
return operation;
}
internal PlayAsyncOperation GetDownloadSizeInternal(string assetBundleName)
{
var operation = new AssetDeliveryAsyncOperation();
if (IsInstallTimeAssetBundle(assetBundleName))
{
operation.SetResult(0L);
return operation;
}
var task = _assetPackManager.GetPackStates(assetBundleName);
task.RegisterOnSuccessCallback(javaPackState =>
{
var assetPacks = new AssetPackStates(javaPackState);
operation.SetResult(assetPacks.TotalBytes);
task.Dispose();
});
task.RegisterOnFailureCallback((message, errorCode) =>
{
operation.SetError(PlayCoreTranslator.TranslatePlayCoreErrorCode(errorCode));
task.Dispose();
});
return operation;
}
internal PlayAsyncOperation, AssetDeliveryErrorCode>
GetDownloadInfoInternal(IList assetPackNames)
{
var operation = new AssetDeliveryAsyncOperation>();
var task = _assetPackManager.GetPackStates(assetPackNames.ToArray());
task.RegisterOnSuccessCallback(javaPackStates =>
{
operation.SetResult(PlayAssetPackDownloadInfoImpl.FromAssetPackStates(javaPackStates));
task.Dispose();
});
task.RegisterOnFailureCallback((message, errorCode) =>
{
operation.SetError(PlayCoreTranslator.TranslatePlayCoreErrorCode(errorCode));
task.Dispose();
});
return operation;
}
internal PlayAsyncOperation RemoveAssetPackInternal(
string assetBundleName)
{
var operation = new AssetDeliveryAsyncOperation();
var task = _assetPackManager.RemovePack(assetBundleName);
task.RegisterOnSuccessCallback(javaPackState =>
{
operation.SetResult(new VoidResult());
task.Dispose();
});
task.RegisterOnFailureCallback((message, errorCode) =>
{
operation.SetError(AssetDeliveryErrorCode.InternalError);
task.Dispose();
});
return operation;
}
private AssetLocation GetAssetLocation(string assetBundleName)
{
var assetPath = Path.Combine(AssetPackFolderName, assetBundleName);
return _assetPackManager.GetAssetLocation(assetBundleName, assetPath);
}
private PlayAssetPackRequestImpl GetExistingAssetPackRequest(string assetPackName)
{
if (_requestRepository.ContainsAssetBundleRequest(assetPackName))
{
throw new ArgumentException(string.Format(
"Cannot create a new PlayAssetPackRequest because there is already an active " +
"PlayAssetBundleRequest for asset bundle: {0}",
assetPackName));
}
PlayAssetPackRequestImpl request;
if (_requestRepository.TryGetRequest(assetPackName, out request))
{
Debug.Log("Returning existing active request for " + assetPackName);
}
return request;
}
private PlayAssetPackRequestImpl InitializeAssetPackRequest(string assetPackName)
{
var request = CreateAssetPackRequest(assetPackName);
_requestRepository.AddRequest(request);
request.Completed += req => _requestRepository.RemoveRequest(assetPackName);
return request;
}
private PlayAssetPackRequestImpl CreateAssetPackRequest(string assetPackName)
{
return new PlayAssetPackRequestImpl(assetPackName, _assetPackManager, _requestRepository);
}
private PlayAssetBundleRequestImpl CreateAssetBundleRequest(string assetBundleName)
{
var packRequest = CreateAssetPackRequest(assetBundleName);
return new PlayAssetBundleRequestImpl(packRequest, _updateHandler);
}
private void InitiateRequest(PlayAssetPackRequestImpl request)
{
StartRequest(request, IsDownloaded(request.AssetPackName));
}
private void InitiateRequest(PlayAssetPackRequestImpl request, bool updateIfAvailable)
{
StartRequest(request, IsCurrentVersionGoodEnough(request.AssetPackName, updateIfAvailable));
}
private bool IsCurrentVersionGoodEnough(string assetPackName, bool wantNewerVersion)
{
var packLocation = _assetPackManager.GetPackLocation(assetPackName);
bool isCurrentVersionGoodEnough;
if (packLocation == null)
{
isCurrentVersionGoodEnough= false;
}
else if (packLocation.PackStorageMethod == AssetPackStorageMethod.ApkAssets)
{
// If the pack exists as an APK, then it is an install-time pack which cannot be updated.
isCurrentVersionGoodEnough = true;
}
else
{
// When wantNewerVersion is true,
// we can't use the version on disk without checking if a newer version exists.
isCurrentVersionGoodEnough = !wantNewerVersion;
}
return isCurrentVersionGoodEnough;
}
private void StartRequest(PlayAssetPackRequestImpl request, bool isAvailable)
{
if (isAvailable)
{
request.OnPackAvailable();
}
else
{
var fetchTask = _assetPackManager.Fetch(request.AssetPackName);
fetchTask.RegisterOnSuccessCallback(javaPackStates =>
{
request.OnInitializedInPlayCore();
fetchTask.Dispose();
});
fetchTask.RegisterOnFailureCallback((reason, errorCode) =>
{
Debug.LogErrorFormat("Failed to retrieve asset pack: {0}", reason);
request.OnErrorOccured(PlayCoreTranslator.TranslatePlayCoreErrorCode(errorCode));
fetchTask.Dispose();
});
}
}
private void ProcessPackStateUpdate(AssetPackState newState)
{
PlayAssetPackRequestImpl request;
if (!_requestRepository.TryGetRequest(newState.Name, out request))
{
Debug.LogWarningFormat(
"Received state update \"{0}\", that is not associated with an active request.",
newState.Name);
return;
}
UpdateRequest(request, newState, newState.ErrorCode);
}
private void UpdateRequest(PlayAssetPackRequestImpl request, AssetPackState newState, int errorCode)
{
if (request.IsDone)
{
// Ignore pack state updates associated with completed requests.
return;
}
var assetDeliveryErrorCode = PlayCoreTranslator.TranslatePlayCoreErrorCode(errorCode);
if (assetDeliveryErrorCode != AssetDeliveryErrorCode.NoError)
{
request.OnErrorOccured(assetDeliveryErrorCode);
return;
}
if (newState.Status == PlayCoreTranslator.AssetPackStatus.Canceled)
{
request.OnErrorOccured(AssetDeliveryErrorCode.Canceled);
return;
}
request.UpdateState(PlayCoreTranslator.TranslatePlayCorePackStatus(newState.Status),
newState.BytesDownloaded,
newState.TotalBytesToDownload);
}
private bool IsInstallTimeAssetBundle(string assetBundleName)
{
var packLocation = _assetPackManager.GetPackLocation(assetBundleName);
return packLocation != null && packLocation.PackStorageMethod == AssetPackStorageMethod.ApkAssets;
}
private ConfirmationDialogResult ConvertToConfirmationDialogResult(int resultCode)
{
ConfirmationDialogResult dialogResult;
switch (resultCode)
{
case ActivityResult.ResultOk:
dialogResult = ConfirmationDialogResult.Accepted;
break;
case ActivityResult.ResultCancelled:
dialogResult = ConfirmationDialogResult.Denied;
break;
default:
throw new NotImplementedException("Unexpected activity result: " + resultCode);
}
return dialogResult;
}
}
}