├── Tests
├── .tests.json
├── Runtime.meta
└── Runtime
│ ├── Unity.Networking.BackgroundDownload.Tests.asmdef.meta
│ ├── DownloadTests.cs.meta
│ ├── Unity.Networking.BackgroundDownload.Tests.asmdef
│ └── DownloadTests.cs
├── CHANGELOG.md.meta
├── LICENSE.md.meta
├── README.md.meta
├── CONTRIBUTING.md.meta
├── package.json.meta
├── Samples
├── SingleFile
│ ├── .sample.json
│ ├── SingleFileDownload.cs.meta
│ └── SingleFileDownload.cs
└── SingleFile.meta
├── Runtime.meta
├── Samples.meta
├── Tests.meta
├── Runtime
├── Plugins.meta
├── Plugins
│ ├── iOS.meta
│ ├── Android.meta
│ ├── Android
│ │ ├── backgrounddownload.androidlib
│ │ │ ├── build.gradle.meta
│ │ │ ├── src.meta
│ │ │ ├── src
│ │ │ │ ├── main.meta
│ │ │ │ └── main
│ │ │ │ │ ├── AndroidManifest.xml.meta
│ │ │ │ │ ├── java.meta
│ │ │ │ │ ├── java
│ │ │ │ │ ├── com.meta
│ │ │ │ │ └── com
│ │ │ │ │ │ ├── unity3d.meta
│ │ │ │ │ │ └── unity3d
│ │ │ │ │ │ ├── backgrounddownload.meta
│ │ │ │ │ │ └── backgrounddownload
│ │ │ │ │ │ ├── BackgroundDownload.java.meta
│ │ │ │ │ │ ├── CompletionReceiver.java.meta
│ │ │ │ │ │ ├── CompletionReceiver.java
│ │ │ │ │ │ └── BackgroundDownload.java
│ │ │ │ │ └── AndroidManifest.xml
│ │ │ └── build.gradle
│ │ └── backgrounddownload.androidlib.meta
│ └── iOS
│ │ ├── BackgroundDownload.mm.meta
│ │ └── BackgroundDownload.mm
├── Unity.Networking.BackgroundDownload.asmdef.meta
├── Unity.Networking.BackgroundDownload.asmdef
├── BackgroundDownload.cs.meta
├── BackgroundDownloadUWP.cs.meta
├── BackgroundDownloadiOS.cs.meta
├── BackgroundDownloadAndroid.cs.meta
├── BackgroundDownloadEditor.cs.meta
├── BackgroundDownloadEditor.cs
├── BackgroundDownloadiOS.cs
├── BackgroundDownloadUWP.cs
├── BackgroundDownloadAndroid.cs
└── BackgroundDownload.cs
├── Documentation~
├── com.unity.backgrounddownload.md
└── filter.yml
├── .yamato
├── package-pack.yml
├── package-publish.yml
├── package-promotion.yml
└── package-test.yml
├── CHANGELOG.md
├── LICENSE.md
├── package.json
├── CONTRIBUTING.md
└── README.md
/Tests/.tests.json:
--------------------------------------------------------------------------------
1 | {
2 | "createSeparatePackage": false
3 | }
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bffeaa8dd22eb4b3db8bb337f61d2e3f
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LICENSE.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 16315247fb8a741538fc2a5a1dc6afee
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a5bf19b8981454248a65bbb395075417
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 474fd87ae492e4cc0a2eefaf04a9ab9a
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b2bd37d093fc4426391629afd8eaa504
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Samples/SingleFile/.sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "displayName":"Single File Sample",
3 | "description": "An example C# script to download a single file in the background.",
4 | "createSeparatePackage": false
5 | }
6 |
--------------------------------------------------------------------------------
/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b5ffa6a5aa83b4bff8dc95c71556c86c
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Samples.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 70c84e7ab60ff47e58e7422d12a47c63
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Tests.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f4eb59724133e4d47bb09fa268627319
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Tests/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7ed6c940dc9764f1c82d2987064a77b2
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 70121a388660446e1b518de600cdf0d0
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins/iOS.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b0af6d7228ae64f96af84e7aee660e2f
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Samples/SingleFile.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 588a81229351e4a3782a2ea1b38ab503
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 83ebdd657218f468a913722d1be42976
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Unity.Networking.BackgroundDownload.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a4b21e772636f4f018b7cf036d722999
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Documentation~/com.unity.backgrounddownload.md:
--------------------------------------------------------------------------------
1 | # About com.unity.backgrounddownload
2 |
3 | ## Note: Unsupported feature
4 | This package is not an officially supported feature, and is provided "as is".
5 |
6 | See [README](../README.md) file for details.
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/build.gradle.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2059b70d9c2dd4bb79c6b049a0ebe522
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Tests/Runtime/Unity.Networking.BackgroundDownload.Tests.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 40d4bd5ce30f44163bb80e96d6dbfde9
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 274af03a545d64794a5fa4eba40fd5cd
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d59eb5ba02e5a43e8b7c9eb853610bd2
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/AndroidManifest.xml.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fd0406ae10c304b59aa56ff282e89afe
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 28
5 |
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 28
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ebbc73a686d234ebf97a11512863542b
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java/com.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c9d2c3b421c594d89a12670e2341b78b
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java/com/unity3d.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 333812364da394c3ead155796eb9d3f6
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Unity.Networking.BackgroundDownload.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unity.Networking.BackgroundDownload",
3 | "references": [],
4 | "includePlatforms": [
5 | "Android",
6 | "Editor",
7 | "iOS",
8 | "WSA"
9 | ],
10 | "excludePlatforms": []
11 | }
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java/com/unity3d/backgrounddownload.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 13b6c748248b741ddaec74d00ddb8b48
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java/com/unity3d/backgrounddownload/BackgroundDownload.java.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e232d9d3e6f904ca5a092337d65a23ae
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java/com/unity3d/backgrounddownload/CompletionReceiver.java.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9a101e404163f49898cbdb0f847199bc
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownload.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5aff7675a751d4f1887bb7c4befd6239
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadUWP.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 71b890c95e3ee4873b9ced6243f2b384
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadiOS.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: aa6b2bf971dea4b1bb7cc8618ea1fce6
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Tests/Runtime/DownloadTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ff0793e1b3e294a4a8ca325706b9b94a
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadAndroid.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7fbff8a2bc69c45988108f3ba0a7c856
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 77c31aeec75d045c09a03d51ae206cd4
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Samples/SingleFile/SingleFileDownload.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 82808b173e1c340c7990116993df40a5
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Tests/Runtime/Unity.Networking.BackgroundDownload.Tests.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Unity.Networking.BackgroundDownload.Tests",
3 | "references": [
4 | "Unity.Networking.BackgroundDownload"
5 | ],
6 | "optionalUnityReferences": [
7 | "TestAssemblies"
8 | ],
9 | "includePlatforms": [],
10 | "excludePlatforms": []
11 | }
12 |
--------------------------------------------------------------------------------
/.yamato/package-pack.yml:
--------------------------------------------------------------------------------
1 | pack:
2 | name: Pack
3 | agent:
4 | type: Unity::VM
5 | image: package-ci/ubuntu:stable
6 | flavor: b1.large
7 | commands:
8 | - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm
9 | - upm-ci package pack
10 | artifacts:
11 | packages:
12 | paths:
13 | - "upm-ci~/packages/**/*"
14 |
--------------------------------------------------------------------------------
/Documentation~/filter.yml:
--------------------------------------------------------------------------------
1 | apiRules:
2 | - exclude:
3 | # inherited Object methods
4 | uidRegex: ^System\.Object\..*$
5 | type: Method
6 | - exclude:
7 | # mentioning types from System.* namespace
8 | uidRegex: ^System\..*$
9 | type: Type
10 | - exclude:
11 | hasAttribute:
12 | uid: System.ObsoleteAttribute
13 | type: Member
14 | - exclude:
15 | hasAttribute:
16 | uid: System.ObsoleteAttribute
17 | type: Type
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this package will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 | ## [0.1.0] - 2019-11-20
8 |
9 | ### This is the first release of *Unity Package com.unity.networking.backgrounddownload*.
10 |
11 | *First release based on former plugin collection.*
12 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | com.unity.networking.backgrounddownload copyright © 2020 Unity Technologies ApS
2 |
3 | Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
4 |
5 | Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.unity.networking.backgrounddownload",
3 | "displayName":"Background Download",
4 | "version": "0.1.0-preview",
5 | "unity": "2020.1",
6 | "description": "Enable file downloads in background for mobile platforms\nAllows to launch file downloads that will continue even if the app goes into background or gets quit by the operating system. The downloads can be picked the next time the app is started.\nSupported platforms are: \n\u25AA Android \n\u25AA iOS \n\u25AA Universal Windows Platform",
7 | "dependencies": {
8 | "com.unity.modules.androidjni": "1.0.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## If you are interested in contributing, here are some ground rules:
4 | * Clone https://github.com/Unity-Technologies/BackgroundDownload
5 | * Ensure feature parity between all supported platforms
6 |
7 | ## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement)
8 | By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions.
9 |
10 | ## Once you have a change ready following these ground rules. Simply make a pull request
11 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Runtime/Plugins/iOS/BackgroundDownload.mm.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 89aaf15448fbe476f8ed6561cf302067
3 | PluginImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | iconMap: {}
7 | executionOrder: {}
8 | defineConstraints: []
9 | isPreloaded: 0
10 | isOverridable: 1
11 | isExplicitlyReferenced: 0
12 | validateReferences: 1
13 | platformData:
14 | - first:
15 | Any:
16 | second:
17 | enabled: 0
18 | settings: {}
19 | - first:
20 | Editor: Editor
21 | second:
22 | enabled: 0
23 | settings:
24 | DefaultValueInitialized: true
25 | - first:
26 | iPhone: iOS
27 | second:
28 | enabled: 1
29 | settings: {}
30 | - first:
31 | tvOS: tvOS
32 | second:
33 | enabled: 1
34 | settings: {}
35 | userData:
36 | assetBundleName:
37 | assetBundleVariant:
38 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadEditor.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_EDITOR
2 |
3 | using System.Collections.Generic;
4 |
5 | namespace Unity.Networking
6 | {
7 | class BackgroundDownloadEditor : BackgroundDownload
8 | {
9 | public BackgroundDownloadEditor(BackgroundDownloadConfig config)
10 | : base(config)
11 | {
12 | _status = BackgroundDownloadStatus.Failed;
13 | _error = "Not implemented for Unity Editor";
14 | }
15 |
16 | public override bool keepWaiting { get { return false; } }
17 |
18 | protected override float GetProgress() { return 1.0f; }
19 |
20 | internal static Dictionary LoadDownloads()
21 | {
22 | return new Dictionary();
23 | }
24 |
25 | internal static void SaveDownloads(Dictionary downloads)
26 | {
27 | }
28 | }
29 | }
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/.yamato/package-publish.yml:
--------------------------------------------------------------------------------
1 | test_editors:
2 | - version: 2020.1
3 | - version: trunk
4 | test_platforms:
5 | - name: win
6 | type: Unity::VM
7 | image: package-ci/win10:stable
8 | flavor: b1.large
9 | - name: mac
10 | type: Unity::VM::osx
11 | image: package-ci/mac:stable
12 | flavor: m1.mac
13 | ---
14 |
15 | publish:
16 | name: Publish to Internal Registry
17 | agent:
18 | type: Unity::VM
19 | image: package-ci/win10:stable
20 | flavor: b1.large
21 | commands:
22 | - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm
23 | - upm-ci package publish
24 | triggers:
25 | tags:
26 | only:
27 | - /^(r|R)(c|C)-\d+\.\d+\.\d+(-preview(\.\d+)?)?$/
28 | artifacts:
29 | artifacts:
30 | paths:
31 | - "upm-ci~/packages/*.tgz"
32 | dependencies:
33 | - .yamato/package-pack.yml#pack
34 | {% for editor in test_editors %}
35 | {% for platform in test_platforms %}
36 | - .yamato/package-test.yml#test_{{ platform.name }}_{{ editor.version }}
37 | - .yamato/package-test.yml#validate_{{ platform.name }}_{{ editor.version }}
38 | {% endfor %}
39 | {% endfor %}
40 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java/com/unity3d/backgrounddownload/CompletionReceiver.java:
--------------------------------------------------------------------------------
1 | package com.unity3d.backgrounddownload;
2 |
3 | import android.app.DownloadManager;
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 |
8 | public class CompletionReceiver extends BroadcastReceiver {
9 | public interface Callback {
10 | void downloadCompleted();
11 | }
12 |
13 | private static Callback callback;
14 |
15 | public static void setCallback(Callback cback) {
16 | callback = cback;
17 | }
18 |
19 | @Override
20 | public void onReceive(Context context, Intent intent) {
21 | if (callback == null)
22 | return;
23 | if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
24 | try {
25 | callback.downloadCompleted();
26 | } catch (UnsatisfiedLinkError e) {
27 | // if app quits, callback can be not-null, but is invalid (C# side destroyed)
28 | // this is fine: we will pick the status next time app is launched
29 | // this is to due to race-condition: C# object destroyed, Java not yet
30 | callback = null;
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Samples/SingleFile/SingleFileDownload.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.IO;
4 | using UnityEngine;
5 | using Unity.Networking;
6 |
7 | /*
8 | * Example for downloading a single file.
9 | */
10 | public class SingleFileDownload : MonoBehaviour
11 | {
12 | void Start()
13 | {
14 | string fileName = "success.jpg";
15 | string destinationFile = Path.Combine(Application.persistentDataPath, fileName);
16 |
17 | if (File.Exists(destinationFile))
18 | {
19 | Debug.Log("File already downloaded");
20 | return;
21 | }
22 |
23 | var downloads = BackgroundDownload.backgroundDownloads;
24 | if (downloads.Length > 0)
25 | StartCoroutine(WaitForDownload(downloads[0]));
26 | else
27 | {
28 | Uri url = new Uri("https://memegenerator.net/img/images/1154731/success-baby.jpg");
29 | StartCoroutine(WaitForDownload(BackgroundDownload.Start(url, fileName)));
30 | }
31 | }
32 |
33 | IEnumerator WaitForDownload(BackgroundDownload download)
34 | {
35 | yield return download;
36 | if (download.status == BackgroundDownloadStatus.Done)
37 | Debug.Log("File successfully downloaded");
38 | else
39 | Debug.Log("File download failed with error: " + download.error);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c467ff3e197564d2eae2496c52ade6c1
3 | folderAsset: yes
4 | PluginImporter:
5 | externalObjects: {}
6 | serializedVersion: 2
7 | iconMap: {}
8 | executionOrder: {}
9 | defineConstraints: []
10 | isPreloaded: 0
11 | isOverridable: 1
12 | isExplicitlyReferenced: 0
13 | validateReferences: 1
14 | platformData:
15 | - first:
16 | : Any
17 | second:
18 | enabled: 0
19 | settings:
20 | Exclude Android: 0
21 | Exclude Editor: 1
22 | Exclude Linux64: 1
23 | Exclude OSXUniversal: 1
24 | Exclude Win: 1
25 | Exclude Win64: 1
26 | Exclude iOS: 1
27 | - first:
28 | Android: Android
29 | second:
30 | enabled: 1
31 | settings:
32 | CPU: ARMv7
33 | - first:
34 | Any:
35 | second:
36 | enabled: 0
37 | settings: {}
38 | - first:
39 | Editor: Editor
40 | second:
41 | enabled: 0
42 | settings:
43 | CPU: AnyCPU
44 | DefaultValueInitialized: true
45 | OS: AnyOS
46 | - first:
47 | Standalone: Linux64
48 | second:
49 | enabled: 0
50 | settings:
51 | CPU: None
52 | - first:
53 | Standalone: OSXUniversal
54 | second:
55 | enabled: 0
56 | settings:
57 | CPU: None
58 | - first:
59 | Standalone: Win
60 | second:
61 | enabled: 0
62 | settings:
63 | CPU: None
64 | - first:
65 | Standalone: Win64
66 | second:
67 | enabled: 0
68 | settings:
69 | CPU: None
70 | - first:
71 | iPhone: iOS
72 | second:
73 | enabled: 0
74 | settings:
75 | AddToEmbeddedBinaries: false
76 | CPU: AnyCPU
77 | CompileFlags:
78 | FrameworkDependencies:
79 | userData:
80 | assetBundleName:
81 | assetBundleVariant:
82 |
--------------------------------------------------------------------------------
/Tests/Runtime/DownloadTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using NUnit.Framework;
6 | using UnityEngine;
7 | using UnityEngine.TestTools;
8 | using Unity.Networking;
9 |
10 | namespace Tests
11 | {
12 | public class DownloadTests
13 | {
14 | const string TEST_URL = "https://memegenerator.net/img/images/1154731/success-baby.jpg";
15 | const string TEST_FILE = "success.jpg";
16 | const string TEST_FILE_IN_DIR = "tests/success.jpg";
17 |
18 | string FilePath { get { return Path.Combine(Application.persistentDataPath, TEST_FILE); } }
19 | string FileInDirPath { get { return Path.Combine(Application.persistentDataPath, TEST_FILE_IN_DIR); } }
20 |
21 | [SetUp]
22 | public void RemoveTestFile()
23 | {
24 | if (File.Exists(FilePath))
25 | File.Delete(FilePath);
26 | if (File.Exists(FileInDirPath))
27 | File.Delete(FileInDirPath);
28 | }
29 |
30 | [UnityTest]
31 | public IEnumerator DownloadFile()
32 | {
33 | #if UNITY_EDITOR
34 | yield break;
35 | #else
36 | return DownloadFileTest(TEST_FILE);
37 | #endif
38 | }
39 |
40 | [UnityTest]
41 | public IEnumerator DownloadFileToSubdir()
42 | {
43 | #if UNITY_EDITOR
44 | yield break;
45 | #else
46 | return DownloadFileTest(TEST_FILE_IN_DIR);
47 | #endif
48 | }
49 |
50 | IEnumerator DownloadFileTest(string filePath)
51 | {
52 | using (var download = BackgroundDownload.Start(new Uri(TEST_URL), filePath))
53 | {
54 | yield return download;
55 | Assert.AreEqual(BackgroundDownloadStatus.Done, download.status);
56 | Assert.IsTrue(File.Exists(Path.Combine(Application.persistentDataPath, filePath)));
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.yamato/package-promotion.yml:
--------------------------------------------------------------------------------
1 | test_editors:
2 | - version: 2020.1
3 | - version: trunk
4 | test_platforms:
5 | - name: win
6 | type: Unity::VM
7 | image: package-ci/win10:stable
8 | flavor: b1.large
9 | ---
10 |
11 | {% for editor in test_editors %}
12 | {% for platform in test_platforms %}
13 | promotion_test_{{ platform.name }}_{{ editor.version }}:
14 | name : Promotion Test {{ editor.version }} on {{ platform.name }}
15 | agent:
16 | type: {{ platform.type }}
17 | image: {{ platform.image }}
18 | flavor: {{ platform.flavor}}
19 | variables:
20 | UPMCI_PROMOTION: 1
21 | commands:
22 | - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm
23 | - upm-ci package test --unity-version {{ editor.version }}
24 | artifacts:
25 | logs:
26 | paths:
27 | - "upm-ci~/test-results/**/*"
28 | dependencies:
29 | - .yamato/package-pack.yml#pack
30 | {% endfor %}
31 | {% endfor %}
32 |
33 | promotion_test_trigger:
34 | name: Promotion Tests Trigger
35 | dependencies:
36 | {% for editor in test_editors %}
37 | {% for platform in test_platforms %}
38 | - .yamato/package-promotion.yml#promotion_test_{{platform.name}}_{{editor.version}}
39 | {% endfor %}
40 | {% endfor %}
41 |
42 | promote:
43 | name: Promote to Production
44 | agent:
45 | type: Unity::VM
46 | image: package-ci/win10:stable
47 | flavor: b1.large
48 | variables:
49 | UPMCI_PROMOTION: 1
50 | commands:
51 | - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm
52 | - upm-ci package promote --dry-run
53 | triggers:
54 | tags:
55 | only:
56 | - /^(r|R)elease-\d+\.\d+\.\d+(-preview(\.\d+)?)?$/
57 | artifacts:
58 | artifacts:
59 | paths:
60 | - "upm-ci~/packages/*.tgz"
61 | dependencies:
62 | - .yamato/package-pack.yml#pack
63 | {% for editor in test_editors %}
64 | {% for platform in test_platforms %}
65 | - .yamato/package-promotion.yml#promotion_test_{{ platform.name }}_{{ editor.version }}
66 | {% endfor %}
67 | {% endfor %}
68 |
--------------------------------------------------------------------------------
/.yamato/package-test.yml:
--------------------------------------------------------------------------------
1 | test_editors:
2 | - version: 2020.1
3 | - version: trunk
4 | test_platforms:
5 | - name: win
6 | type: Unity::VM
7 | image: package-ci/win10:stable
8 | flavor: b1.large
9 | - name: mac
10 | type: Unity::VM::osx
11 | image: package-ci/mac:stable
12 | flavor: m1.mac
13 | - name: ubuntu
14 | type: Unity::VM
15 | image: package-ci/ubuntu:stable
16 | flavor: b1.large
17 | - name: centos
18 | type: Unity::VM::GPU
19 | image: package-ci/centos:stable
20 | flavor: b1.large
21 | ---
22 |
23 | {% for editor in test_editors %}
24 | {% for platform in test_platforms %}
25 | test_{{ platform.name }}_{{ editor.version }}:
26 | name : Test {{ editor.version }} on {{ platform.name }}
27 | agent:
28 | type: {{ platform.type }}
29 | image: {{ platform.image }}
30 | flavor: {{ platform.flavor}}
31 | commands:
32 | - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm
33 | - {% if platform.name == "centos" %}DISPLAY=:0 {% endif %}upm-ci package test -u {{ editor.version }} --type package-tests
34 | artifacts:
35 | logs:
36 | paths:
37 | - "upm-ci~/test-results/**/*"
38 | dependencies:
39 | - .yamato/package-pack.yml#pack
40 | {% endfor %}
41 | {% endfor %}
42 |
43 | # Validate the package on each editor version and each platform
44 | # Validation only occurs in editmode.
45 | {% for editor in test_editors %}
46 | {% for platform in test_platforms %}
47 | validate_{{ platform.name }}_{{ editor.version }}:
48 | name : Validate {{ editor.version }} on {{ platform.name }}
49 | agent:
50 | type: {{ platform.type }}
51 | image: {{ platform.image }}
52 | flavor: {{ platform.flavor}}
53 | commands:
54 | - npm install upm-ci-utils@stable -g --registry https://artifactory.prd.cds.internal.unity3d.com/artifactory/api/npm/upm-npm
55 | - {% if platform.name == "centos" %}DISPLAY=:0 {% endif %}upm-ci package test -u {{ editor.version }} --type vetting-tests --platform editmode
56 | artifacts:
57 | logs:
58 | paths:
59 | - "upm-ci~/test-results/**/*"
60 | dependencies:
61 | - .yamato/package-pack.yml#pack
62 | {% endfor %}
63 | {% endfor %}
64 |
65 | test_trigger:
66 | name: Tests Trigger
67 | triggers:
68 | branches:
69 | only:
70 | - "master"
71 | pull_requests:
72 | - targets:
73 | only:
74 | - "/.*/"
75 | dependencies:
76 | - .yamato/package-pack.yml#pack
77 | {% for editor in test_editors %}
78 | {% for platform in test_platforms %}
79 | - .yamato/package-test.yml#test_{{platform.name}}_{{editor.version}}
80 | - .yamato/package-test.yml#validate_{{platform.name}}_{{editor.version}}
81 | {% endfor %}
82 | {% endfor %}
83 |
--------------------------------------------------------------------------------
/Runtime/Plugins/Android/backgrounddownload.androidlib/src/main/java/com/unity3d/backgrounddownload/BackgroundDownload.java:
--------------------------------------------------------------------------------
1 | package com.unity3d.backgrounddownload;
2 |
3 | import android.app.DownloadManager;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 |
8 | public class BackgroundDownload {
9 | public static BackgroundDownload create(String url, String destUri) {
10 | Uri uri = Uri.parse(url);
11 | Uri dest = Uri.parse(destUri);
12 | return new BackgroundDownload(uri, dest);
13 | }
14 |
15 | public static BackgroundDownload recreate(Context context, long id) {
16 | DownloadManager manager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
17 | DownloadManager.Query query = new DownloadManager.Query();
18 | query.setFilterById(id);
19 | Cursor cursor = manager.query(query);
20 | if (cursor.getCount() == 0)
21 | return null;
22 | cursor.moveToFirst();
23 | Uri url = Uri.parse(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI)));
24 | Uri dest = Uri.parse(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
25 | cursor.close();
26 | return new BackgroundDownload(manager, id, url, dest);
27 | }
28 |
29 | private Uri downloadUri;
30 | private Uri destinationUri;
31 | private DownloadManager manager;
32 | private DownloadManager.Request request;
33 | private long id;
34 | private String error;
35 |
36 | private BackgroundDownload(Uri url, Uri dest) {
37 | downloadUri = url;
38 | destinationUri = dest;
39 | request = new DownloadManager.Request(url);
40 | request.setDestinationUri(dest);
41 | request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
42 | }
43 |
44 | private BackgroundDownload(DownloadManager manager, long id, Uri url, Uri dest)
45 | {
46 | this.manager = manager;
47 | this.id = id;
48 | downloadUri = url;
49 | destinationUri = dest;
50 | }
51 |
52 | public void setAllowMetered(boolean allow) {
53 | request.setAllowedOverMetered(allow);
54 | }
55 |
56 | public void setAllowRoaming(boolean allow) {
57 | request.setAllowedOverRoaming(allow);
58 | }
59 |
60 | public long start(Context context) {
61 | manager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
62 | id = manager.enqueue(request);
63 | return id;
64 | }
65 |
66 | public void addRequestHeader(String name, String value) {
67 | request.addRequestHeader(name, value);
68 | }
69 |
70 | public int checkFinished() {
71 | if (error != null)
72 | return -1;
73 | Uri uri = manager.getUriForDownloadedFile(id);
74 | if (uri != null)
75 | return 1;
76 | DownloadManager.Query query = new DownloadManager.Query();
77 | query.setFilterById(id);
78 | Cursor cursor = manager.query(query);
79 | if (cursor.getCount() == 0) {
80 | error = "Background download not found";
81 | return -1;
82 | }
83 | cursor.moveToFirst();
84 | int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
85 | int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
86 | cursor.close();
87 | switch (status)
88 | {
89 | case DownloadManager.STATUS_FAILED:
90 | error = reasonToError(reason);
91 | return -1;
92 | case DownloadManager.STATUS_SUCCESSFUL:
93 | return 1;
94 | default:
95 | return 0;
96 | }
97 | }
98 |
99 | public float getProgress() {
100 | if (error != null)
101 | return 1.0f;
102 | Uri uri = manager.getUriForDownloadedFile(id);
103 | if (uri != null)
104 | return 1.0f;
105 | DownloadManager.Query query = new DownloadManager.Query();
106 | query.setFilterById(id);
107 | Cursor cursor = manager.query(query);
108 | if (cursor.getCount() == 0) {
109 | error = "Background download not found";
110 | return 1.0f;
111 | }
112 | cursor.moveToFirst();
113 | int downloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
114 | int total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
115 | cursor.close();
116 | if (downloaded <= 0)
117 | return 0.0f;
118 | if (total <= 0)
119 | return -1.0f;
120 | float ret = downloaded / (float)total;
121 | if (ret < 1.0f)
122 | return ret;
123 | return 1.0f;
124 | }
125 |
126 | public String getDownloadUrl() {
127 | return downloadUri.toString();
128 | }
129 |
130 | public String getDestinationUri() {
131 | return destinationUri.toString();
132 | }
133 |
134 | public String getError() {
135 | return error;
136 | }
137 |
138 | private static String reasonToError(int reason) {
139 | switch (reason) {
140 | case DownloadManager.ERROR_CANNOT_RESUME:
141 | return "Cannot resume";
142 | case DownloadManager.ERROR_DEVICE_NOT_FOUND:
143 | return "Device not found";
144 | case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
145 | return "File already exists";
146 | case DownloadManager.ERROR_FILE_ERROR:
147 | return "File error";
148 | case DownloadManager.ERROR_HTTP_DATA_ERROR:
149 | return "HTTP data error";
150 | case DownloadManager.ERROR_INSUFFICIENT_SPACE:
151 | return "Insufficient space";
152 | case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
153 | return "Too many redirects";
154 | case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
155 | return "Unhandled HTTP code";
156 | case DownloadManager.ERROR_UNKNOWN:
157 | default:
158 | return "Unkown error";
159 | }
160 | }
161 |
162 | public void remove() {
163 | if (checkFinished() == 0)
164 | error = "Aborted";
165 | manager.remove(id);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadiOS.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_IOS
2 |
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Text;
7 | using System.Runtime.InteropServices;
8 | using UnityEngine;
9 |
10 |
11 | namespace Unity.Networking
12 | {
13 |
14 | class BackgroundDownloadiOS : BackgroundDownload
15 | {
16 | IntPtr _backend;
17 |
18 | internal BackgroundDownloadiOS(BackgroundDownloadConfig config)
19 | : base(config)
20 | {
21 | var destDir = Path.GetDirectoryName(Path.Combine(Application.persistentDataPath, config.filePath));
22 | if (!Directory.Exists(destDir))
23 | Directory.CreateDirectory(destDir);
24 | IntPtr request = UnityBackgroundDownloadCreateRequest(config.url.AbsoluteUri);
25 | if (config.requestHeaders != null)
26 | foreach (var header in config.requestHeaders)
27 | if (header.Value != null)
28 | foreach (var val in header.Value)
29 | UnityBackgroundDownloadAddRequestHeader(request, header.Key, val);
30 | _backend = UnityBackgroundDownloadStart(request, config.filePath);
31 | }
32 |
33 | BackgroundDownloadiOS(IntPtr backend, BackgroundDownloadConfig config)
34 | : base(config)
35 | {
36 | _backend = backend;
37 | }
38 |
39 | public override BackgroundDownloadStatus status { get { UpdateStatus(); return base.status; } }
40 |
41 | public override bool keepWaiting
42 | {
43 | get
44 | {
45 | UpdateStatus();
46 | return _status == BackgroundDownloadStatus.Downloading;
47 | }
48 | }
49 |
50 | internal static Dictionary LoadDownloads()
51 | {
52 | var downloads = new Dictionary();
53 | int numDownloads = UnityBackgroundDownloadGetCount();
54 | if (numDownloads > 0)
55 | {
56 | IntPtr[] loadedDownloads = new IntPtr[numDownloads];
57 | UnityBackgroundDownloadGetAll(loadedDownloads);
58 | byte[] buffer = new byte[2048];
59 |
60 | for (int i = 0; i < numDownloads; ++i)
61 | {
62 | IntPtr backend = loadedDownloads[i];
63 | BackgroundDownloadConfig config = new BackgroundDownloadConfig();
64 | int length = UnityBackgroundDownloadGetUrl(backend, buffer);
65 | config.url = new Uri(MarshalObjCString(buffer, length));
66 | length = UnityBackgroundDownloadGetFilePath(backend, buffer);
67 | config.filePath = MarshalObjCString(buffer, length);
68 | var dl = new BackgroundDownloadiOS(backend, config);
69 | downloads[config.filePath] = dl;
70 | }
71 | }
72 |
73 | return downloads;
74 | }
75 |
76 | internal static void SaveDownloads(Dictionary downloads)
77 | {
78 | }
79 |
80 | protected override float GetProgress()
81 | {
82 | if (_backend == IntPtr.Zero)
83 | return 1.0f;
84 | if (_status != BackgroundDownloadStatus.Downloading)
85 | return 1.0f;
86 | return UnityBackgroundDownloadGetProgress(_backend);
87 | }
88 |
89 | public override void Dispose()
90 | {
91 | if (_backend != IntPtr.Zero)
92 | UnityBackgroundDownloadDestroy(_backend);
93 | base.Dispose();
94 | }
95 |
96 | private void UpdateStatus()
97 | {
98 | if (_backend == IntPtr.Zero)
99 | return;
100 | if (_status != BackgroundDownloadStatus.Downloading)
101 | return;
102 | _status = (BackgroundDownloadStatus)UnityBackgroundDownloadGetStatus(_backend);
103 | if (_status == BackgroundDownloadStatus.Failed)
104 | _error = GetError();
105 | }
106 |
107 | private string GetError()
108 | {
109 | if (_backend == IntPtr.Zero)
110 | return "";
111 | byte[] buffer = new byte[2048];
112 | int length = UnityBackgroundDownloadGetError(_backend, buffer);
113 | return MarshalObjCString(buffer, length);
114 | }
115 |
116 | private static string MarshalObjCString(byte[] buffer, int length)
117 | {
118 | return Encoding.Unicode.GetString(buffer, 0, length);
119 | }
120 |
121 | [DllImport("__Internal")]
122 | static extern IntPtr UnityBackgroundDownloadCreateRequest([MarshalAs(UnmanagedType.LPWStr)] string url);
123 |
124 | [DllImport("__Internal")]
125 | static extern void UnityBackgroundDownloadAddRequestHeader(IntPtr headers,
126 | [MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] string value);
127 |
128 | [DllImport("__Internal")]
129 | static extern IntPtr UnityBackgroundDownloadStart(IntPtr request, [MarshalAs(UnmanagedType.LPWStr)] string dest);
130 |
131 | [DllImport("__Internal")]
132 | static extern int UnityBackgroundDownloadGetStatus(IntPtr backend);
133 |
134 | [DllImport("__Internal")]
135 | static extern float UnityBackgroundDownloadGetProgress(IntPtr backend);
136 |
137 | [DllImport("__Internal")]
138 | static extern void UnityBackgroundDownloadDestroy(IntPtr backend);
139 |
140 | [DllImport("__Internal")]
141 | static extern int UnityBackgroundDownloadGetCount();
142 |
143 | [DllImport("__Internal")]
144 | static extern void UnityBackgroundDownloadGetAll([MarshalAs(UnmanagedType.LPArray)] IntPtr[] downloads);
145 |
146 | [DllImport("__Internal")]
147 | static extern int UnityBackgroundDownloadGetUrl(IntPtr backend, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer);
148 |
149 | [DllImport("__Internal")]
150 | static extern int UnityBackgroundDownloadGetFilePath(IntPtr backend, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer);
151 |
152 | [DllImport("__Internal")]
153 | static extern int UnityBackgroundDownloadGetError(IntPtr backend, [MarshalAs(UnmanagedType.LPArray)] byte[] buffer);
154 | }
155 |
156 | }
157 |
158 | #endif
159 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About com.unity.backgrounddownload
2 |
3 | ## Note: Unsupported feature
4 | This package is not an officially supported feature, and is provided "as is".
5 |
6 | ## Feature scope
7 | Use Background Download to download large files in the background on mobile platforms. It lets you fetch files that aren't required immediately while caring less about application lifecycle. Downloads will continue even if your application goes into background or the Operating System closes it (usually due to low memory for foreground tasks).
8 | The Background Download package is not a replacement for HTTP clients. It has a specific focus on fetching lower-priority files for future use. Because the app assumes that these downloads have lower priority, download speeds can also be slower.
9 |
10 | **The Background Download is not integrated with the Addressable System or Asset Bundles** and will require you write additional code to use in this context.
11 |
12 |
13 | ## Limited platform support
14 | Background Download only works on Android, iOS and Universal Windows Platform.
15 | It does not work in the Unity Editor, it only compiles.
16 |
17 | ## How to install
18 |
19 | ### For 2019.4 or newer
20 | This feature is built as a package. To install the package, follow the instructions in the Package Manager documentation [from a local folder](https://docs.unity3d.com/Manual/upm-ui-local.html) or [from a GIT URL](https://docs.unity3d.com/Manual/upm-ui-giturl.html).
21 |
22 | For 2019.3 or older:
23 | Clone/download this project from the 2019-3-and-older branch. Drop BackgroundDownload and Plugins folders into Assets in your Unity project. If you are building for Android, you have to set Write Permission to External in Player Settings. If you are building for Universal Windows Platform, you need to enable one of the Internet permissions.
24 |
25 | ## Package contents
26 |
27 | The following table describes the package folder structure:
28 |
29 | |**Location**|**Description**|
30 | |---|---|
31 | |`Runtime`|Contains C# code and native plugins for mobile platforms.|
32 | |`Samples`|Contains example C# scripts explaining how to use this package.|
33 |
34 |
35 | # Examples
36 |
37 | The example below shows how to call [`StartCoroutine(StartDownload())`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html) to download a file during the same app session in a coroutine.
38 |
39 | ```
40 | IEnumerator StartDownload()
41 | {
42 | using (var download = BackgroundDownload.Start(new Uri("https://mysite.com/file"), "files/file.data"))
43 | {
44 | yield return download;
45 | if (download.status == BackgroundDownloadStatus.Failed)
46 | Debug.Log(download.error);
47 | else
48 | Debug.Log("DONE downloading file");
49 | }
50 | }
51 | ```
52 |
53 | The example below shows how to pick up a download from a previous app run and continue it until it finishes.
54 |
55 | ```
56 | IEnumerator ResumeDownload()
57 | {
58 | if (BackgroundDownload.backgroundDownloads.Length == 0)
59 | yield break;
60 | var download = BackgroundDownload.backgroundDownloads[0];
61 | yield return download;
62 | // deal with results here
63 | // dispose download
64 | download.Dispose();
65 | }
66 | ```
67 |
68 | # API
69 |
70 | ## BackgroundDownloadPolicy
71 |
72 | Enum that lets control the network types over which the downloads are allowed to happen. Not supported on iOS.
73 |
74 | Possible values:
75 | * `UnrestrictedOnly` - downloads using unlimited connection, such as Wi-Fi.
76 | * `AllowMetered` - allows downloads using metered connections, such as mobile data (default).
77 | * `AlwaysAllow` - allows downloads using all network types, including potentially expensive ones, such as roaming.
78 |
79 |
80 | ## BackgroundDownloadConfig
81 |
82 | Structure containing all the data required to start background download.
83 | This structure must contain the URL to file to download and a path to file to store. Destination file will be overwritten if exists. Destination path must relative and result will be placed inside `Application.persistentDataPath`, because directories an application is allowed to write to are not guaranteed to be the same across different app runs.
84 | Optionally can contain custom HTTP headers to send and network policy. These two settings are not guaranteed to persist across different app runs.
85 |
86 | Fields:
87 | * `System.Uri url` - the URL to the file to download.
88 | * `string filePath` - a **relative** file path that must be relative (will be inside `Application.persistentDataPath`).
89 | * `BackgroundDownloadPolicy policy` - policy to limit downloads to certain network types. Does not persist across app runs.
90 | * `float progress` - how far the request has progressed (0 to 1), negative value if unkown. Accessing this field can be very expensive (in particular on Android).
91 | * `Dictionary> requestHeaders` - custom HTTP headers to send. Does not persist across app runs.
92 |
93 | Methods:
94 | * `void AddRequestHeader(string name, string value)` - convenience method to add custom HTTP header to `requestHeaders`.
95 |
96 | ## BackgroundDownloadStatus
97 |
98 | The state of the background download
99 |
100 | Values:
101 | * `Downloading` - the download is in progress.
102 | * `Done` - the download has finished successfully.
103 | * `Failed` - the download operation has failed.
104 |
105 | ## BackgroundDownload
106 |
107 | A class for launching and picking up downloads in background.
108 | Every background download can be returned from the coroutine to wait for it's completion. To free system resources, after completion each background download must be disposed by calling `Dispose()` or by placing the object in a `using` block. If background download is disposed before completion, it is also cancelled, the existence and contents of result file is undefined in such case.
109 | **The destination file must not be used before download completes.** Otherwise it may prevent the download from writing to destination.
110 | If app is quit by the operating system, on next run background downloads can be picked up by accessing `BackgroundDownload.backgroundDownloads`.
111 |
112 | Properties:
113 | * `static BackgroundDownload[] backgroundDownloads` - an array containing all current downloads.
114 | * `BackgroundDownloadConfig config` - the configuration of download. Read only.
115 | * `BackgroundDownloadStatus status` - the status of download. Read only.
116 | * `string error` - contains error message for a failed download. Read only.
117 |
118 | Methods:
119 | * `static BackgroundDownload Start(BackgroundDownloadConfig config)` - start download using given configuration.
120 | * `static BackgroundDownload Start(Uri url, String filePath)` - convenience method to start download when no additional settings are required.
121 | * `void Dispose()` - release the resources and remove the download. Cancels download if incomplete.
122 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadUWP.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_WSA
2 |
3 | using System.Collections.Generic;
4 | #if ENABLE_WINMD_SUPPORT
5 | using System;
6 | using System.IO;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using Windows.Foundation;
10 | using Windows.Networking.BackgroundTransfer;
11 | using Windows.Storage;
12 | using UnityEngine;
13 | #endif
14 |
15 | namespace Unity.Networking
16 | {
17 | class BackgroundDownloadUWP : BackgroundDownload
18 | {
19 | #if ENABLE_WINMD_SUPPORT
20 | static BackgroundTransferGroup s_BackgroundDownloadGroup;
21 |
22 | CancellationTokenSource _cancelSource;
23 | IAsyncOperationWithProgress _downloadOperation;
24 | DownloadOperation _download;
25 | #endif
26 |
27 | internal BackgroundDownloadUWP(BackgroundDownloadConfig config)
28 | : base(config)
29 | {
30 | #if ENABLE_WINMD_SUPPORT
31 | CreateBackgroundDownloadGroup();
32 | string filePath = Path.Combine(Application.persistentDataPath, config.filePath);
33 | string directory = Path.GetDirectoryName(filePath).Replace('/', '\\');
34 | if (!Directory.Exists(directory))
35 | Directory.CreateDirectory(directory);
36 | _cancelSource = new CancellationTokenSource();
37 | StartDownload(filePath, directory);
38 | #endif
39 | }
40 |
41 | #if ENABLE_WINMD_SUPPORT
42 | // constructor for recreating download from existing one in OS
43 | internal BackgroundDownloadUWP(Uri url, string filePath)
44 | {
45 | _config.url = url;
46 | _config.filePath = filePath;
47 | _cancelSource = new CancellationTokenSource();
48 | }
49 |
50 | async void StartDownload(string filePath, string directory)
51 | {
52 | try
53 | {
54 | StorageFolder folder = await StorageFolder.GetFolderFromPathAsync(directory).AsTask(_cancelSource.Token);
55 | var fileName = Path.GetFileName(filePath);
56 | StorageFile resultFile = await folder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting).AsTask(_cancelSource.Token);
57 |
58 | var downloader = new BackgroundDownloader();
59 | downloader.TransferGroup = s_BackgroundDownloadGroup;
60 | if (config.requestHeaders != null)
61 | foreach (var header in config.requestHeaders)
62 | if (header.Value != null)
63 | foreach (var value in header.Value)
64 | downloader.SetRequestHeader(header.Key, value);
65 | switch (config.policy)
66 | {
67 | case BackgroundDownloadPolicy.AlwaysAllow:
68 | downloader.CostPolicy = BackgroundTransferCostPolicy.Always;
69 | break;
70 | case BackgroundDownloadPolicy.AllowMetered:
71 | case BackgroundDownloadPolicy.Default:
72 | downloader.CostPolicy = BackgroundTransferCostPolicy.Default;
73 | break;
74 | case BackgroundDownloadPolicy.UnrestrictedOnly:
75 | downloader.CostPolicy = BackgroundTransferCostPolicy.UnrestrictedOnly;
76 | break;
77 | }
78 | _download = downloader.CreateDownload(config.url, resultFile);
79 | _downloadOperation = _download.StartAsync();
80 | var downloadTask = _downloadOperation.AsTask();
81 | downloadTask.ContinueWith(DownloadTaskFinished, _cancelSource.Token);
82 | }
83 | catch (OperationCanceledException)
84 | {
85 | _error = "Download aborted";
86 | _status = BackgroundDownloadStatus.Failed;
87 | }
88 | catch (Exception e)
89 | {
90 | _error = "Download: " + e.Message;
91 | _status = BackgroundDownloadStatus.Failed;
92 | }
93 | }
94 | #endif
95 |
96 | public override bool keepWaiting { get { return _status == BackgroundDownloadStatus.Downloading; } }
97 |
98 | internal static Dictionary LoadDownloads()
99 | {
100 | var downloads = new Dictionary();
101 |
102 | #if ENABLE_WINMD_SUPPORT
103 | CreateBackgroundDownloadGroup();
104 | var downloadsTask = BackgroundDownloader.GetCurrentDownloadsForTransferGroupAsync(s_BackgroundDownloadGroup).AsTask();
105 | downloadsTask.Wait();
106 | foreach (var download in downloadsTask.Result)
107 | {
108 | var uri = download.RequestedUri;
109 | var filePath = GetDownloadPath(download.ResultFile.Path);
110 | if (filePath != null)
111 | {
112 | var dl = new BackgroundDownloadUWP(uri, filePath);
113 | dl._download = download;
114 | switch (download.Progress.Status)
115 | {
116 | case BackgroundTransferStatus.Completed:
117 | dl._status = BackgroundDownloadStatus.Done;
118 | break;
119 | case BackgroundTransferStatus.Error:
120 | dl._status = BackgroundDownloadStatus.Failed;
121 | dl._error = download.CurrentWebErrorStatus.ToString();
122 | break;
123 | }
124 |
125 | downloads[filePath] = dl;
126 | dl._downloadOperation = download.AttachAsync();
127 | dl._downloadOperation.AsTask().ContinueWith(dl.DownloadTaskFinished, dl._cancelSource.Token);
128 | }
129 | }
130 | #endif
131 |
132 | return downloads;
133 | }
134 |
135 | internal static void SaveDownloads(Dictionary downloads)
136 | {
137 | }
138 |
139 | protected override float GetProgress()
140 | {
141 | #if ENABLE_WINMD_SUPPORT
142 | if (_status != BackgroundDownloadStatus.Downloading)
143 | return 1.0f;
144 | if (_download != null)
145 | {
146 | var progress = _download.Progress;
147 | double received = progress.BytesReceived;
148 | double total = progress.TotalBytesToReceive;
149 | float ret = total > 0 ? (float)(received / total) : -1.0f;
150 | return ret > 1 ? 1.0f : ret;
151 | }
152 | #endif
153 | return 0.0f;
154 | }
155 |
156 | #if ENABLE_WINMD_SUPPORT
157 | public override void Dispose()
158 | {
159 | if (_status == BackgroundDownloadStatus.Downloading)
160 | {
161 | _cancelSource.Cancel();
162 | if (_downloadOperation != null)
163 | _downloadOperation.Cancel();
164 | }
165 | base.Dispose();
166 | }
167 |
168 | static void CreateBackgroundDownloadGroup()
169 | {
170 | if (s_BackgroundDownloadGroup == null)
171 | s_BackgroundDownloadGroup = BackgroundTransferGroup.CreateGroup("UnityBackgroundDownloads");
172 | }
173 |
174 | string GetErrorMessage(Task task, string prefix)
175 | {
176 | switch (task.Status)
177 | {
178 | case TaskStatus.Canceled:
179 | return "Aborted";
180 | case TaskStatus.Faulted:
181 | return prefix + task.Exception.GetBaseException().Message;
182 | default:
183 | return "Unknown error";
184 | }
185 | }
186 |
187 | void DownloadTaskFinished(Task downloadT)
188 | {
189 | if (downloadT.Status == TaskStatus.RanToCompletion)
190 | _status = BackgroundDownloadStatus.Done;
191 | else
192 | {
193 | _error = GetErrorMessage(downloadT, "Download failed: ");
194 | _status = BackgroundDownloadStatus.Failed;
195 | }
196 | }
197 |
198 | static string GetDownloadPath(string absPath)
199 | {
200 | string basePath = Application.persistentDataPath.Replace('/', '\\');
201 | if (!absPath.StartsWith(basePath)) // not something started by Unity
202 | return null;
203 | int idx = basePath.Length;
204 | if (absPath[idx] == '\\')
205 | idx++;
206 | return absPath.Substring(idx);
207 | }
208 | #endif
209 | }
210 | }
211 |
212 | #endif
213 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownloadAndroid.cs:
--------------------------------------------------------------------------------
1 | #if UNITY_ANDROID
2 |
3 | using System;
4 | using System.Collections;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using UnityEngine;
8 |
9 | namespace Unity.Networking
10 | {
11 |
12 | class BackgroundDownloadAndroid : BackgroundDownload
13 | {
14 | private const string TEMP_FILE_SUFFIX = ".part";
15 | static AndroidJavaClass _playerClass;
16 | static AndroidJavaClass _backgroundDownloadClass;
17 |
18 | class Callback : AndroidJavaProxy
19 | {
20 | public Callback()
21 | : base("com.unity3d.backgrounddownload.CompletionReceiver$Callback")
22 | {}
23 |
24 | void downloadCompleted()
25 | {
26 | lock (typeof(BackgroundDownload))
27 | {
28 | foreach (var download in _downloads.Values)
29 | ((BackgroundDownloadAndroid) download).CheckFinished();
30 | }
31 | }
32 | }
33 |
34 | static Callback _finishedCallback;
35 |
36 | AndroidJavaObject _download;
37 | long _id = 0;
38 | string _tempFilePath;
39 |
40 | static void SetupBackendStatics()
41 | {
42 | if (_backgroundDownloadClass == null)
43 | _backgroundDownloadClass = new AndroidJavaClass("com.unity3d.backgrounddownload.BackgroundDownload");
44 | if (_finishedCallback == null)
45 | {
46 | _finishedCallback = new Callback();
47 | var receiver = new AndroidJavaClass("com.unity3d.backgrounddownload.CompletionReceiver");
48 | receiver.CallStatic("setCallback", _finishedCallback);
49 | }
50 | if (_playerClass == null)
51 | _playerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
52 | }
53 |
54 | internal BackgroundDownloadAndroid(BackgroundDownloadConfig config)
55 | : base(config)
56 | {
57 | SetupBackendStatics();
58 | string filePath = Path.Combine(Application.persistentDataPath, config.filePath);
59 | _tempFilePath = filePath + TEMP_FILE_SUFFIX;
60 | if (File.Exists(filePath))
61 | File.Delete(filePath);
62 | if (File.Exists(_tempFilePath))
63 | File.Delete(_tempFilePath);
64 | else
65 | {
66 | var dir = Path.GetDirectoryName(filePath);
67 | if (!Directory.Exists(dir))
68 | Directory.CreateDirectory(dir);
69 | }
70 | string fileUri = "file://" + _tempFilePath;
71 | bool allowMetered = false;
72 | bool allowRoaming = false;
73 | switch (_config.policy)
74 | {
75 | case BackgroundDownloadPolicy.AllowMetered:
76 | allowMetered = true;
77 | break;
78 | case BackgroundDownloadPolicy.AlwaysAllow:
79 | allowMetered = true;
80 | allowRoaming = true;
81 | break;
82 | default:
83 | break;
84 | }
85 | _download = _backgroundDownloadClass.CallStatic("create", config.url.AbsoluteUri, fileUri);
86 |
87 | _download.Call("setAllowMetered", allowMetered);
88 | _download.Call("setAllowRoaming", allowRoaming);
89 | if (config.requestHeaders != null)
90 | foreach (var header in config.requestHeaders)
91 | if (header.Value != null)
92 | foreach (var val in header.Value)
93 | _download.Call("addRequestHeader", header.Key, val);
94 | var activity = _playerClass.GetStatic("currentActivity");
95 | _id = _download.Call("start", activity);
96 | }
97 |
98 | BackgroundDownloadAndroid(long id, AndroidJavaObject download)
99 | {
100 | _id = id;
101 | _download = download;
102 | _config.url = QueryDownloadUri();
103 | _config.filePath = QueryDestinationPath(out _tempFilePath);
104 | CheckFinished();
105 | }
106 |
107 | static BackgroundDownloadAndroid Recreate(long id)
108 | {
109 | try
110 | {
111 | SetupBackendStatics();
112 | var activity = _playerClass.GetStatic("currentActivity");
113 | var download = _backgroundDownloadClass.CallStatic("recreate", activity, id);
114 | if (download != null)
115 | return new BackgroundDownloadAndroid(id, download);
116 | }
117 | catch (Exception e)
118 | {
119 | Debug.LogError(string.Format("Failed to recreate background download with id {0}: {1}", id, e.Message));
120 | }
121 |
122 | return null;
123 | }
124 |
125 | Uri QueryDownloadUri()
126 | {
127 | return new Uri(_download.Call("getDownloadUrl"));
128 | }
129 |
130 | string QueryDestinationPath(out string tempFilePath)
131 | {
132 | string uri = _download.Call("getDestinationUri");
133 | string basePath = Application.persistentDataPath;
134 | var pos = uri.IndexOf(basePath);
135 | tempFilePath = uri.Substring(pos);
136 | pos += basePath.Length;
137 | if (uri[pos] == '/')
138 | ++pos;
139 | var suffixPos = uri.LastIndexOf(TEMP_FILE_SUFFIX);
140 | if (suffixPos > 0)
141 | {
142 | var length = suffixPos;
143 | length -= pos;
144 | return uri.Substring(pos, length);
145 | }
146 |
147 | return uri.Substring(pos);
148 | }
149 |
150 | string GetError()
151 | {
152 | return _download.Call("getError");
153 | }
154 |
155 | void CheckFinished()
156 | {
157 | if (_status == BackgroundDownloadStatus.Downloading)
158 | {
159 | int status = _download.Call("checkFinished");
160 | if (status == 1)
161 | {
162 | if (_tempFilePath.EndsWith(TEMP_FILE_SUFFIX))
163 | {
164 | string filePath = _tempFilePath.Substring(0, _tempFilePath.Length - TEMP_FILE_SUFFIX.Length);
165 | if (File.Exists(_tempFilePath))
166 | {
167 | if (File.Exists(filePath))
168 | File.Delete(filePath);
169 | File.Move(_tempFilePath, filePath);
170 | }
171 | }
172 |
173 | _status = BackgroundDownloadStatus.Done;
174 | }
175 | else if (status < 0)
176 | {
177 | _status = BackgroundDownloadStatus.Failed;
178 | _error = GetError();
179 | }
180 | }
181 | }
182 |
183 | void RemoveDownload()
184 | {
185 | _download.Call("remove");
186 | }
187 |
188 | public override bool keepWaiting { get { return _status == BackgroundDownloadStatus.Downloading; } }
189 |
190 | protected override float GetProgress()
191 | {
192 | return _download.Call("getProgress");
193 | }
194 |
195 | public override void Dispose()
196 | {
197 | RemoveDownload();
198 | base.Dispose();
199 | }
200 |
201 | internal static Dictionary LoadDownloads()
202 | {
203 | var downloads = new Dictionary();
204 | var file = Path.Combine(Application.persistentDataPath, "unity_background_downloads.dl");
205 | if (File.Exists(file))
206 | {
207 | foreach (var line in File.ReadAllLines(file))
208 | if (!string.IsNullOrEmpty(line))
209 | {
210 | long id = long.Parse(line);
211 | var dl = Recreate(id);
212 | if (dl != null)
213 | downloads[dl.config.filePath] = dl;
214 | }
215 | }
216 |
217 | // some loads might have failed, save the actual state
218 | SaveDownloads(downloads);
219 | return downloads;
220 | }
221 |
222 | internal static void SaveDownloads(Dictionary downloads)
223 | {
224 | var file = Path.Combine(Application.persistentDataPath, "unity_background_downloads.dl");
225 | if (downloads.Count > 0)
226 | {
227 | var ids = new string[downloads.Count];
228 | int i = 0;
229 | foreach (var dl in downloads)
230 | ids[i++] = ((BackgroundDownloadAndroid)dl.Value)._id.ToString();
231 | File.WriteAllLines(file, ids);
232 | }
233 | else if (File.Exists(file))
234 | File.Delete(file);
235 | }
236 | }
237 |
238 | }
239 |
240 | #endif
241 |
--------------------------------------------------------------------------------
/Runtime/BackgroundDownload.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using UnityEngine;
5 |
6 | #if UNITY_EDITOR
7 | using BackgroundDownloadimpl = Unity.Networking.BackgroundDownloadEditor;
8 | #elif UNITY_ANDROID
9 | using BackgroundDownloadimpl = Unity.Networking.BackgroundDownloadAndroid;
10 | #elif UNITY_IOS
11 | using BackgroundDownloadimpl = Unity.Networking.BackgroundDownloadiOS;
12 | #elif UNITY_WSA_10_0
13 | using BackgroundDownloadimpl = Unity.Networking.BackgroundDownloadUWP;
14 | #endif
15 |
16 | namespace Unity.Networking
17 | {
18 | ///
19 | /// The policy for download, such as what type of network connection can be used.
20 | /// Not supported on iOS (does nothing).
21 | ///
22 | public enum BackgroundDownloadPolicy
23 | {
24 | ///
25 | /// Operating System recommended policy.
26 | ///
27 | Default = 0,
28 | ///
29 | /// Only download when using Wi-Fi or similar unlimited connection.
30 | ///
31 | UnrestrictedOnly = 1,
32 | ///
33 | /// Allows Mobile and other metered connections, that may involve additional charges.
34 | ///
35 | AllowMetered = 2,
36 | ///
37 | /// Allows to use any connection including expensive options, such as roaming.
38 | ///
39 | AlwaysAllow = 3,
40 | }
41 |
42 | ///
43 | /// All the settings to perform a download.
44 | /// This structure must contain the URL to file to download and a path to file to store.
45 | /// Destination file will be overwritten if exists. Destination path must relative and result will be placed inside Application.persistentDataPath, because directories an application is allowed to write to are not guaranteed to be the same across different app runs.
46 | /// Optionally can contain custom HTTP headers to send and network policy. These two settings are not guaranteed to persist across different app runs.
47 | ///
48 | public struct BackgroundDownloadConfig
49 | {
50 | ///
51 | /// URL to resource to download.
52 | ///
53 | public Uri url;
54 | ///
55 | /// A relative path to safe downloaded file to; will be saved under Application.persistentDataPath.
56 | ///
57 | public string filePath;
58 | ///
59 | /// A policy to limit this download to certain network types; does not persist across app runs.
60 | ///
61 | public BackgroundDownloadPolicy policy;
62 | ///
63 | /// Additional HTTP headers to send with the request. Key is HTTP header name, value is a list of values, when more than one, multiple headers with the same key will be sent.
64 | ///
65 | public Dictionary> requestHeaders;
66 |
67 | ///
68 | /// A convenience helper to add a single HTTP header to requestHeaders. Fills the value list if called again with the same header name.
69 | ///
70 | /// HTTP header name.
71 | /// HTTP header value.
72 | /// Thrown when name or value is not valid.
73 | /// Thrown when header value is null.
74 | public void AddRequestHeader(string name, string value)
75 | {
76 | if (string.IsNullOrEmpty(name))
77 | throw new ArgumentException("Header name cannot be empty");
78 | if (value == null)
79 | throw new ArgumentNullException("Header value cannot be null");
80 | if (requestHeaders == null)
81 | requestHeaders = new Dictionary>();
82 | List values;
83 | if (requestHeaders.TryGetValue(name, out values))
84 | values.Add(value);
85 | else
86 | {
87 | values = new List();
88 | values.Add(value);
89 | requestHeaders[name] = values;
90 | }
91 | }
92 | }
93 |
94 | ///
95 | /// The current status of the download.
96 | ///
97 | public enum BackgroundDownloadStatus
98 | {
99 | ///
100 | /// The download is in progress.
101 | ///
102 | Downloading = 0,
103 | ///
104 | /// The download has finished successfully.
105 | ///
106 | Done = 1,
107 | ///
108 | /// The download has finished with an error.
109 | ///
110 | Failed = 2,
111 | }
112 |
113 | ///
114 | /// A class to perform file downloads that will continue if app goes into background or even gets shut down by OS.
115 | /// Note, that the download might not finish under specific conditions, for example Operating System can provide user with a feature to cancel such download.
116 | /// An instance of this class can be returned from the Coroutine to suspend it until download is finished.
117 | /// The object of this class must be disposed when no longer required by calling Dispose() or by placing the object in the using() block.
118 | /// If the background download is disposed before completion, it is also cancelled. The result file may or may not exist, contain old data, or contain partial data.
119 | /// The destination file must not be used before download completes. Otherwise it may prevent the download from writing to destination.
120 | /// If app is quit by the operating system, on next run background downloads can be picked up by accessing BackgroundDownload.backgroundDownloads.
121 | ///
122 | public abstract class BackgroundDownload
123 | : CustomYieldInstruction
124 | , IDisposable
125 | {
126 | protected static Dictionary _downloads;
127 |
128 | ///
129 | /// Returns an array of currently present download.
130 | /// After starting an application this should be queried to pick up the downloads from previous session.
131 | ///
132 | public static BackgroundDownload[] backgroundDownloads
133 | {
134 | get
135 | {
136 | lock (typeof(BackgroundDownload))
137 | {
138 | LoadDownloads();
139 | var ret = new BackgroundDownload[_downloads.Count];
140 | int i = 0;
141 | foreach (var download in _downloads)
142 | ret[i++] = download.Value;
143 | return ret;
144 | }
145 | }
146 | }
147 |
148 | /// Holds download configuration. For internal use only.
149 | protected BackgroundDownloadConfig _config;
150 | /// Hold download status. For internal use only.
151 | protected BackgroundDownloadStatus _status = BackgroundDownloadStatus.Downloading;
152 | /// Hold error message. For internal use only.
153 | protected string _error;
154 |
155 | ///
156 | /// Start download from given URL. Creates BackgroundDownloadConfig using given arguments.
157 | ///
158 | /// URL to download from.
159 | /// A relative path to save to; will be saved under Application.persistentDataPath.
160 | /// An instance for monitoring the progress.
161 | /// Thrown if there already is a download with given destination file.
162 | public static BackgroundDownload Start(Uri url, String filePath)
163 | {
164 | var config = new BackgroundDownloadConfig();
165 | config.url = url;
166 | config.filePath = filePath;
167 | return Start(config);
168 | }
169 |
170 | ///
171 | /// Start download using given configuration.
172 | ///
173 | /// The configuration for the download.
174 | /// An instance for monitoring the progress.
175 | /// Thrown if there already is a download with given destination file.
176 | public static BackgroundDownload Start(BackgroundDownloadConfig config)
177 | {
178 | lock (typeof(BackgroundDownload))
179 | {
180 | LoadDownloads();
181 | if (_downloads.ContainsKey(config.filePath))
182 | throw new ArgumentException("Download of this file is already present");
183 | var download = new BackgroundDownloadimpl(config);
184 | _downloads.Add(config.filePath, download);
185 | SaveDownloads();
186 | return download;
187 | }
188 | }
189 |
190 | /// For internal use.
191 | protected BackgroundDownload()
192 | {
193 | }
194 |
195 | /// For internal use.
196 | protected BackgroundDownload(BackgroundDownloadConfig config)
197 | {
198 | _config = config;
199 | switch (_config.policy)
200 | {
201 | case BackgroundDownloadPolicy.UnrestrictedOnly:
202 | case BackgroundDownloadPolicy.AllowMetered:
203 | case BackgroundDownloadPolicy.AlwaysAllow:
204 | break;
205 | case BackgroundDownloadPolicy.Default:
206 | default:
207 | _config.policy = BackgroundDownloadPolicy.AllowMetered;
208 | break;
209 | }
210 | }
211 |
212 | ///
213 | /// The configuration of this download.
214 | ///
215 | public BackgroundDownloadConfig config { get { return _config; } }
216 |
217 | ///
218 | /// The current status of this download.
219 | ///
220 | public virtual BackgroundDownloadStatus status { get { return _status; } }
221 |
222 | ///
223 | /// Error message for a failed download.
224 | ///
225 | public string error { get { return _error; } }
226 |
227 | ///
228 | /// How far the request has progressed (0 to 1), negative value if unknown. Accessing this field can be very expensive (in particular on Android).
229 | ///
230 | public float progress { get { return GetProgress(); } }
231 |
232 | /// For internal use.
233 | protected abstract float GetProgress();
234 |
235 | ///
236 | /// Disposes of this download, aborts the download if still in progress.
237 | /// All background download instances have to be disposed when no longer required.
238 | ///
239 | public virtual void Dispose()
240 | {
241 | lock (typeof(BackgroundDownload))
242 | {
243 | _downloads.Remove(_config.filePath);
244 | SaveDownloads();
245 | if (_status == BackgroundDownloadStatus.Downloading)
246 | {
247 | _status = BackgroundDownloadStatus.Failed;
248 | _error = "Aborted";
249 | }
250 | }
251 | }
252 |
253 | static void LoadDownloads()
254 | {
255 | if (_downloads == null)
256 | _downloads = BackgroundDownloadimpl.LoadDownloads();
257 | }
258 |
259 | static void SaveDownloads()
260 | {
261 | BackgroundDownloadimpl.SaveDownloads(_downloads);
262 | }
263 | }
264 | }
265 |
--------------------------------------------------------------------------------
/Runtime/Plugins/iOS/BackgroundDownload.mm:
--------------------------------------------------------------------------------
1 | #include "PluginBase/AppDelegateListener.h"
2 | #include
3 |
4 | typedef void (^UnityHandleEventsForBackgroundURLSession)();
5 |
6 | static NSString* _Nonnull kUnityBackgroungDownloadSessionID = @"UnityBackgroundDownload";
7 | static NSURLSession* gUnityBackgroundDownloadSession = nil;
8 |
9 | static NSURLSession* UnityBackgroundDownloadSession();
10 | static NSURL* GetDestinationUri(NSString* dest, NSFileManager** fileManager)
11 | {
12 | NSFileManager* manager = [NSFileManager defaultManager];
13 | NSURL* documents = [[manager URLsForDirectory: NSDocumentDirectory inDomains: NSUserDomainMask] lastObject];
14 | NSURL* destUri = [documents URLByAppendingPathComponent: dest];
15 | if (fileManager != NULL)
16 | *fileManager = manager;
17 | return destUri;
18 | }
19 |
20 | static NSString* MakeNSString(const char16_t* str)
21 | {
22 | return [NSString stringWithCharacters: (const unichar*)str length: std::char_traits::length(str)];
23 | }
24 |
25 | static int32_t NSStringToUTF16(NSString* str, void* buffer, unsigned size)
26 | {
27 | NSUInteger ret;
28 | BOOL converted = [str getBytes:buffer maxLength:size usedLength:&ret encoding:NSUTF16StringEncoding options:NSStringEncodingConversionAllowLossy range:NSMakeRange(0, str.length) remainingRange:nil];
29 | return converted ? (int32_t)ret : 0;
30 | }
31 |
32 | enum
33 | {
34 | kStatusDownloading = 0,
35 | kStatusDone = 1,
36 | kStatusFailed = 2,
37 | };
38 |
39 | @interface UnityBackgroundDownload : NSObject
40 | {
41 | }
42 |
43 | @property int status;
44 | @property NSString* error;
45 |
46 | @end
47 |
48 | @implementation UnityBackgroundDownload
49 | {
50 | int _status;
51 | NSString* _error;
52 | }
53 |
54 | @synthesize status = _status;
55 | @synthesize error = _error;
56 |
57 | - (id)init
58 | {
59 | _status = kStatusDownloading;
60 | _error = nil;
61 | return self;
62 | }
63 |
64 | @end
65 |
66 |
67 | @interface UnityBackgroundDownloadDelegate : NSObject
68 | {
69 | }
70 |
71 | @property (nullable) UnityHandleEventsForBackgroundURLSession finishEventsHandler;
72 |
73 | + (void)setFinishEventsHandler:(nonnull UnityHandleEventsForBackgroundURLSession)handler;
74 |
75 | @end
76 |
77 |
78 | @implementation UnityBackgroundDownloadDelegate
79 | {
80 | NSMutableDictionary* backgroundDownloads;
81 | UnityHandleEventsForBackgroundURLSession _finishEventsHandler;
82 | }
83 |
84 | @synthesize finishEventsHandler = _finishEventsHandler;
85 |
86 | + (void)setFinishEventsHandler:(nonnull UnityHandleEventsForBackgroundURLSession)handler
87 | {
88 | NSURLSession* session = UnityBackgroundDownloadSession();
89 | UnityBackgroundDownloadDelegate* delegate = (UnityBackgroundDownloadDelegate*)session.delegate;
90 | delegate.finishEventsHandler = handler;
91 | }
92 |
93 | - (id)init
94 | {
95 | backgroundDownloads = [[NSMutableDictionary alloc] init];
96 | return self;
97 | }
98 |
99 | - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
100 | {
101 | NSFileManager* fileManager;
102 | NSURL* destUri = GetDestinationUri(downloadTask.taskDescription, &fileManager);
103 | [fileManager replaceItemAtURL: destUri withItemAtURL: location backupItemName: nil options: NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL: nil error: nil];
104 | UnityBackgroundDownload* download = [backgroundDownloads objectForKey: downloadTask];
105 | download.status = kStatusDone;
106 | }
107 |
108 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
109 | {
110 | if (error != nil)
111 | {
112 | UnityBackgroundDownload* download = [backgroundDownloads objectForKey: (NSURLSessionDownloadTask*)task];
113 | download.status = kStatusFailed;
114 | download.error = error.localizedDescription;
115 | }
116 | }
117 |
118 | - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
119 | {
120 | if (self.finishEventsHandler != nil)
121 | {
122 | dispatch_async(dispatch_get_main_queue(), self.finishEventsHandler);
123 | self.finishEventsHandler = nil;
124 | }
125 | }
126 |
127 | - (NSURLSessionDownloadTask*)newSessionTask:(NSURLSession*)session withRequest:(NSURLRequest*)request forDestination:(NSString*)dest
128 | {
129 | NSURLSessionDownloadTask *task = [session downloadTaskWithRequest: request];
130 | task.taskDescription = dest;
131 | UnityBackgroundDownload* download = [[UnityBackgroundDownload alloc] init];
132 | [backgroundDownloads setObject: download forKey: task];
133 | return task;
134 | }
135 |
136 | - (void)collectTasksForSession:(NSURLSession*)session
137 | {
138 | [session getTasksWithCompletionHandler:^(NSArray * _Nonnull dataTasks, NSArray * _Nonnull uploadTasks, NSArray * _Nonnull downloadTasks) {
139 | for (NSUInteger i = 0; i < downloadTasks.count; ++i)
140 | {
141 | UnityBackgroundDownload* download = [[UnityBackgroundDownload alloc] init];
142 | [backgroundDownloads setObject: download forKey: downloadTasks[i]];
143 | }
144 | }];
145 | }
146 |
147 | - (NSUInteger)taskCount
148 | {
149 | return backgroundDownloads.count;
150 | }
151 |
152 | - (void)getAllTasks:(void**)downloads
153 | {
154 | NSEnumerator* tasks = backgroundDownloads.keyEnumerator;
155 | NSURLSessionDownloadTask* task = [tasks nextObject];
156 | int i = 0;
157 | while (task != nil)
158 | {
159 | downloads[i++] = (__bridge void*)task;
160 | task = [tasks nextObject];
161 | }
162 | }
163 |
164 | - (int)taskStatus:(NSURLSessionDownloadTask*)task
165 | {
166 | UnityBackgroundDownload* download = [backgroundDownloads objectForKey:task];
167 | if (download == nil)
168 | return YES;
169 | return download.status;
170 | }
171 |
172 | - (NSString*)taskError:(NSURLSessionDownloadTask*)task
173 | {
174 | UnityBackgroundDownload* download = [backgroundDownloads objectForKey:task];
175 | if (download != nil)
176 | {
177 | NSString* error = download.error;
178 | if (error != nil)
179 | return error;
180 | }
181 | return @"Unknown error";
182 | }
183 |
184 | - (void)removeTask:(NSURLSessionDownloadTask*)task
185 | {
186 | [task cancel];
187 | [backgroundDownloads removeObjectForKey: task];
188 | }
189 |
190 | @end
191 |
192 |
193 | static NSURLSession* UnityBackgroundDownloadSession()
194 | {
195 | if (gUnityBackgroundDownloadSession == nil)
196 | {
197 | NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: kUnityBackgroungDownloadSessionID];
198 | UnityBackgroundDownloadDelegate* delegate = [[UnityBackgroundDownloadDelegate alloc] init];
199 | gUnityBackgroundDownloadSession = [NSURLSession sessionWithConfiguration: config delegate: delegate delegateQueue: nil];
200 | [delegate collectTasksForSession: gUnityBackgroundDownloadSession];
201 | }
202 |
203 | return gUnityBackgroundDownloadSession;
204 | }
205 |
206 | @interface BackgroundDownloadAppListener : NSObject
207 |
208 | @end
209 |
210 | @implementation BackgroundDownloadAppListener
211 |
212 | - (void)applicationWillFinishLaunchingWithOptions:(NSNotification*)notification
213 | {
214 | UnityBackgroundDownloadSession();
215 | }
216 |
217 | - (void)onHandleEventsForBackgroundURLSession:(NSNotification *)notification
218 | {
219 | NSDictionary* args = notification.userInfo;
220 | if (args != nil)
221 | {
222 | UnityHandleEventsForBackgroundURLSession handler = [args objectForKey:kUnityBackgroungDownloadSessionID];
223 | [UnityBackgroundDownloadDelegate setFinishEventsHandler: handler];
224 | }
225 | }
226 |
227 | @end
228 |
229 | static BackgroundDownloadAppListener* s_AppEventListener;
230 |
231 | class UnityBackgroundDownloadRegistrator
232 | {
233 | public:
234 | UnityBackgroundDownloadRegistrator()
235 | {
236 | s_AppEventListener = [[BackgroundDownloadAppListener alloc] init];
237 | UnityRegisterAppDelegateListener(s_AppEventListener);
238 | }
239 | };
240 |
241 | static UnityBackgroundDownloadRegistrator gRegistrator;
242 |
243 |
244 | extern "C" void* UnityBackgroundDownloadCreateRequest(const char16_t* url)
245 | {
246 | NSURL* downloadUrl = [NSURL URLWithString: MakeNSString(url)];
247 | NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
248 | request.HTTPMethod = @"GET";
249 | request.URL = downloadUrl;
250 | return (__bridge_retained void*)request;
251 | }
252 |
253 | extern "C" void UnityBackgroundDownloadAddRequestHeader(void* req, const char16_t* header, const char16_t* value)
254 | {
255 | NSMutableURLRequest* request = (__bridge NSMutableURLRequest*)req;
256 | [request setValue: MakeNSString(value) forHTTPHeaderField: MakeNSString(header)];
257 | }
258 |
259 | extern "C" void* UnityBackgroundDownloadStart(void* req, const char16_t* dest)
260 | {
261 | NSMutableURLRequest* request = (__bridge_transfer NSMutableURLRequest*)req;
262 | NSString* destPath = MakeNSString(dest);
263 | NSURLSession* session = UnityBackgroundDownloadSession();
264 | UnityBackgroundDownloadDelegate* delegate = (UnityBackgroundDownloadDelegate*)session.delegate;
265 | NSURLSessionDownloadTask *task = [delegate newSessionTask: session withRequest: request forDestination: destPath];
266 | [task resume];
267 | return (__bridge void*)task;
268 | }
269 |
270 | extern "C" int32_t UnityBackgroundDownloadGetCount()
271 | {
272 | NSURLSession* session = UnityBackgroundDownloadSession();
273 | UnityBackgroundDownloadDelegate* delegate = (UnityBackgroundDownloadDelegate*)session.delegate;
274 | return (int32_t)[delegate taskCount];
275 | }
276 |
277 | extern "C" void UnityBackgroundDownloadGetAll(void** downloads)
278 | {
279 | NSURLSession* session = UnityBackgroundDownloadSession();
280 | UnityBackgroundDownloadDelegate* delegate = (UnityBackgroundDownloadDelegate*)session.delegate;
281 | [delegate getAllTasks:downloads];
282 | }
283 |
284 | extern "C" int32_t UnityBackgroundDownloadGetUrl(void* download, char* buffer)
285 | {
286 | NSURLSessionDownloadTask* task = (__bridge NSURLSessionDownloadTask*)download;
287 | NSString* url = task.originalRequest.URL.absoluteString;
288 | return NSStringToUTF16(url, buffer, 2048);
289 | }
290 |
291 | extern "C" int32_t UnityBackgroundDownloadGetFilePath(void* download, char* buffer)
292 | {
293 | NSURLSessionDownloadTask* task = (__bridge NSURLSessionDownloadTask*)download;
294 | NSString* dest = task.taskDescription;
295 | return NSStringToUTF16(dest, buffer, 2048);
296 | }
297 |
298 | extern "C" int32_t UnityBackgroundDownloadGetStatus(void* download)
299 | {
300 | NSURLSession* session = UnityBackgroundDownloadSession();
301 | UnityBackgroundDownloadDelegate* delegate = (UnityBackgroundDownloadDelegate*)session.delegate;
302 | NSURLSessionDownloadTask* task = (__bridge NSURLSessionDownloadTask*)download;
303 | return (int)[delegate taskStatus:task];
304 | }
305 |
306 | extern "C" float UnityBackgroundDownloadGetProgress(void* download)
307 | {
308 | if (UnityBackgroundDownloadGetStatus(download) != kStatusDownloading)
309 | return 1.0f;
310 | if (UnityiOS111orNewer())
311 | {
312 | NSURLSessionDownloadTask* task = (__bridge NSURLSessionDownloadTask*)download;
313 | return (float)task.progress.fractionCompleted;
314 | }
315 | return -1.0f;
316 | }
317 |
318 | extern "C" int32_t UnityBackgroundDownloadGetError(void* download, void* buffer)
319 | {
320 | NSURLSession* session = UnityBackgroundDownloadSession();
321 | UnityBackgroundDownloadDelegate* delegate = (UnityBackgroundDownloadDelegate*)session.delegate;
322 | NSURLSessionDownloadTask* task = (__bridge NSURLSessionDownloadTask*)download;
323 | NSString* error = [delegate taskError:task];
324 | return NSStringToUTF16(error, buffer, 2048);
325 | }
326 |
327 | extern "C" void UnityBackgroundDownloadDestroy(void* download)
328 | {
329 | NSURLSessionDownloadTask* task = (__bridge NSURLSessionDownloadTask*)download;
330 | NSURLSession* session = UnityBackgroundDownloadSession();
331 | UnityBackgroundDownloadDelegate* delegate = (UnityBackgroundDownloadDelegate*)session.delegate;
332 | [delegate removeTask: task];
333 | }
334 |
--------------------------------------------------------------------------------