├── .gitignore ├── Assets ├── Plugins │ └── Android │ │ └── unityOBBDownloader.aar └── Scripts │ ├── DownloadObbExample.cs │ ├── GooglePlayDownloader.cs │ └── GooglePlayDownloaderImpl.cs ├── CHANGELOG ├── LICENSE.txt ├── README.md └── src └── unityOBBDownloader ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proguard.cfg └── src └── main ├── AndroidManifest.xml ├── aidl └── com │ └── android │ └── vending │ └── licensing │ ├── ILicenseResultListener.aidl │ └── ILicensingService.aidl ├── java └── com │ ├── google │ └── android │ │ └── vending │ │ ├── expansion │ │ └── downloader │ │ │ ├── Constants.java │ │ │ ├── DownloadProgressInfo.java │ │ │ ├── DownloaderClientMarshaller.java │ │ │ ├── DownloaderServiceMarshaller.java │ │ │ ├── Helpers.java │ │ │ ├── IDownloaderClient.java │ │ │ ├── IDownloaderService.java │ │ │ ├── IStub.java │ │ │ ├── SystemFacade.java │ │ │ └── impl │ │ │ ├── CustomIntentService.java │ │ │ ├── DownloadInfo.java │ │ │ ├── DownloadNotification.java │ │ │ ├── DownloadThread.java │ │ │ ├── DownloaderService.java │ │ │ ├── DownloadsDB.java │ │ │ └── HttpDateTime.java │ │ └── licensing │ │ ├── AESObfuscator.java │ │ ├── APKExpansionPolicy.java │ │ ├── DeviceLimiter.java │ │ ├── LicenseChecker.java │ │ ├── LicenseCheckerCallback.java │ │ ├── LicenseValidator.java │ │ ├── NullDeviceLimiter.java │ │ ├── Obfuscator.java │ │ ├── Policy.java │ │ ├── PreferenceObfuscator.java │ │ ├── ResponseData.java │ │ ├── ServerManagedPolicy.java │ │ ├── StrictPolicy.java │ │ ├── ValidationException.java │ │ └── util │ │ ├── Base64.java │ │ ├── Base64DecoderException.java │ │ └── URIQueryDecoder.java │ └── unity3d │ └── plugin │ └── downloader │ ├── UnityAlarmReceiver.java │ ├── UnityDownloaderActivity.java │ └── UnityDownloaderService.java └── res ├── drawable-hdpi └── notify_panel_notification_icon_bg.png ├── drawable-mdpi └── notify_panel_notification_icon_bg.png ├── layout ├── main.xml └── status_bar_ongoing_event_progress_bar.xml └── values ├── main-strings.xml ├── strings.xml └── styles.xml /.gitignore: -------------------------------------------------------------------------------- 1 | Library/ 2 | Packages/ 3 | ProjectSettings/ 4 | src/unityOBBDownloader/.gradle/ 5 | src/unityOBBDownloader/build/ 6 | Temp/ 7 | UnityPackageManager/ 8 | *.meta 9 | *.csproj 10 | *.sln 11 | .vs/ 12 | -------------------------------------------------------------------------------- /Assets/Plugins/Android/unityOBBDownloader.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Over17/UnityOBBDownloader/d77e66f64c2738b2642f39c5ac91bc31ad464f12/Assets/Plugins/Android/unityOBBDownloader.aar -------------------------------------------------------------------------------- /Assets/Scripts/DownloadObbExample.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class DownloadObbExample : MonoBehaviour 5 | { 6 | private IGooglePlayObbDownloader m_obbDownloader; 7 | void Start() 8 | { 9 | m_obbDownloader = GooglePlayObbDownloadManager.GetGooglePlayObbDownloader(); 10 | m_obbDownloader.PublicKey = ""; // YOUR PUBLIC KEY HERE 11 | } 12 | 13 | void OnGUI() 14 | { 15 | if (!GooglePlayObbDownloadManager.IsDownloaderAvailable()) 16 | { 17 | GUI.Label(new Rect(10, 10, Screen.width-10, 20), "Use GooglePlayDownloader only on Android device!"); 18 | return; 19 | } 20 | 21 | string expPath = m_obbDownloader.GetExpansionFilePath(); 22 | if (expPath == null) 23 | { 24 | GUI.Label(new Rect(10, 10, Screen.width-10, 20), "External storage is not available!"); 25 | } 26 | else 27 | { 28 | var mainPath = m_obbDownloader.GetMainOBBPath(); 29 | var patchPath = m_obbDownloader.GetPatchOBBPath(); 30 | 31 | GUI.Label(new Rect(10, 10, Screen.width-10, 20), "Main = ..." + ( mainPath == null ? " NOT AVAILABLE" : mainPath.Substring(expPath.Length))); 32 | GUI.Label(new Rect(10, 25, Screen.width-10, 20), "Patch = ..." + (patchPath == null ? " NOT AVAILABLE" : patchPath.Substring(expPath.Length))); 33 | if (mainPath == null || patchPath == null) 34 | if (GUI.Button(new Rect(10, 100, 100, 100), "Fetch OBBs")) 35 | m_obbDownloader.FetchOBB(); 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Assets/Scripts/GooglePlayDownloader.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | 4 | public interface IGooglePlayObbDownloader 5 | { 6 | string PublicKey { get; set; } 7 | 8 | string GetExpansionFilePath(); 9 | string GetMainOBBPath(); 10 | string GetPatchOBBPath(); 11 | void FetchOBB(); 12 | } 13 | 14 | public class GooglePlayObbDownloadManager 15 | { 16 | private static AndroidJavaClass m_AndroidOSBuildClass = new AndroidJavaClass("android.os.Build"); 17 | private static IGooglePlayObbDownloader m_Instance; 18 | 19 | public static IGooglePlayObbDownloader GetGooglePlayObbDownloader() 20 | { 21 | if (m_Instance != null) 22 | return m_Instance; 23 | 24 | if (!IsDownloaderAvailable()) 25 | return null; 26 | 27 | m_Instance = new GooglePlayObbDownloader(); 28 | return m_Instance; 29 | } 30 | 31 | public static bool IsDownloaderAvailable() 32 | { 33 | return m_AndroidOSBuildClass.GetRawClass() != IntPtr.Zero; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Assets/Scripts/GooglePlayDownloaderImpl.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.IO; 3 | using System; 4 | 5 | internal class GooglePlayObbDownloader : IGooglePlayObbDownloader 6 | { 7 | private static AndroidJavaClass EnvironmentClass = new AndroidJavaClass("android.os.Environment"); 8 | private const string Environment_MediaMounted = "mounted"; 9 | 10 | public string PublicKey { get; set; } 11 | 12 | private void ApplyPublicKey() 13 | { 14 | if (string.IsNullOrEmpty(PublicKey)) 15 | { 16 | Debug.LogError("GooglePlayObbDownloader: The public key is not set - did you forget to set it in the script?\n"); 17 | } 18 | using (var downloaderServiceClass = new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderService")) 19 | { 20 | downloaderServiceClass.SetStatic("BASE64_PUBLIC_KEY", PublicKey); 21 | // Used by the preference obfuscator 22 | downloaderServiceClass.SetStatic("SALT", new byte[] { 1, 43, 256 - 12, 256 - 1, 54, 98, 256 - 100, 256 - 12, 43, 2, 256 - 8, 256 - 4, 9, 5, 256 - 106, 256 - 108, 256 - 33, 45, 256 - 1, 84 }); 23 | } 24 | } 25 | 26 | public void FetchOBB() 27 | { 28 | ApplyPublicKey(); 29 | using (var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) 30 | { 31 | var currentActivity = unityPlayerClass.GetStatic("currentActivity"); 32 | var intent = new AndroidJavaObject("android.content.Intent", 33 | currentActivity, 34 | new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderActivity")); 35 | 36 | const int Intent_FLAG_ACTIVITY_NO_ANIMATION = 0x10000; 37 | intent.Call("addFlags", Intent_FLAG_ACTIVITY_NO_ANIMATION); 38 | intent.Call("putExtra", "unityplayer.Activity", 39 | currentActivity.Call("getClass").Call("getName")); 40 | try 41 | { 42 | currentActivity.Call("startActivity", intent); 43 | } 44 | catch (Exception ex) 45 | { 46 | Debug.LogError("GooglePlayObbDownloader: Exception occurred while attempting to start DownloaderActivity - is the AndroidManifest.xml incorrect?\n" + ex.Message); 47 | } 48 | } 49 | } 50 | 51 | private string m_ExpansionFilePath; 52 | public string GetExpansionFilePath() 53 | { 54 | if (EnvironmentClass.CallStatic("getExternalStorageState") != Environment_MediaMounted) 55 | { 56 | m_ExpansionFilePath = null; 57 | return m_ExpansionFilePath; 58 | } 59 | 60 | if (string.IsNullOrEmpty(m_ExpansionFilePath)) 61 | { 62 | const string obbPath = "Android/obb"; 63 | using (var externalStorageDirectory = EnvironmentClass.CallStatic("getExternalStorageDirectory")) 64 | { 65 | var externalRoot = externalStorageDirectory.Call("getPath"); 66 | m_ExpansionFilePath = string.Format("{0}/{1}/{2}", externalRoot, obbPath, ObbPackage); 67 | } 68 | } 69 | return m_ExpansionFilePath; 70 | } 71 | 72 | public string GetMainOBBPath() 73 | { 74 | return GetOBBPackagePath(GetExpansionFilePath(), "main"); 75 | } 76 | 77 | public string GetPatchOBBPath() 78 | { 79 | return GetOBBPackagePath(GetExpansionFilePath(), "patch"); 80 | } 81 | 82 | private static string GetOBBPackagePath(string expansionFilePath, string prefix) 83 | { 84 | if (string.IsNullOrEmpty(expansionFilePath)) 85 | return null; 86 | 87 | var filePath = string.Format("{0}/{1}.{2}.{3}.obb", expansionFilePath, prefix, ObbVersion, ObbPackage); 88 | return File.Exists(filePath) ? filePath : null; 89 | } 90 | 91 | private static string m_ObbPackage; 92 | private static string ObbPackage 93 | { 94 | get 95 | { 96 | if (m_ObbPackage == null) 97 | { 98 | PopulateOBBProperties(); 99 | } 100 | return m_ObbPackage; 101 | } 102 | } 103 | 104 | private static int m_ObbVersion; 105 | private static int ObbVersion 106 | { 107 | get 108 | { 109 | if (m_ObbVersion == 0) 110 | { 111 | PopulateOBBProperties(); 112 | } 113 | return m_ObbVersion; 114 | } 115 | } 116 | 117 | // This code will reuse the package version from the .apk when looking for the .obb 118 | // Modify as appropriate 119 | private static void PopulateOBBProperties() 120 | { 121 | using (var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) 122 | { 123 | var currentActivity = unityPlayerClass.GetStatic("currentActivity"); 124 | m_ObbPackage = currentActivity.Call("getPackageName"); 125 | var packageInfo = currentActivity.Call("getPackageManager").Call("getPackageInfo", m_ObbPackage, 0); 126 | m_ObbVersion = packageInfo.Get("versionCode"); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 3.0 (2017-09-22) 2 | 3 | * Scripts rewritten, with IGooglePlayObbDownloader interface extracted 4 | * Sample updated to match the new API 5 | 6 | 7 | Version 2.0 (2017-08-25) 8 | 9 | * Updated the plugin to be an AAR 10 | * Solved Android M compatibility issues (however min API level is now 11) 11 | * Updated project structure and documentation 12 | 13 | 14 | Version 1.0 15 | 16 | * Initial version -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2015-2016 Yury Habets 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityOBBDownloader 2 | This package is an adaption of the Google Play market_downloader library, for use with Unity Android (as a plugin). 3 | 4 | This plugin does NOT solve splitting up a >100MB .apk into .obb (through asset bundles or similar techniques). 5 | It merely handles the downloading of .obb files attached to a published .apk, on devices that don't support automatic downloading. 6 | 7 | This software is free and published as is, with no warranty and responsibilities - use it at your own risk. 8 | 9 | ## Usage 10 | This plugin is published as an AAR, and is compatible with Unity 5+. Minimum Android version supported is Honeycomb (API level 11). For a version compatible with Unity 4 or with older Android devices, please checkout changeset tagged `1.0`. 11 | The C# API of the plugin is available in `IGooglePlayObbDownloader` interface. Use `GooglePlayObbDownloadManager.GetGooglePlayObbDownloader()` to obtain an instance. 12 | 13 | 0. Add the plugin to your project. You need the AAR and the C# scripts (`Assets/Plugins/Android/unityOBBDownloader.aar`, `Assets/Scripts/GooglePlayDownloader.cs` and `Assets/Scripts/GooglePlayDownloaderImpl.cs`) 14 | 1. In your script, don't forget to set `PublicKey` to your own value. You'll see a message logged and probably a crash if you skip this step 15 | 2. Change the Bundle Identifier / Version Code so it matches the application already available on Google Play (that has .obb files attached) 16 | 3. Build and Run on your Android device 17 | 18 | For a script sample, please refer to `Assets/Scripts/DownloadObbExample.cs`. 19 | 20 | ## How to Build 21 | Run `gradlew assemble` from src/UnityAndroidPermissions/ 22 | 23 | ## License 24 | Copyright (C) 2016-2017 Yury Habets 25 | 26 | Licensed under the Apache License, Version 2.0 (the "License"); 27 | you may not use this file except in compliance with the License. 28 | You may obtain a copy of the License at 29 | 30 | http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | Unless required by applicable law or agreed to in writing, software 33 | distributed under the License is distributed on an "AS IS" BASIS, 34 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35 | See the License for the specific language governing permissions and 36 | limitations under the License. 37 | 38 | ## See also 39 | For more information on using .obb files in Unity, please refer to http://docs.unity3d.com/Manual/android-OBBsupport.html and http://developer.android.com/guide/market/expansion-files.html 40 | 41 | For more information on Unity Android plugins, please refer to http://docs.unity3d.com/Manual/PluginsForAndroid.html 42 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | google() 7 | } 8 | 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.0.1' 11 | } 12 | } 13 | 14 | android { 15 | compileSdkVersion 26 16 | buildToolsVersion "26.0.2" 17 | 18 | defaultConfig { 19 | minSdkVersion 11 20 | targetSdkVersion 26 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled true 26 | proguardFiles 'proguard.cfg' 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Over17/UnityOBBDownloader/d77e66f64c2738b2642f39c5ac91bc31ad464f12/src/unityOBBDownloader/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/unityOBBDownloader/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip 6 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends com.google.android.vending.expansion.downloader.impl.DownloaderService 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | 16 | -keepclasseswithmembernames class * { 17 | native ; 18 | } 19 | 20 | -keepclasseswithmembers class * { 21 | public (android.content.Context, android.util.AttributeSet); 22 | } 23 | 24 | -keepclasseswithmembers class * { 25 | public (android.content.Context, android.util.AttributeSet, int); 26 | } 27 | 28 | -keepclassmembers class * extends android.app.Activity { 29 | public void *(android.view.View); 30 | } 31 | 32 | -keepclassmembers enum * { 33 | public static **[] values(); 34 | public static ** valueOf(java.lang.String); 35 | } 36 | 37 | -keepclassmembers class * implements android.os.Parcelable { 38 | public static final android.os.Parcelable$Creator *; 39 | } 40 | 41 | # remove obfuscation on critical parts of the google play downloader 42 | -keepclassmembers class * extends android.app.Service { ; } 43 | -flattenpackagehierarchy com.unity3d.plugin.downloader 44 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/aidl/com/android/vending/licensing/ILicenseResultListener.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.vending.licensing; 18 | 19 | oneway interface ILicenseResultListener { 20 | void verifyLicense(int responseCode, String signedData, String signature); 21 | } 22 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/aidl/com/android/vending/licensing/ILicensingService.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.android.vending.licensing; 18 | 19 | import com.android.vending.licensing.ILicenseResultListener; 20 | 21 | oneway interface ILicensingService { 22 | void checkLicense(long nonce, String packageName, in ILicenseResultListener listener); 23 | } 24 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import java.io.File; 20 | 21 | 22 | /** 23 | * Contains the internal constants that are used in the download manager. 24 | * As a general rule, modifying these constants should be done with care. 25 | */ 26 | public class Constants { 27 | /** Tag used for debugging/logging */ 28 | public static final String TAG = "LVLDL"; 29 | 30 | /** 31 | * Expansion path where we store obb files 32 | */ 33 | public static final String EXP_PATH = File.separator + "Android" 34 | + File.separator + "obb" + File.separator; 35 | 36 | /** The intent that gets sent when the service must wake up for a retry */ 37 | public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP"; 38 | 39 | /** the intent that gets sent when clicking a successful download */ 40 | public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN"; 41 | 42 | /** the intent that gets sent when clicking an incomplete/failed download */ 43 | public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST"; 44 | 45 | /** the intent that gets sent when deleting the notification of a completed download */ 46 | public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE"; 47 | 48 | /** 49 | * When a number has to be appended to the filename, this string is used to separate the 50 | * base filename from the sequence number 51 | */ 52 | public static final String FILENAME_SEQUENCE_SEPARATOR = "-"; 53 | 54 | /** The default user agent used for downloads */ 55 | public static final String DEFAULT_USER_AGENT = "Android.LVLDM"; 56 | 57 | /** The buffer size used to stream the data */ 58 | public static final int BUFFER_SIZE = 4096; 59 | 60 | /** The minimum amount of progress that has to be done before the progress bar gets updated */ 61 | public static final int MIN_PROGRESS_STEP = 4096; 62 | 63 | /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */ 64 | public static final long MIN_PROGRESS_TIME = 1000; 65 | 66 | /** The maximum number of rows in the database (FIFO) */ 67 | public static final int MAX_DOWNLOADS = 1000; 68 | 69 | /** 70 | * The number of times that the download manager will retry its network 71 | * operations when no progress is happening before it gives up. 72 | */ 73 | public static final int MAX_RETRIES = 5; 74 | 75 | /** 76 | * The minimum amount of time that the download manager accepts for 77 | * a Retry-After response header with a parameter in delta-seconds. 78 | */ 79 | public static final int MIN_RETRY_AFTER = 30; // 30s 80 | 81 | /** 82 | * The maximum amount of time that the download manager accepts for 83 | * a Retry-After response header with a parameter in delta-seconds. 84 | */ 85 | public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h 86 | 87 | /** 88 | * The maximum number of redirects. 89 | */ 90 | public static final int MAX_REDIRECTS = 5; // can't be more than 7. 91 | 92 | /** 93 | * The time between a failure and the first retry after an IOException. 94 | * Each subsequent retry grows exponentially, doubling each time. 95 | * The time is in seconds. 96 | */ 97 | public static final int RETRY_FIRST_DELAY = 30; 98 | 99 | /** Enable separate connectivity logging */ 100 | public static final boolean LOGX = true; 101 | 102 | /** Enable verbose logging */ 103 | public static final boolean LOGV = false; 104 | 105 | /** Enable super-verbose logging */ 106 | private static final boolean LOCAL_LOGVV = false; 107 | public static final boolean LOGVV = LOCAL_LOGVV && LOGV; 108 | 109 | /** 110 | * This download has successfully completed. 111 | * Warning: there might be other status values that indicate success 112 | * in the future. 113 | * Use isSucccess() to capture the entire category. 114 | */ 115 | public static final int STATUS_SUCCESS = 200; 116 | 117 | /** 118 | * This request couldn't be parsed. This is also used when processing 119 | * requests with unknown/unsupported URI schemes. 120 | */ 121 | public static final int STATUS_BAD_REQUEST = 400; 122 | 123 | /** 124 | * This download can't be performed because the content type cannot be 125 | * handled. 126 | */ 127 | public static final int STATUS_NOT_ACCEPTABLE = 406; 128 | 129 | /** 130 | * This download cannot be performed because the length cannot be 131 | * determined accurately. This is the code for the HTTP error "Length 132 | * Required", which is typically used when making requests that require 133 | * a content length but don't have one, and it is also used in the 134 | * client when a response is received whose length cannot be determined 135 | * accurately (therefore making it impossible to know when a download 136 | * completes). 137 | */ 138 | public static final int STATUS_LENGTH_REQUIRED = 411; 139 | 140 | /** 141 | * This download was interrupted and cannot be resumed. 142 | * This is the code for the HTTP error "Precondition Failed", and it is 143 | * also used in situations where the client doesn't have an ETag at all. 144 | */ 145 | public static final int STATUS_PRECONDITION_FAILED = 412; 146 | 147 | /** 148 | * The lowest-valued error status that is not an actual HTTP status code. 149 | */ 150 | public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488; 151 | 152 | /** 153 | * The requested destination file already exists. 154 | */ 155 | public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488; 156 | 157 | /** 158 | * Some possibly transient error occurred, but we can't resume the download. 159 | */ 160 | public static final int STATUS_CANNOT_RESUME = 489; 161 | 162 | /** 163 | * This download was canceled 164 | */ 165 | public static final int STATUS_CANCELED = 490; 166 | 167 | /** 168 | * This download has completed with an error. 169 | * Warning: there will be other status values that indicate errors in 170 | * the future. Use isStatusError() to capture the entire category. 171 | */ 172 | public static final int STATUS_UNKNOWN_ERROR = 491; 173 | 174 | /** 175 | * This download couldn't be completed because of a storage issue. 176 | * Typically, that's because the filesystem is missing or full. 177 | * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} 178 | * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate. 179 | */ 180 | public static final int STATUS_FILE_ERROR = 492; 181 | 182 | /** 183 | * This download couldn't be completed because of an HTTP 184 | * redirect response that the download manager couldn't 185 | * handle. 186 | */ 187 | public static final int STATUS_UNHANDLED_REDIRECT = 493; 188 | 189 | /** 190 | * This download couldn't be completed because of an 191 | * unspecified unhandled HTTP code. 192 | */ 193 | public static final int STATUS_UNHANDLED_HTTP_CODE = 494; 194 | 195 | /** 196 | * This download couldn't be completed because of an 197 | * error receiving or processing data at the HTTP level. 198 | */ 199 | public static final int STATUS_HTTP_DATA_ERROR = 495; 200 | 201 | /** 202 | * This download couldn't be completed because of an 203 | * HttpException while setting up the request. 204 | */ 205 | public static final int STATUS_HTTP_EXCEPTION = 496; 206 | 207 | /** 208 | * This download couldn't be completed because there were 209 | * too many redirects. 210 | */ 211 | public static final int STATUS_TOO_MANY_REDIRECTS = 497; 212 | 213 | /** 214 | * This download couldn't be completed due to insufficient storage 215 | * space. Typically, this is because the SD card is full. 216 | */ 217 | public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; 218 | 219 | /** 220 | * This download couldn't be completed because no external storage 221 | * device was found. Typically, this is because the SD card is not 222 | * mounted. 223 | */ 224 | public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; 225 | 226 | /** 227 | * The wake duration to check to see if a download is possible. 228 | */ 229 | public static final long WATCHDOG_WAKE_TIMER = 60*1000; 230 | 231 | /** 232 | * The wake duration to check to see if the process was killed. 233 | */ 234 | public static final long ACTIVE_THREAD_WATCHDOG = 5*1000; 235 | 236 | } -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.os.Parcel; 20 | import android.os.Parcelable; 21 | 22 | 23 | /** 24 | * This class contains progress information about the active download(s). 25 | * 26 | * When you build the Activity that initiates a download and tracks the 27 | * progress by implementing the {@link IDownloaderClient} interface, you'll 28 | * receive a DownloadProgressInfo object in each call to the {@link 29 | * IDownloaderClient#onDownloadProgress} method. This allows you to update 30 | * your activity's UI with information about the download progress, such 31 | * as the progress so far, time remaining and current speed. 32 | */ 33 | public class DownloadProgressInfo implements Parcelable { 34 | public long mOverallTotal; 35 | public long mOverallProgress; 36 | public long mTimeRemaining; // time remaining 37 | public float mCurrentSpeed; // speed in KB/S 38 | 39 | @Override 40 | public int describeContents() { 41 | return 0; 42 | } 43 | 44 | @Override 45 | public void writeToParcel(Parcel p, int i) { 46 | p.writeLong(mOverallTotal); 47 | p.writeLong(mOverallProgress); 48 | p.writeLong(mTimeRemaining); 49 | p.writeFloat(mCurrentSpeed); 50 | } 51 | 52 | public DownloadProgressInfo(Parcel p) { 53 | mOverallTotal = p.readLong(); 54 | mOverallProgress = p.readLong(); 55 | mTimeRemaining = p.readLong(); 56 | mCurrentSpeed = p.readFloat(); 57 | } 58 | 59 | public DownloadProgressInfo(long overallTotal, long overallProgress, 60 | long timeRemaining, 61 | float currentSpeed) { 62 | this.mOverallTotal = overallTotal; 63 | this.mOverallProgress = overallProgress; 64 | this.mTimeRemaining = timeRemaining; 65 | this.mCurrentSpeed = currentSpeed; 66 | } 67 | 68 | public static final Creator CREATOR = new Creator() { 69 | @Override 70 | public DownloadProgressInfo createFromParcel(Parcel parcel) { 71 | return new DownloadProgressInfo(parcel); 72 | } 73 | 74 | @Override 75 | public DownloadProgressInfo[] newArray(int i) { 76 | return new DownloadProgressInfo[i]; 77 | } 78 | }; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService; 20 | 21 | import android.app.PendingIntent; 22 | import android.content.ComponentName; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.ServiceConnection; 26 | import android.content.pm.PackageManager.NameNotFoundException; 27 | import android.os.Bundle; 28 | import android.os.Handler; 29 | import android.os.IBinder; 30 | import android.os.Message; 31 | import android.os.Messenger; 32 | import android.os.RemoteException; 33 | import android.util.Log; 34 | 35 | 36 | 37 | /** 38 | * This class binds the service API to your application client. It contains the IDownloaderClient proxy, 39 | * which is used to call functions in your client as well as the Stub, which is used to call functions 40 | * in the client implementation of IDownloaderClient. 41 | * 42 | *

The IPC is implemented using an Android Messenger and a service Binder. The connect method 43 | * should be called whenever the client wants to bind to the service. It opens up a service connection 44 | * that ends up calling the onServiceConnected client API that passes the service messenger 45 | * in. If the client wants to be notified by the service, it is responsible for then passing its 46 | * messenger to the service in a separate call. 47 | * 48 | *

Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}. 49 | * 50 | *

When your application first starts, you should first check whether your app's expansion files are 51 | * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which 52 | * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method 53 | * returns a value indicating whether download is required or not. 54 | * 55 | *

If a download is required, {@link #startDownloadServiceIfRequired} begins the download through 56 | * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link 57 | * IStub} object that you need in order to receive calls through your {@link IDownloaderClient} 58 | * interface. 59 | */ 60 | public class DownloaderClientMarshaller { 61 | public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10; 62 | public static final int MSG_ONDOWNLOADPROGRESS = 11; 63 | public static final int MSG_ONSERVICECONNECTED = 12; 64 | 65 | public static final String PARAM_NEW_STATE = "newState"; 66 | public static final String PARAM_PROGRESS = "progress"; 67 | public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; 68 | 69 | public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED; 70 | public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED; 71 | public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED; 72 | 73 | private static class Proxy implements IDownloaderClient { 74 | private Messenger mServiceMessenger; 75 | 76 | @Override 77 | public void onDownloadStateChanged(int newState) { 78 | Bundle params = new Bundle(1); 79 | params.putInt(PARAM_NEW_STATE, newState); 80 | send(MSG_ONDOWNLOADSTATE_CHANGED, params); 81 | } 82 | 83 | @Override 84 | public void onDownloadProgress(DownloadProgressInfo progress) { 85 | Bundle params = new Bundle(1); 86 | params.putParcelable(PARAM_PROGRESS, progress); 87 | send(MSG_ONDOWNLOADPROGRESS, params); 88 | } 89 | 90 | private void send(int method, Bundle params) { 91 | Message m = Message.obtain(null, method); 92 | m.setData(params); 93 | try { 94 | mServiceMessenger.send(m); 95 | } catch (RemoteException e) { 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | public Proxy(Messenger msg) { 101 | mServiceMessenger = msg; 102 | } 103 | 104 | @Override 105 | public void onServiceConnected(Messenger m) { 106 | /** 107 | * This is never called through the proxy. 108 | */ 109 | } 110 | } 111 | 112 | private static class Stub implements IStub { 113 | private IDownloaderClient mItf = null; 114 | private Class mDownloaderServiceClass; 115 | private boolean mBound; 116 | private Messenger mServiceMessenger; 117 | private Context mContext; 118 | /** 119 | * Target we publish for clients to send messages to IncomingHandler. 120 | */ 121 | final Messenger mMessenger = new Messenger(new Handler() { 122 | @Override 123 | public void handleMessage(Message msg) { 124 | switch (msg.what) { 125 | case MSG_ONDOWNLOADPROGRESS: 126 | Bundle bun = msg.getData(); 127 | if ( null != mContext ) { 128 | bun.setClassLoader(mContext.getClassLoader()); 129 | DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData() 130 | .getParcelable(PARAM_PROGRESS); 131 | mItf.onDownloadProgress(dpi); 132 | } 133 | break; 134 | case MSG_ONDOWNLOADSTATE_CHANGED: 135 | mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE)); 136 | break; 137 | case MSG_ONSERVICECONNECTED: 138 | mItf.onServiceConnected( 139 | (Messenger) msg.getData().getParcelable(PARAM_MESSENGER)); 140 | break; 141 | } 142 | } 143 | }); 144 | 145 | public Stub(IDownloaderClient itf, Class downloaderService) { 146 | mItf = itf; 147 | mDownloaderServiceClass = downloaderService; 148 | } 149 | 150 | /** 151 | * Class for interacting with the main interface of the service. 152 | */ 153 | private ServiceConnection mConnection = new ServiceConnection() { 154 | public void onServiceConnected(ComponentName className, IBinder service) { 155 | // This is called when the connection with the service has been 156 | // established, giving us the object we can use to 157 | // interact with the service. We are communicating with the 158 | // service using a Messenger, so here we get a client-side 159 | // representation of that from the raw IBinder object. 160 | mServiceMessenger = new Messenger(service); 161 | mItf.onServiceConnected( 162 | mServiceMessenger); 163 | } 164 | 165 | public void onServiceDisconnected(ComponentName className) { 166 | // This is called when the connection with the service has been 167 | // unexpectedly disconnected -- that is, its process crashed. 168 | mServiceMessenger = null; 169 | } 170 | }; 171 | 172 | @Override 173 | public void connect(Context c) { 174 | mContext = c; 175 | Intent bindIntent = new Intent(c, mDownloaderServiceClass); 176 | bindIntent.putExtra(PARAM_MESSENGER, mMessenger); 177 | if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) { 178 | if ( Constants.LOGVV ) { 179 | Log.d(Constants.TAG, "Service Unbound"); 180 | } 181 | } else { 182 | mBound = true; 183 | } 184 | 185 | } 186 | 187 | @Override 188 | public void disconnect(Context c) { 189 | if (mBound) { 190 | c.unbindService(mConnection); 191 | mBound = false; 192 | } 193 | mContext = null; 194 | } 195 | 196 | @Override 197 | public Messenger getMessenger() { 198 | return mMessenger; 199 | } 200 | } 201 | 202 | /** 203 | * Returns a proxy that will marshal calls to IDownloaderClient methods 204 | * 205 | * @param msg 206 | * @return 207 | */ 208 | public static IDownloaderClient CreateProxy(Messenger msg) { 209 | return new Proxy(msg); 210 | } 211 | 212 | /** 213 | * Returns a stub object that, when connected, will listen for marshaled 214 | * {@link IDownloaderClient} methods and translate them into calls to the supplied 215 | * interface. 216 | * 217 | * @param itf An implementation of IDownloaderClient that will be called 218 | * when remote method calls are unmarshaled. 219 | * @param downloaderService The class for your implementation of {@link 220 | * impl.DownloaderService}. 221 | * @return The {@link IStub} that allows you to connect to the service such that 222 | * your {@link IDownloaderClient} receives status updates. 223 | */ 224 | public static IStub CreateStub(IDownloaderClient itf, Class downloaderService) { 225 | return new Stub(itf, downloaderService); 226 | } 227 | 228 | /** 229 | * Starts the download if necessary. This function starts a flow that does ` 230 | * many things. 1) Checks to see if the APK version has been checked and 231 | * the metadata database updated 2) If the APK version does not match, 232 | * checks the new LVL status to see if a new download is required 3) If the 233 | * APK version does match, then checks to see if the download(s) have been 234 | * completed 4) If the downloads have been completed, returns 235 | * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the 236 | * startup of an application to quickly ascertain if the application needs 237 | * to wait to hear about any updated APK expansion files. Note that this does 238 | * mean that the application MUST be run for the first time with a network 239 | * connection, even if Market delivers all of the files. 240 | * 241 | * @param context Your application Context. 242 | * @param notificationClient A PendingIntent to start the Activity in your application 243 | * that shows the download progress and which will also start the application when download 244 | * completes. 245 | * @param serviceClass the class of your {@link imp.DownloaderService} implementation 246 | * @return whether the service was started and the reason for starting the service. 247 | * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link 248 | * #DOWNLOAD_REQUIRED}. 249 | * @throws NameNotFoundException 250 | */ 251 | public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, 252 | Class serviceClass) 253 | throws NameNotFoundException { 254 | return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, 255 | serviceClass); 256 | } 257 | 258 | /** 259 | * This version assumes that the intent contains the pending intent as a parameter. This 260 | * is used for responding to alarms. 261 | *

The pending intent must be in an extra with the key {@link 262 | * impl.DownloaderService#EXTRA_PENDING_INTENT}. 263 | * 264 | * @param context 265 | * @param notificationClient 266 | * @param serviceClass the class of the service to start 267 | * @return 268 | * @throws NameNotFoundException 269 | */ 270 | public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, 271 | Class serviceClass) 272 | throws NameNotFoundException { 273 | return DownloaderService.startDownloadServiceIfRequired(context, notificationClient, 274 | serviceClass); 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService; 20 | 21 | import android.content.Context; 22 | import android.os.Bundle; 23 | import android.os.Handler; 24 | import android.os.Message; 25 | import android.os.Messenger; 26 | import android.os.RemoteException; 27 | 28 | 29 | 30 | /** 31 | * This class is used by the client activity to proxy requests to the Downloader 32 | * Service. 33 | * 34 | * Most importantly, you must call {@link #CreateProxy} during the {@link 35 | * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate 36 | * an {@link IDownloaderService} object that you can then use to issue commands to the {@link 37 | * DownloaderService} (such as to pause and resume downloads). 38 | */ 39 | public class DownloaderServiceMarshaller { 40 | 41 | public static final int MSG_REQUEST_ABORT_DOWNLOAD = 42 | 1; 43 | public static final int MSG_REQUEST_PAUSE_DOWNLOAD = 44 | 2; 45 | public static final int MSG_SET_DOWNLOAD_FLAGS = 46 | 3; 47 | public static final int MSG_REQUEST_CONTINUE_DOWNLOAD = 48 | 4; 49 | public static final int MSG_REQUEST_DOWNLOAD_STATE = 50 | 5; 51 | public static final int MSG_REQUEST_CLIENT_UPDATE = 52 | 6; 53 | 54 | public static final String PARAMS_FLAGS = "flags"; 55 | public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER; 56 | 57 | private static class Proxy implements IDownloaderService { 58 | private Messenger mMsg; 59 | 60 | private void send(int method, Bundle params) { 61 | Message m = Message.obtain(null, method); 62 | m.setData(params); 63 | try { 64 | mMsg.send(m); 65 | } catch (RemoteException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | 70 | public Proxy(Messenger msg) { 71 | mMsg = msg; 72 | } 73 | 74 | @Override 75 | public void requestAbortDownload() { 76 | send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle()); 77 | } 78 | 79 | @Override 80 | public void requestPauseDownload() { 81 | send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle()); 82 | } 83 | 84 | @Override 85 | public void setDownloadFlags(int flags) { 86 | Bundle params = new Bundle(); 87 | params.putInt(PARAMS_FLAGS, flags); 88 | send(MSG_SET_DOWNLOAD_FLAGS, params); 89 | } 90 | 91 | @Override 92 | public void requestContinueDownload() { 93 | send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle()); 94 | } 95 | 96 | @Override 97 | public void requestDownloadStatus() { 98 | send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle()); 99 | } 100 | 101 | @Override 102 | public void onClientUpdated(Messenger clientMessenger) { 103 | Bundle bundle = new Bundle(1); 104 | bundle.putParcelable(PARAM_MESSENGER, clientMessenger); 105 | send(MSG_REQUEST_CLIENT_UPDATE, bundle); 106 | } 107 | } 108 | 109 | private static class Stub implements IStub { 110 | private IDownloaderService mItf = null; 111 | final Messenger mMessenger = new Messenger(new Handler() { 112 | @Override 113 | public void handleMessage(Message msg) { 114 | switch (msg.what) { 115 | case MSG_REQUEST_ABORT_DOWNLOAD: 116 | mItf.requestAbortDownload(); 117 | break; 118 | case MSG_REQUEST_CONTINUE_DOWNLOAD: 119 | mItf.requestContinueDownload(); 120 | break; 121 | case MSG_REQUEST_PAUSE_DOWNLOAD: 122 | mItf.requestPauseDownload(); 123 | break; 124 | case MSG_SET_DOWNLOAD_FLAGS: 125 | mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS)); 126 | break; 127 | case MSG_REQUEST_DOWNLOAD_STATE: 128 | mItf.requestDownloadStatus(); 129 | break; 130 | case MSG_REQUEST_CLIENT_UPDATE: 131 | mItf.onClientUpdated((Messenger) msg.getData().getParcelable( 132 | PARAM_MESSENGER)); 133 | break; 134 | } 135 | } 136 | }); 137 | 138 | public Stub(IDownloaderService itf) { 139 | mItf = itf; 140 | } 141 | 142 | @Override 143 | public Messenger getMessenger() { 144 | return mMessenger; 145 | } 146 | 147 | @Override 148 | public void connect(Context c) { 149 | 150 | } 151 | 152 | @Override 153 | public void disconnect(Context c) { 154 | 155 | } 156 | } 157 | 158 | /** 159 | * Returns a proxy that will marshall calls to IDownloaderService methods 160 | * 161 | * @param ctx 162 | * @return 163 | */ 164 | public static IDownloaderService CreateProxy(Messenger msg) { 165 | return new Proxy(msg); 166 | } 167 | 168 | /** 169 | * Returns a stub object that, when connected, will listen for marshalled 170 | * IDownloaderService methods and translate them into calls to the supplied 171 | * interface. 172 | * 173 | * @param itf An implementation of IDownloaderService that will be called 174 | * when remote method calls are unmarshalled. 175 | * @return 176 | */ 177 | public static IStub CreateStub(IDownloaderService itf) { 178 | return new Stub(itf); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Helpers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.annotation.TargetApi; 20 | import android.content.Context; 21 | import android.os.Build; 22 | import android.os.Environment; 23 | import android.os.StatFs; 24 | import android.os.SystemClock; 25 | import android.util.Log; 26 | 27 | //import com.android.vending.expansion.downloader.R; 28 | 29 | import java.io.File; 30 | import java.text.SimpleDateFormat; 31 | import java.util.Date; 32 | import java.util.Locale; 33 | import java.util.Random; 34 | import java.util.TimeZone; 35 | import java.util.regex.Matcher; 36 | import java.util.regex.Pattern; 37 | 38 | /** 39 | * Some helper functions for the download manager 40 | */ 41 | public class Helpers { 42 | 43 | public static Random sRandom = new Random(SystemClock.uptimeMillis()); 44 | 45 | /** Regex used to parse content-disposition headers */ 46 | private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern 47 | .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\""); 48 | 49 | private Helpers() { 50 | } 51 | 52 | /* 53 | * Parse the Content-Disposition HTTP Header. The format of the header is defined here: 54 | * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for 55 | * content that is going to be downloaded to the file system. We only support the attachment 56 | * type. 57 | */ 58 | static String parseContentDisposition(String contentDisposition) { 59 | try { 60 | Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); 61 | if (m.find()) { 62 | return m.group(1); 63 | } 64 | } catch (IllegalStateException ex) { 65 | // This function is defined as returning null when it can't parse 66 | // the header 67 | } 68 | return null; 69 | } 70 | 71 | /** 72 | * @return the root of the filesystem containing the given path 73 | */ 74 | public static File getFilesystemRoot(String path) { 75 | File cache = Environment.getDownloadCacheDirectory(); 76 | if (path.startsWith(cache.getPath())) { 77 | return cache; 78 | } 79 | File external = Environment.getExternalStorageDirectory(); 80 | if (path.startsWith(external.getPath())) { 81 | return external; 82 | } 83 | throw new IllegalArgumentException( 84 | "Cannot determine filesystem root for " + path); 85 | } 86 | 87 | public static boolean isExternalMediaMounted() { 88 | if (!Environment.getExternalStorageState().equals( 89 | Environment.MEDIA_MOUNTED)) { 90 | // No SD card found. 91 | if (Constants.LOGVV) { 92 | Log.d(Constants.TAG, "no external storage"); 93 | } 94 | return false; 95 | } 96 | return true; 97 | } 98 | 99 | /** 100 | * @return the number of bytes available on the filesystem rooted at the given File 101 | */ 102 | public static long getAvailableBytes(File root) { 103 | StatFs stat = new StatFs(root.getPath()); 104 | // put a bit of margin (in case creating the file grows the system by a 105 | // few blocks) 106 | long availableBlocks = (long) stat.getAvailableBlocks() - 4; 107 | return stat.getBlockSize() * availableBlocks; 108 | } 109 | 110 | /** 111 | * Checks whether the filename looks legitimate 112 | */ 113 | public static boolean isFilenameValid(String filename) { 114 | filename = filename.replaceFirst("/+", "/"); // normalize leading 115 | // slashes 116 | return filename.startsWith(Environment.getDownloadCacheDirectory().toString()) 117 | || filename.startsWith(Environment.getExternalStorageDirectory().toString()); 118 | } 119 | 120 | /* 121 | * Delete the given file from device 122 | */ 123 | /* package */static void deleteFile(String path) { 124 | try { 125 | File file = new File(path); 126 | file.delete(); 127 | } catch (Exception e) { 128 | Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e); 129 | } 130 | } 131 | 132 | /** 133 | * Showing progress in MB here. It would be nice to choose the unit (KB, MB, GB) based on total 134 | * file size, but given what we know about the expected ranges of file sizes for APK expansion 135 | * files, it's probably not necessary. 136 | * 137 | * @param overallProgress 138 | * @param overallTotal 139 | * @return 140 | */ 141 | 142 | static public String getDownloadProgressString(long overallProgress, long overallTotal) { 143 | if (overallTotal == 0) { 144 | if (Constants.LOGVV) { 145 | Log.e(Constants.TAG, "Notification called when total is zero"); 146 | } 147 | return ""; 148 | } 149 | return String.format("%.2f", 150 | (float) overallProgress / (1024.0f * 1024.0f)) 151 | + "MB /" + 152 | String.format("%.2f", (float) overallTotal / 153 | (1024.0f * 1024.0f)) 154 | + "MB"; 155 | } 156 | 157 | /** 158 | * Adds a percentile to getDownloadProgressString. 159 | * 160 | * @param overallProgress 161 | * @param overallTotal 162 | * @return 163 | */ 164 | static public String getDownloadProgressStringNotification(long overallProgress, 165 | long overallTotal) { 166 | if (overallTotal == 0) { 167 | if (Constants.LOGVV) { 168 | Log.e(Constants.TAG, "Notification called when total is zero"); 169 | } 170 | return ""; 171 | } 172 | return getDownloadProgressString(overallProgress, overallTotal) + " (" + 173 | getDownloadProgressPercent(overallProgress, overallTotal) + ")"; 174 | } 175 | 176 | public static String getDownloadProgressPercent(long overallProgress, long overallTotal) { 177 | if (overallTotal == 0) { 178 | if (Constants.LOGVV) { 179 | Log.e(Constants.TAG, "Notification called when total is zero"); 180 | } 181 | return ""; 182 | } 183 | return Long.toString(overallProgress * 100 / overallTotal) + "%"; 184 | } 185 | 186 | public static String getSpeedString(float bytesPerMillisecond) { 187 | return String.format("%.2f", bytesPerMillisecond * 1000 / 1024); 188 | } 189 | 190 | public static String getTimeRemaining(long durationInMilliseconds) { 191 | SimpleDateFormat sdf; 192 | if (durationInMilliseconds > 1000 * 60 * 60) { 193 | sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); 194 | } else { 195 | sdf = new SimpleDateFormat("mm:ss", Locale.getDefault()); 196 | } 197 | return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset())); 198 | } 199 | 200 | /** 201 | * Returns the file name (without full path) for an Expansion APK file from the given context. 202 | * 203 | * @param c the context 204 | * @param mainFile true for main file, false for patch file 205 | * @param versionCode the version of the file 206 | * @return String the file name of the expansion file 207 | */ 208 | public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) { 209 | return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb"; 210 | } 211 | 212 | /** 213 | * Returns the filename (where the file should be saved) from info about a download 214 | */ 215 | static public String generateSaveFileName(Context c, String fileName) { 216 | String path = getSaveFilePath(c) 217 | + File.separator + fileName; 218 | return path; 219 | } 220 | 221 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 222 | static public String getSaveFilePath(Context c) { 223 | // This technically existed since Honeycomb, but it is critical 224 | // on KitKat and greater versions since it will create the 225 | // directory if needed 226 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 227 | return c.getObbDir().toString(); 228 | } else { 229 | File root = Environment.getExternalStorageDirectory(); 230 | String path = root.toString() + Constants.EXP_PATH + c.getPackageName(); 231 | return path; 232 | } 233 | } 234 | 235 | /** 236 | * Helper function to ascertain the existence of a file and return true/false appropriately 237 | * 238 | * @param c the app/activity/service context 239 | * @param fileName the name (sans path) of the file to query 240 | * @param fileSize the size that the file must match 241 | * @param deleteFileOnMismatch if the file sizes do not match, delete the file 242 | * @return true if it does exist, false otherwise 243 | */ 244 | static public boolean doesFileExist(Context c, String fileName, long fileSize, 245 | boolean deleteFileOnMismatch) { 246 | // the file may have been delivered by Play --- let's make sure 247 | // it's the size we expect 248 | File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); 249 | if (fileForNewFile.exists()) { 250 | if (fileForNewFile.length() == fileSize) { 251 | return true; 252 | } 253 | if (deleteFileOnMismatch) { 254 | // delete the file --- we won't be able to resume 255 | // because we cannot confirm the integrity of the file 256 | fileForNewFile.delete(); 257 | } 258 | } 259 | return false; 260 | } 261 | 262 | public static final int FS_READABLE = 0; 263 | public static final int FS_DOES_NOT_EXIST = 1; 264 | public static final int FS_CANNOT_READ = 2; 265 | 266 | /** 267 | * Helper function to ascertain whether a file can be read. 268 | * 269 | * @param c the app/activity/service context 270 | * @param fileName the name (sans path) of the file to query 271 | * @return true if it does exist, false otherwise 272 | */ 273 | static public int getFileStatus(Context c, String fileName) { 274 | // the file may have been delivered by Play --- let's make sure 275 | // it's the size we expect 276 | File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName)); 277 | int returnValue; 278 | if (fileForNewFile.exists()) { 279 | if (fileForNewFile.canRead()) { 280 | returnValue = FS_READABLE; 281 | } else { 282 | returnValue = FS_CANNOT_READ; 283 | } 284 | } else { 285 | returnValue = FS_DOES_NOT_EXIST; 286 | } 287 | return returnValue; 288 | } 289 | 290 | /** 291 | * Helper function to ascertain whether the application has the correct access to the OBB 292 | * directory to allow an OBB file to be written. 293 | * 294 | * @param c the app/activity/service context 295 | * @return true if the application can write an OBB file, false otherwise 296 | */ 297 | static public boolean canWriteOBBFile(Context c) { 298 | String path = getSaveFilePath(c); 299 | File fileForNewFile = new File(path); 300 | boolean canWrite; 301 | if (fileForNewFile.exists()) { 302 | canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite(); 303 | } else { 304 | canWrite = fileForNewFile.mkdirs(); 305 | } 306 | return canWrite; 307 | } 308 | 309 | /** 310 | * Converts download states that are returned by the 311 | * {@link IDownloaderClient#onDownloadStateChanged} callback into usable strings. This is useful 312 | * if using the state strings built into the library to display user messages. 313 | * 314 | * @param state One of the STATE_* constants from {@link IDownloaderClient}. 315 | * @return string resource ID for the corresponding string. 316 | */ 317 | static public int getDownloaderStringResourceIDFromState(Context ctx, int state) { 318 | switch (state) { 319 | case IDownloaderClient.STATE_IDLE: 320 | return getStringResource(ctx, "state_idle"); 321 | case IDownloaderClient.STATE_FETCHING_URL: 322 | return getStringResource(ctx, "state_fetching_url"); 323 | case IDownloaderClient.STATE_CONNECTING: 324 | return getStringResource(ctx, "state_connecting"); 325 | case IDownloaderClient.STATE_DOWNLOADING: 326 | return getStringResource(ctx, "state_downloading"); 327 | case IDownloaderClient.STATE_COMPLETED: 328 | return getStringResource(ctx, "state_completed"); 329 | case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE: 330 | return getStringResource(ctx, "state_paused_network_unavailable"); 331 | case IDownloaderClient.STATE_PAUSED_BY_REQUEST: 332 | return getStringResource(ctx, "state_paused_by_request"); 333 | case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: 334 | return getStringResource(ctx, "state_paused_wifi_disabled"); 335 | case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: 336 | return getStringResource(ctx, "state_paused_wifi_unavailable"); 337 | case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED: 338 | return getStringResource(ctx, "state_paused_wifi_disabled"); 339 | case IDownloaderClient.STATE_PAUSED_NEED_WIFI: 340 | return getStringResource(ctx, "state_paused_wifi_unavailable"); 341 | case IDownloaderClient.STATE_PAUSED_ROAMING: 342 | return getStringResource(ctx, "state_paused_roaming"); 343 | case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE: 344 | return getStringResource(ctx, "state_paused_network_setup_failure"); 345 | case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: 346 | return getStringResource(ctx, "state_paused_sdcard_unavailable"); 347 | case IDownloaderClient.STATE_FAILED_UNLICENSED: 348 | return getStringResource(ctx, "state_failed_unlicensed"); 349 | case IDownloaderClient.STATE_FAILED_FETCHING_URL: 350 | return getStringResource(ctx, "state_failed_fetching_url"); 351 | case IDownloaderClient.STATE_FAILED_SDCARD_FULL: 352 | return getStringResource(ctx, "state_failed_sdcard_full"); 353 | case IDownloaderClient.STATE_FAILED_CANCELED: 354 | return getStringResource(ctx, "state_failed_cancelled"); 355 | default: 356 | return getStringResource(ctx, "state_unknown"); 357 | } 358 | } 359 | 360 | static public int getStringResource(Context ctx, String name) { 361 | return ctx.getResources().getIdentifier(name, "string", ctx.getPackageName()); 362 | } 363 | static public int getLayoutResource(Context ctx, String name) { 364 | return ctx.getResources().getIdentifier(name, "layout", ctx.getPackageName()); 365 | } 366 | static public int getIdResource(Context ctx, String name) { 367 | return ctx.getResources().getIdentifier(name, "id", ctx.getPackageName()); 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.os.Messenger; 20 | 21 | /** 22 | * This interface should be implemented by the client activity for the 23 | * downloader. It is used to pass status from the service to the client. 24 | */ 25 | public interface IDownloaderClient { 26 | static final int STATE_IDLE = 1; 27 | static final int STATE_FETCHING_URL = 2; 28 | static final int STATE_CONNECTING = 3; 29 | static final int STATE_DOWNLOADING = 4; 30 | static final int STATE_COMPLETED = 5; 31 | 32 | static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6; 33 | static final int STATE_PAUSED_BY_REQUEST = 7; 34 | 35 | /** 36 | * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and 37 | * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and 38 | * cellular permission will restart the service. Wi-Fi disabled means that 39 | * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the 40 | * other case Wi-Fi is enabled but not available. 41 | */ 42 | static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8; 43 | static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9; 44 | 45 | /** 46 | * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that 47 | * Wi-Fi is unavailable and cellular permission will NOT restart the 48 | * service. Wi-Fi disabled means that the Wi-Fi manager is returning that 49 | * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not 50 | * available. 51 | *

52 | * The service does not return these values. We recommend that app 53 | * developers with very large payloads do not allow these payloads to be 54 | * downloaded over cellular connections. 55 | */ 56 | static final int STATE_PAUSED_WIFI_DISABLED = 10; 57 | static final int STATE_PAUSED_NEED_WIFI = 11; 58 | 59 | static final int STATE_PAUSED_ROAMING = 12; 60 | 61 | /** 62 | * Scary case. We were on a network that redirected us to another website 63 | * that delivered us the wrong file. 64 | */ 65 | static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13; 66 | 67 | static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14; 68 | 69 | static final int STATE_FAILED_UNLICENSED = 15; 70 | static final int STATE_FAILED_FETCHING_URL = 16; 71 | static final int STATE_FAILED_SDCARD_FULL = 17; 72 | static final int STATE_FAILED_CANCELED = 18; 73 | 74 | static final int STATE_FAILED = 19; 75 | 76 | /** 77 | * Called internally by the stub when the service is bound to the client. 78 | *

79 | * Critical implementation detail. In onServiceConnected we create the 80 | * remote service and marshaler. This is how we pass the client information 81 | * back to the service so the client can be properly notified of changes. We 82 | * must do this every time we reconnect to the service. 83 | *

84 | * That is, when you receive this callback, you should call 85 | * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member 86 | * instance of {@link IDownloaderService}, then call 87 | * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved 88 | * from your {@link IStub} proxy object. 89 | * 90 | * @param m the service Messenger. This Messenger is used to call the 91 | * service API from the client. 92 | */ 93 | void onServiceConnected(Messenger m); 94 | 95 | /** 96 | * Called when the download state changes. Depending on the state, there may 97 | * be user requests. The service is free to change the download state in the 98 | * middle of a user request, so the client should be able to handle this. 99 | *

100 | * The Downloader Library includes a collection of string resources that 101 | * correspond to each of the states, which you can use to provide users a 102 | * useful message based on the state provided in this callback. To fetch the 103 | * appropriate string for a state, call 104 | * {@link Helpers#getDownloaderStringResourceIDFromState}. 105 | *

106 | * What this means to the developer: The application has gotten a message 107 | * that the download has paused due to lack of WiFi. The developer should 108 | * then show UI asking the user if they want to enable downloading over 109 | * cellular connections with appropriate warnings. If the application 110 | * suddenly starts downloading, the application should revert to showing the 111 | * progress again, rather than leaving up the download over cellular UI up. 112 | * 113 | * @param newState one of the STATE_* values defined in IDownloaderClient 114 | */ 115 | void onDownloadStateChanged(int newState); 116 | 117 | /** 118 | * Shows the download progress. This is intended to be used to fill out a 119 | * client UI. This progress should only be shown in a few states such as 120 | * STATE_DOWNLOADING. 121 | * 122 | * @param progress the DownloadProgressInfo object containing the current 123 | * progress of all downloads. 124 | */ 125 | void onDownloadProgress(DownloadProgressInfo progress); 126 | } 127 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import com.google.android.vending.expansion.downloader.impl.DownloaderService; 20 | import android.os.Messenger; 21 | 22 | /** 23 | * This interface is implemented by the DownloaderService and by the 24 | * DownloaderServiceMarshaller. It contains functions to control the service. 25 | * When a client binds to the service, it must call the onClientUpdated 26 | * function. 27 | *

28 | * You can acquire a proxy that implements this interface for your service by 29 | * calling {@link DownloaderServiceMarshaller#CreateProxy} during the 30 | * {@link IDownloaderClient#onServiceConnected} callback. At which point, you 31 | * should immediately call {@link #onClientUpdated}. 32 | */ 33 | public interface IDownloaderService { 34 | /** 35 | * Set this flag in response to the 36 | * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then 37 | * call RequestContinueDownload to resume a download 38 | */ 39 | public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1; 40 | 41 | /** 42 | * Request that the service abort the current download. The service should 43 | * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}. 44 | */ 45 | void requestAbortDownload(); 46 | 47 | /** 48 | * Request that the service pause the current download. The service should 49 | * respond by changing the state to 50 | * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. 51 | */ 52 | void requestPauseDownload(); 53 | 54 | /** 55 | * Request that the service continue a paused download, when in any paused 56 | * or failed state, including 57 | * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}. 58 | */ 59 | void requestContinueDownload(); 60 | 61 | /** 62 | * Set the flags for this download (e.g. 63 | * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}). 64 | * 65 | * @param flags 66 | */ 67 | void setDownloadFlags(int flags); 68 | 69 | /** 70 | * Requests that the download status be sent to the client. 71 | */ 72 | void requestDownloadStatus(); 73 | 74 | /** 75 | * Call this when you get {@link 76 | * IDownloaderClient.onServiceConnected(Messenger m)} from the 77 | * DownloaderClient to register the client with the service. It will 78 | * automatically send the current status to the client. 79 | * 80 | * @param clientMessenger 81 | */ 82 | void onClientUpdated(Messenger clientMessenger); 83 | } 84 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IStub.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.content.Context; 20 | import android.os.Messenger; 21 | 22 | /** 23 | * This is the interface that is used to connect/disconnect from the downloader 24 | * service. 25 | *

26 | * You should get a proxy object that implements this interface by calling 27 | * {@link DownloaderClientMarshaller#CreateStub} in your activity when the 28 | * downloader service starts. Then, call {@link #connect} during your activity's 29 | * onResume() and call {@link #disconnect} during onStop(). 30 | *

31 | * Then during the {@link IDownloaderClient#onServiceConnected} callback, you 32 | * should call {@link #getMessenger} to pass the stub's Messenger object to 33 | * {@link IDownloaderService#onClientUpdated}. 34 | */ 35 | public interface IStub { 36 | Messenger getMessenger(); 37 | 38 | void connect(Context c); 39 | 40 | void disconnect(Context c); 41 | } 42 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/SystemFacade.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader; 18 | 19 | import android.app.Notification; 20 | import android.app.NotificationManager; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.pm.PackageManager.NameNotFoundException; 24 | import android.net.ConnectivityManager; 25 | import android.net.NetworkInfo; 26 | import android.telephony.TelephonyManager; 27 | import android.util.Log; 28 | 29 | /** 30 | * Contains useful helper functions, typically tied to the application context. 31 | */ 32 | class SystemFacade { 33 | private Context mContext; 34 | private NotificationManager mNotificationManager; 35 | 36 | public SystemFacade(Context context) { 37 | mContext = context; 38 | mNotificationManager = (NotificationManager) 39 | mContext.getSystemService(Context.NOTIFICATION_SERVICE); 40 | } 41 | 42 | public long currentTimeMillis() { 43 | return System.currentTimeMillis(); 44 | } 45 | 46 | public Integer getActiveNetworkType() { 47 | ConnectivityManager connectivity = 48 | (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 49 | if (connectivity == null) { 50 | Log.w(Constants.TAG, "couldn't get connectivity manager"); 51 | return null; 52 | } 53 | 54 | NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); 55 | if (activeInfo == null) { 56 | if (Constants.LOGVV) { 57 | Log.v(Constants.TAG, "network is not available"); 58 | } 59 | return null; 60 | } 61 | return activeInfo.getType(); 62 | } 63 | 64 | public boolean isNetworkRoaming() { 65 | ConnectivityManager connectivity = 66 | (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 67 | if (connectivity == null) { 68 | Log.w(Constants.TAG, "couldn't get connectivity manager"); 69 | return false; 70 | } 71 | 72 | NetworkInfo info = connectivity.getActiveNetworkInfo(); 73 | boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); 74 | TelephonyManager tm = (TelephonyManager) mContext 75 | .getSystemService(Context.TELEPHONY_SERVICE); 76 | if (null == tm) { 77 | Log.w(Constants.TAG, "couldn't get telephony manager"); 78 | return false; 79 | } 80 | boolean isRoaming = isMobile && tm.isNetworkRoaming(); 81 | if (Constants.LOGVV && isRoaming) { 82 | Log.v(Constants.TAG, "network is roaming"); 83 | } 84 | return isRoaming; 85 | } 86 | 87 | public Long getMaxBytesOverMobile() { 88 | return (long) Integer.MAX_VALUE; 89 | } 90 | 91 | public Long getRecommendedMaxBytesOverMobile() { 92 | return 2097152L; 93 | } 94 | 95 | public void sendBroadcast(Intent intent) { 96 | mContext.sendBroadcast(intent); 97 | } 98 | 99 | public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException { 100 | return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid; 101 | } 102 | 103 | public void postNotification(long id, Notification notification) { 104 | /** 105 | * TODO: The system notification manager takes ints, not longs, as IDs, 106 | * but the download manager uses IDs take straight from the database, 107 | * which are longs. This will have to be dealt with at some point. 108 | */ 109 | mNotificationManager.notify((int) id, notification); 110 | } 111 | 112 | public void cancelNotification(long id) { 113 | mNotificationManager.cancel((int) id); 114 | } 115 | 116 | public void cancelAllNotifications() { 117 | mNotificationManager.cancelAll(); 118 | } 119 | 120 | public void startThread(Thread thread) { 121 | thread.start(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import android.app.Service; 20 | import android.content.Intent; 21 | import android.os.Handler; 22 | import android.os.HandlerThread; 23 | import android.os.IBinder; 24 | import android.os.Looper; 25 | import android.os.Message; 26 | import android.util.Log; 27 | 28 | /** 29 | * This service differs from IntentService in a few minor ways/ It will not 30 | * auto-stop itself after the intent is handled unless the target returns "true" 31 | * in should stop. Since the goal of this service is to handle a single kind of 32 | * intent, it does not queue up batches of intents of the same type. 33 | */ 34 | public abstract class CustomIntentService extends Service { 35 | private String mName; 36 | private boolean mRedelivery; 37 | private volatile ServiceHandler mServiceHandler; 38 | private volatile Looper mServiceLooper; 39 | private static final String LOG_TAG = "CustomIntentService"; 40 | private static final int WHAT_MESSAGE = -10; 41 | 42 | public CustomIntentService(String paramString) { 43 | this.mName = paramString; 44 | } 45 | 46 | @Override 47 | public IBinder onBind(Intent paramIntent) { 48 | return null; 49 | } 50 | 51 | @Override 52 | public void onCreate() { 53 | super.onCreate(); 54 | HandlerThread localHandlerThread = new HandlerThread("IntentService[" 55 | + this.mName + "]"); 56 | localHandlerThread.start(); 57 | this.mServiceLooper = localHandlerThread.getLooper(); 58 | this.mServiceHandler = new ServiceHandler(this.mServiceLooper); 59 | } 60 | 61 | @Override 62 | public void onDestroy() { 63 | Thread localThread = this.mServiceLooper.getThread(); 64 | if ((localThread != null) && (localThread.isAlive())) { 65 | localThread.interrupt(); 66 | } 67 | this.mServiceLooper.quit(); 68 | Log.d(LOG_TAG, "onDestroy"); 69 | } 70 | 71 | protected abstract void onHandleIntent(Intent paramIntent); 72 | 73 | protected abstract boolean shouldStop(); 74 | 75 | @Override 76 | public void onStart(Intent paramIntent, int startId) { 77 | if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) { 78 | Message localMessage = this.mServiceHandler.obtainMessage(); 79 | localMessage.arg1 = startId; 80 | localMessage.obj = paramIntent; 81 | localMessage.what = WHAT_MESSAGE; 82 | this.mServiceHandler.sendMessage(localMessage); 83 | } 84 | } 85 | 86 | @Override 87 | public int onStartCommand(Intent paramIntent, int flags, int startId) { 88 | onStart(paramIntent, startId); 89 | return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; 90 | } 91 | 92 | public void setIntentRedelivery(boolean enabled) { 93 | this.mRedelivery = enabled; 94 | } 95 | 96 | private final class ServiceHandler extends Handler { 97 | public ServiceHandler(Looper looper) { 98 | super(looper); 99 | } 100 | 101 | @Override 102 | public void handleMessage(Message paramMessage) { 103 | CustomIntentService.this 104 | .onHandleIntent((Intent) paramMessage.obj); 105 | if (shouldStop()) { 106 | Log.d(LOG_TAG, "stopSelf"); 107 | CustomIntentService.this.stopSelf(paramMessage.arg1); 108 | Log.d(LOG_TAG, "afterStopSelf"); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import com.google.android.vending.expansion.downloader.Constants; 20 | import com.google.android.vending.expansion.downloader.Helpers; 21 | 22 | import android.util.Log; 23 | 24 | /** 25 | * Representation of information about an individual download from the database. 26 | */ 27 | public class DownloadInfo { 28 | public String mUri; 29 | public final int mIndex; 30 | public final String mFileName; 31 | public String mETag; 32 | public long mTotalBytes; 33 | public long mCurrentBytes; 34 | public long mLastMod; 35 | public int mStatus; 36 | public int mControl; 37 | public int mNumFailed; 38 | public int mRetryAfter; 39 | public int mRedirectCount; 40 | 41 | boolean mInitialized; 42 | 43 | public int mFuzz; 44 | 45 | public DownloadInfo(int index, String fileName, String pkg) { 46 | mFuzz = Helpers.sRandom.nextInt(1001); 47 | mFileName = fileName; 48 | mIndex = index; 49 | } 50 | 51 | public void resetDownload() { 52 | mCurrentBytes = 0; 53 | mETag = ""; 54 | mLastMod = 0; 55 | mStatus = 0; 56 | mControl = 0; 57 | mNumFailed = 0; 58 | mRetryAfter = 0; 59 | mRedirectCount = 0; 60 | } 61 | 62 | /** 63 | * Returns the time when a download should be restarted. 64 | */ 65 | public long restartTime(long now) { 66 | if (mNumFailed == 0) { 67 | return now; 68 | } 69 | if (mRetryAfter > 0) { 70 | return mLastMod + mRetryAfter; 71 | } 72 | return mLastMod + 73 | Constants.RETRY_FIRST_DELAY * 74 | (1000 + mFuzz) * (1 << (mNumFailed - 1)); 75 | } 76 | 77 | public void logVerboseInfo() { 78 | Log.v(Constants.TAG, "Service adding new entry"); 79 | Log.v(Constants.TAG, "FILENAME: " + mFileName); 80 | Log.v(Constants.TAG, "URI : " + mUri); 81 | Log.v(Constants.TAG, "FILENAME: " + mFileName); 82 | Log.v(Constants.TAG, "CONTROL : " + mControl); 83 | Log.v(Constants.TAG, "STATUS : " + mStatus); 84 | Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); 85 | Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); 86 | Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount); 87 | Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); 88 | Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); 89 | Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); 90 | Log.v(Constants.TAG, "ETAG : " + mETag); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import com.google.android.vending.expansion.downloader.DownloadProgressInfo; 20 | import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; 21 | import com.google.android.vending.expansion.downloader.Helpers; 22 | import com.google.android.vending.expansion.downloader.IDownloaderClient; 23 | 24 | import android.app.Notification; 25 | import android.app.NotificationManager; 26 | import android.app.PendingIntent; 27 | import android.content.Context; 28 | import android.os.Build; 29 | import android.os.Messenger; 30 | 31 | /** 32 | * This class handles displaying the notification associated with the download 33 | * queue going on in the download manager. It handles multiple status types; 34 | * Some require user interaction and some do not. Some of the user interactions 35 | * may be transient. (for example: the user is queried to continue the download 36 | * on 3G when it started on WiFi, but then the phone locks onto WiFi again so 37 | * the prompt automatically goes away) 38 | *

39 | * The application interface for the downloader also needs to understand and 40 | * handle these transient states. 41 | */ 42 | public class DownloadNotification implements IDownloaderClient { 43 | 44 | private int mState; 45 | private final Context mContext; 46 | private final NotificationManager mNotificationManager; 47 | private CharSequence mCurrentTitle; 48 | 49 | private IDownloaderClient mClientProxy; 50 | private Notification.Builder mActiveDownloadBuilder; 51 | private Notification.Builder mBuilder; 52 | private Notification.Builder mCurrentBuilder; 53 | private CharSequence mLabel; 54 | private String mCurrentText; 55 | private DownloadProgressInfo mProgressInfo; 56 | private PendingIntent mContentIntent; 57 | 58 | private static final String LOGTAG = "DownloadNotification"; 59 | private static final int NOTIFICATION_ID = LOGTAG.hashCode(); 60 | 61 | public void setClientIntent(PendingIntent clientIntent) { 62 | this.mBuilder.setContentIntent(clientIntent); 63 | this.mActiveDownloadBuilder.setContentIntent(clientIntent); 64 | this.mContentIntent = clientIntent; 65 | } 66 | 67 | public void resendState() { 68 | if (null != mClientProxy) { 69 | mClientProxy.onDownloadStateChanged(mState); 70 | } 71 | } 72 | 73 | @Override 74 | public void onDownloadStateChanged(int newState) { 75 | if (null != mClientProxy) { 76 | mClientProxy.onDownloadStateChanged(newState); 77 | } 78 | if (newState != mState) { 79 | mState = newState; 80 | if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) { 81 | return; 82 | } 83 | int stringDownloadID; 84 | int iconResource; 85 | boolean ongoingEvent; 86 | 87 | // get the new title string and paused text 88 | switch (newState) { 89 | case 0: 90 | iconResource = android.R.drawable.stat_sys_warning; 91 | stringDownloadID = Helpers.getStringResource(mContext, "state_unknown"); 92 | ongoingEvent = false; 93 | break; 94 | 95 | case IDownloaderClient.STATE_DOWNLOADING: 96 | iconResource = android.R.drawable.stat_sys_download; 97 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState); 98 | ongoingEvent = true; 99 | break; 100 | 101 | case IDownloaderClient.STATE_FETCHING_URL: 102 | case IDownloaderClient.STATE_CONNECTING: 103 | iconResource = android.R.drawable.stat_sys_download_done; 104 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState); 105 | ongoingEvent = true; 106 | break; 107 | 108 | case IDownloaderClient.STATE_COMPLETED: 109 | case IDownloaderClient.STATE_PAUSED_BY_REQUEST: 110 | iconResource = android.R.drawable.stat_sys_download_done; 111 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState); 112 | ongoingEvent = false; 113 | break; 114 | 115 | case IDownloaderClient.STATE_FAILED: 116 | case IDownloaderClient.STATE_FAILED_CANCELED: 117 | case IDownloaderClient.STATE_FAILED_FETCHING_URL: 118 | case IDownloaderClient.STATE_FAILED_SDCARD_FULL: 119 | case IDownloaderClient.STATE_FAILED_UNLICENSED: 120 | iconResource = android.R.drawable.stat_sys_warning; 121 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState); 122 | ongoingEvent = false; 123 | break; 124 | 125 | default: 126 | iconResource = android.R.drawable.stat_sys_warning; 127 | stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState); 128 | ongoingEvent = true; 129 | break; 130 | } 131 | 132 | mCurrentText = mContext.getString(stringDownloadID); 133 | mCurrentTitle = mLabel; 134 | mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText) 135 | .setSmallIcon(iconResource) 136 | .setContentTitle(mCurrentTitle) 137 | .setContentText(mCurrentText); 138 | if (ongoingEvent) { 139 | mCurrentBuilder.setOngoing(true); 140 | } else { 141 | mCurrentBuilder.setOngoing(false); 142 | mCurrentBuilder.setAutoCancel(true); 143 | } 144 | Notification notification; 145 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 146 | notification = mCurrentBuilder.build(); 147 | } else { 148 | notification = mCurrentBuilder.getNotification(); 149 | } 150 | mNotificationManager.notify(NOTIFICATION_ID, notification); 151 | } 152 | } 153 | 154 | @Override 155 | public void onDownloadProgress(DownloadProgressInfo progress) { 156 | mProgressInfo = progress; 157 | if (null != mClientProxy) { 158 | mClientProxy.onDownloadProgress(progress); 159 | } 160 | if (progress.mOverallTotal <= 0) { 161 | // we just show the text 162 | mBuilder.setTicker(mCurrentTitle) 163 | .setSmallIcon(android.R.drawable.stat_sys_download) 164 | .setContentTitle(mCurrentTitle) 165 | .setContentText(mCurrentText); 166 | mCurrentBuilder = mBuilder; 167 | } else { 168 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 169 | mActiveDownloadBuilder.setProgress((int) progress.mOverallTotal, (int) progress.mOverallProgress, false); 170 | } 171 | mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal)) 172 | .setSmallIcon(android.R.drawable.stat_sys_download) 173 | .setTicker(mLabel + ": " + mCurrentText) 174 | .setContentTitle(mLabel) 175 | .setContentInfo(mContext.getString(Helpers.getStringResource(mContext, "time_remaining_notification"), 176 | Helpers.getTimeRemaining(progress.mTimeRemaining))); 177 | mCurrentBuilder = mActiveDownloadBuilder; 178 | } 179 | Notification notification; 180 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 181 | notification = mCurrentBuilder.build(); 182 | } else { 183 | notification = mCurrentBuilder.getNotification(); 184 | } 185 | mNotificationManager.notify(NOTIFICATION_ID, notification); 186 | } 187 | 188 | /** 189 | * Called in response to onClientUpdated. Creates a new proxy and notifies 190 | * it of the current state. 191 | * 192 | * @param msg the client Messenger to notify 193 | */ 194 | public void setMessenger(Messenger msg) { 195 | mClientProxy = DownloaderClientMarshaller.CreateProxy(msg); 196 | if (null != mProgressInfo) { 197 | mClientProxy.onDownloadProgress(mProgressInfo); 198 | } 199 | if (mState != -1) { 200 | mClientProxy.onDownloadStateChanged(mState); 201 | } 202 | } 203 | 204 | /** 205 | * Constructor 206 | * 207 | * @param ctx The context to use to obtain access to the Notification 208 | * Service 209 | */ 210 | DownloadNotification(Context ctx, CharSequence applicationLabel) { 211 | mState = -1; 212 | mContext = ctx; 213 | mLabel = applicationLabel; 214 | mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 215 | mActiveDownloadBuilder = new Notification.Builder(ctx); 216 | mBuilder = new Notification.Builder(ctx); 217 | 218 | // Set Notification category and priorities to something that makes sense for a long 219 | // lived background task. 220 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 221 | mActiveDownloadBuilder.setPriority(Notification.PRIORITY_LOW); 222 | mBuilder.setPriority(Notification.PRIORITY_LOW); 223 | } 224 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 225 | mActiveDownloadBuilder.setCategory(Notification.CATEGORY_PROGRESS); 226 | mBuilder.setCategory(Notification.CATEGORY_PROGRESS); 227 | } 228 | 229 | mCurrentBuilder = mBuilder; 230 | } 231 | 232 | @Override 233 | public void onServiceConnected(Messenger m) { 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.expansion.downloader.impl; 18 | 19 | import android.text.format.Time; 20 | 21 | import java.util.Calendar; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * Helper for parsing an HTTP date. 27 | */ 28 | public final class HttpDateTime { 29 | 30 | /* 31 | * Regular expression for parsing HTTP-date. Wdy, DD Mon YYYY HH:MM:SS GMT 32 | * RFC 822, updated by RFC 1123 Weekday, DD-Mon-YY HH:MM:SS GMT RFC 850, 33 | * obsoleted by RFC 1036 Wdy Mon DD HH:MM:SS YYYY ANSI C's asctime() format 34 | * with following variations Wdy, DD-Mon-YYYY HH:MM:SS GMT Wdy, (SP)D Mon 35 | * YYYY HH:MM:SS GMT Wdy,DD Mon YYYY HH:MM:SS GMT Wdy, DD-Mon-YY HH:MM:SS 36 | * GMT Wdy, DD Mon YYYY HH:MM:SS -HHMM Wdy, DD Mon YYYY HH:MM:SS Wdy Mon 37 | * (SP)D HH:MM:SS YYYY Wdy Mon DD HH:MM:SS YYYY GMT HH can be H if the first 38 | * digit is zero. Mon can be the full name of the month. 39 | */ 40 | private static final String HTTP_DATE_RFC_REGEXP = 41 | "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]" 42 | + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])"; 43 | 44 | private static final String HTTP_DATE_ANSIC_REGEXP = 45 | "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]" 46 | + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})"; 47 | 48 | /** 49 | * The compiled version of the HTTP-date regular expressions. 50 | */ 51 | private static final Pattern HTTP_DATE_RFC_PATTERN = 52 | Pattern.compile(HTTP_DATE_RFC_REGEXP); 53 | private static final Pattern HTTP_DATE_ANSIC_PATTERN = 54 | Pattern.compile(HTTP_DATE_ANSIC_REGEXP); 55 | 56 | private static class TimeOfDay { 57 | TimeOfDay(int h, int m, int s) { 58 | this.hour = h; 59 | this.minute = m; 60 | this.second = s; 61 | } 62 | 63 | int hour; 64 | int minute; 65 | int second; 66 | } 67 | 68 | public static long parse(String timeString) 69 | throws IllegalArgumentException { 70 | 71 | int date = 1; 72 | int month = Calendar.JANUARY; 73 | int year = 1970; 74 | TimeOfDay timeOfDay; 75 | 76 | Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString); 77 | if (rfcMatcher.find()) { 78 | date = getDate(rfcMatcher.group(1)); 79 | month = getMonth(rfcMatcher.group(2)); 80 | year = getYear(rfcMatcher.group(3)); 81 | timeOfDay = getTime(rfcMatcher.group(4)); 82 | } else { 83 | Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString); 84 | if (ansicMatcher.find()) { 85 | month = getMonth(ansicMatcher.group(1)); 86 | date = getDate(ansicMatcher.group(2)); 87 | timeOfDay = getTime(ansicMatcher.group(3)); 88 | year = getYear(ansicMatcher.group(4)); 89 | } else { 90 | throw new IllegalArgumentException(); 91 | } 92 | } 93 | 94 | // FIXME: Y2038 BUG! 95 | if (year >= 2038) { 96 | year = 2038; 97 | month = Calendar.JANUARY; 98 | date = 1; 99 | } 100 | 101 | Time time = new Time(Time.TIMEZONE_UTC); 102 | time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date, 103 | month, year); 104 | return time.toMillis(false /* use isDst */); 105 | } 106 | 107 | private static int getDate(String dateString) { 108 | if (dateString.length() == 2) { 109 | return (dateString.charAt(0) - '0') * 10 110 | + (dateString.charAt(1) - '0'); 111 | } else { 112 | return (dateString.charAt(0) - '0'); 113 | } 114 | } 115 | 116 | /* 117 | * jan = 9 + 0 + 13 = 22 feb = 5 + 4 + 1 = 10 mar = 12 + 0 + 17 = 29 apr = 0 118 | * + 15 + 17 = 32 may = 12 + 0 + 24 = 36 jun = 9 + 20 + 13 = 42 jul = 9 + 20 119 | * + 11 = 40 aug = 0 + 20 + 6 = 26 sep = 18 + 4 + 15 = 37 oct = 14 + 2 + 19 120 | * = 35 nov = 13 + 14 + 21 = 48 dec = 3 + 4 + 2 = 9 121 | */ 122 | private static int getMonth(String monthString) { 123 | int hash = Character.toLowerCase(monthString.charAt(0)) + 124 | Character.toLowerCase(monthString.charAt(1)) + 125 | Character.toLowerCase(monthString.charAt(2)) - 3 * 'a'; 126 | switch (hash) { 127 | case 22: 128 | return Calendar.JANUARY; 129 | case 10: 130 | return Calendar.FEBRUARY; 131 | case 29: 132 | return Calendar.MARCH; 133 | case 32: 134 | return Calendar.APRIL; 135 | case 36: 136 | return Calendar.MAY; 137 | case 42: 138 | return Calendar.JUNE; 139 | case 40: 140 | return Calendar.JULY; 141 | case 26: 142 | return Calendar.AUGUST; 143 | case 37: 144 | return Calendar.SEPTEMBER; 145 | case 35: 146 | return Calendar.OCTOBER; 147 | case 48: 148 | return Calendar.NOVEMBER; 149 | case 9: 150 | return Calendar.DECEMBER; 151 | default: 152 | throw new IllegalArgumentException(); 153 | } 154 | } 155 | 156 | private static int getYear(String yearString) { 157 | if (yearString.length() == 2) { 158 | int year = (yearString.charAt(0) - '0') * 10 159 | + (yearString.charAt(1) - '0'); 160 | if (year >= 70) { 161 | return year + 1900; 162 | } else { 163 | return year + 2000; 164 | } 165 | } else if (yearString.length() == 3) { 166 | // According to RFC 2822, three digit years should be added to 1900. 167 | int year = (yearString.charAt(0) - '0') * 100 168 | + (yearString.charAt(1) - '0') * 10 169 | + (yearString.charAt(2) - '0'); 170 | return year + 1900; 171 | } else if (yearString.length() == 4) { 172 | return (yearString.charAt(0) - '0') * 1000 173 | + (yearString.charAt(1) - '0') * 100 174 | + (yearString.charAt(2) - '0') * 10 175 | + (yearString.charAt(3) - '0'); 176 | } else { 177 | return 1970; 178 | } 179 | } 180 | 181 | private static TimeOfDay getTime(String timeString) { 182 | // HH might be H 183 | int i = 0; 184 | int hour = timeString.charAt(i++) - '0'; 185 | if (timeString.charAt(i) != ':') 186 | hour = hour * 10 + (timeString.charAt(i++) - '0'); 187 | // Skip ':' 188 | i++; 189 | 190 | int minute = (timeString.charAt(i++) - '0') * 10 191 | + (timeString.charAt(i++) - '0'); 192 | // Skip ':' 193 | i++; 194 | 195 | int second = (timeString.charAt(i++) - '0') * 10 196 | + (timeString.charAt(i++) - '0'); 197 | 198 | return new TimeOfDay(hour, minute, second); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/AESObfuscator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | import com.google.android.vending.licensing.util.Base64; 20 | import com.google.android.vending.licensing.util.Base64DecoderException; 21 | 22 | import java.io.UnsupportedEncodingException; 23 | import java.security.GeneralSecurityException; 24 | import java.security.spec.KeySpec; 25 | 26 | import javax.crypto.BadPaddingException; 27 | import javax.crypto.Cipher; 28 | import javax.crypto.IllegalBlockSizeException; 29 | import javax.crypto.SecretKey; 30 | import javax.crypto.SecretKeyFactory; 31 | import javax.crypto.spec.IvParameterSpec; 32 | import javax.crypto.spec.PBEKeySpec; 33 | import javax.crypto.spec.SecretKeySpec; 34 | 35 | /** 36 | * An Obfuscator that uses AES to encrypt data. 37 | */ 38 | public class AESObfuscator implements Obfuscator { 39 | private static final String UTF8 = "UTF-8"; 40 | private static final String KEYGEN_ALGORITHM = "PBEWITHSHAAND256BITAES-CBC-BC"; 41 | private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; 42 | private static final byte[] IV = 43 | { 16, 74, 71, -80, 32, 101, -47, 72, 117, -14, 0, -29, 70, 65, -12, 74 }; 44 | private static final String header = "com.google.android.vending.licensing.AESObfuscator-1|"; 45 | 46 | private Cipher mEncryptor; 47 | private Cipher mDecryptor; 48 | 49 | /** 50 | * @param salt an array of random bytes to use for each (un)obfuscation 51 | * @param applicationId application identifier, e.g. the package name 52 | * @param deviceId device identifier. Use as many sources as possible to 53 | * create this unique identifier. 54 | */ 55 | public AESObfuscator(byte[] salt, String applicationId, String deviceId) { 56 | try { 57 | SecretKeyFactory factory = SecretKeyFactory.getInstance(KEYGEN_ALGORITHM); 58 | KeySpec keySpec = 59 | new PBEKeySpec((applicationId + deviceId).toCharArray(), salt, 1024, 256); 60 | SecretKey tmp = factory.generateSecret(keySpec); 61 | SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES"); 62 | mEncryptor = Cipher.getInstance(CIPHER_ALGORITHM); 63 | mEncryptor.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(IV)); 64 | mDecryptor = Cipher.getInstance(CIPHER_ALGORITHM); 65 | mDecryptor.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(IV)); 66 | } catch (GeneralSecurityException e) { 67 | // This can't happen on a compatible Android device. 68 | throw new RuntimeException("Invalid environment", e); 69 | } 70 | } 71 | 72 | public String obfuscate(String original, String key) { 73 | if (original == null) { 74 | return null; 75 | } 76 | try { 77 | // Header is appended as an integrity check 78 | return Base64.encode(mEncryptor.doFinal((header + key + original).getBytes(UTF8))); 79 | } catch (UnsupportedEncodingException e) { 80 | throw new RuntimeException("Invalid environment", e); 81 | } catch (GeneralSecurityException e) { 82 | throw new RuntimeException("Invalid environment", e); 83 | } 84 | } 85 | 86 | public String unobfuscate(String obfuscated, String key) throws ValidationException { 87 | if (obfuscated == null) { 88 | return null; 89 | } 90 | try { 91 | String result = new String(mDecryptor.doFinal(Base64.decode(obfuscated)), UTF8); 92 | // Check for presence of header. This serves as a final integrity check, for cases 93 | // where the block size is correct during decryption. 94 | int headerIndex = result.indexOf(header+key); 95 | if (headerIndex != 0) { 96 | throw new ValidationException("Header not found (invalid data or key)" + ":" + 97 | obfuscated); 98 | } 99 | return result.substring(header.length()+key.length(), result.length()); 100 | } catch (Base64DecoderException e) { 101 | throw new ValidationException(e.getMessage() + ":" + obfuscated); 102 | } catch (IllegalBlockSizeException e) { 103 | throw new ValidationException(e.getMessage() + ":" + obfuscated); 104 | } catch (BadPaddingException e) { 105 | throw new ValidationException(e.getMessage() + ":" + obfuscated); 106 | } catch (UnsupportedEncodingException e) { 107 | throw new RuntimeException("Invalid environment", e); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/APKExpansionPolicy.java: -------------------------------------------------------------------------------- 1 | 2 | package com.google.android.vending.licensing; 3 | 4 | /* 5 | * Copyright (C) 2012 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | import android.content.Context; 21 | import android.content.SharedPreferences; 22 | import android.util.Log; 23 | 24 | import com.google.android.vending.licensing.util.URIQueryDecoder; 25 | 26 | import java.net.URI; 27 | import java.net.URISyntaxException; 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | import java.util.Set; 31 | import java.util.Vector; 32 | 33 | /** 34 | * Default policy. All policy decisions are based off of response data received 35 | * from the licensing service. Specifically, the licensing server sends the 36 | * following information: response validity period, error retry period, and 37 | * error retry count. 38 | *

39 | * These values will vary based on the the way the application is configured in 40 | * the Google Play publishing console, such as whether the application is 41 | * marked as free or is within its refund period, as well as how often an 42 | * application is checking with the licensing service. 43 | *

44 | * Developers who need more fine grained control over their application's 45 | * licensing policy should implement a custom Policy. 46 | */ 47 | public class APKExpansionPolicy implements Policy { 48 | 49 | private static final String TAG = "APKExpansionPolicy"; 50 | private static final String PREFS_FILE = "com.google.android.vending.licensing.APKExpansionPolicy"; 51 | private static final String PREF_LAST_RESPONSE = "lastResponse"; 52 | private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; 53 | private static final String PREF_RETRY_UNTIL = "retryUntil"; 54 | private static final String PREF_MAX_RETRIES = "maxRetries"; 55 | private static final String PREF_RETRY_COUNT = "retryCount"; 56 | private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; 57 | private static final String DEFAULT_RETRY_UNTIL = "0"; 58 | private static final String DEFAULT_MAX_RETRIES = "0"; 59 | private static final String DEFAULT_RETRY_COUNT = "0"; 60 | 61 | private static final long MILLIS_PER_MINUTE = 60 * 1000; 62 | 63 | private long mValidityTimestamp; 64 | private long mRetryUntil; 65 | private long mMaxRetries; 66 | private long mRetryCount; 67 | private long mLastResponseTime = 0; 68 | private int mLastResponse; 69 | private PreferenceObfuscator mPreferences; 70 | private Vector mExpansionURLs = new Vector(); 71 | private Vector mExpansionFileNames = new Vector(); 72 | private Vector mExpansionFileSizes = new Vector(); 73 | 74 | /** 75 | * The design of the protocol supports n files. Currently the market can 76 | * only deliver two files. To accommodate this, we have these two constants, 77 | * but the order is the only relevant thing here. 78 | */ 79 | public static final int MAIN_FILE_URL_INDEX = 0; 80 | public static final int PATCH_FILE_URL_INDEX = 1; 81 | 82 | /** 83 | * @param context The context for the current application 84 | * @param obfuscator An obfuscator to be used with preferences. 85 | */ 86 | public APKExpansionPolicy(Context context, Obfuscator obfuscator) { 87 | // Import old values 88 | SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); 89 | mPreferences = new PreferenceObfuscator(sp, obfuscator); 90 | mLastResponse = Integer.parseInt( 91 | mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); 92 | mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, 93 | DEFAULT_VALIDITY_TIMESTAMP)); 94 | mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); 95 | mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); 96 | mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); 97 | } 98 | 99 | /** 100 | * We call this to guarantee that we fetch a fresh policy from the server. 101 | * This is to be used if the URL is invalid. 102 | */ 103 | public void resetPolicy() { 104 | mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY)); 105 | setRetryUntil(DEFAULT_RETRY_UNTIL); 106 | setMaxRetries(DEFAULT_MAX_RETRIES); 107 | setRetryCount(Long.parseLong(DEFAULT_RETRY_COUNT)); 108 | setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); 109 | mPreferences.commit(); 110 | } 111 | 112 | /** 113 | * Process a new response from the license server. 114 | *

115 | * This data will be used for computing future policy decisions. The 116 | * following parameters are processed: 117 | *

    118 | *
  • VT: the timestamp that the client should consider the response valid 119 | * until 120 | *
  • GT: the timestamp that the client should ignore retry errors until 121 | *
  • GR: the number of retry errors that the client should ignore 122 | *
123 | * 124 | * @param response the result from validating the server response 125 | * @param rawData the raw server response data 126 | */ 127 | public void processServerResponse(int response, 128 | com.google.android.vending.licensing.ResponseData rawData) { 129 | 130 | // Update retry counter 131 | if (response != Policy.RETRY) { 132 | setRetryCount(0); 133 | } else { 134 | setRetryCount(mRetryCount + 1); 135 | } 136 | 137 | if (response == Policy.LICENSED) { 138 | // Update server policy data 139 | Map extras = decodeExtras(rawData.extra); 140 | mLastResponse = response; 141 | setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE)); 142 | Set keys = extras.keySet(); 143 | for (String key : keys) { 144 | if (key.equals("VT")) { 145 | setValidityTimestamp(extras.get(key)); 146 | } else if (key.equals("GT")) { 147 | setRetryUntil(extras.get(key)); 148 | } else if (key.equals("GR")) { 149 | setMaxRetries(extras.get(key)); 150 | } else if (key.startsWith("FILE_URL")) { 151 | int index = Integer.parseInt(key.substring("FILE_URL".length())) - 1; 152 | setExpansionURL(index, extras.get(key)); 153 | } else if (key.startsWith("FILE_NAME")) { 154 | int index = Integer.parseInt(key.substring("FILE_NAME".length())) - 1; 155 | setExpansionFileName(index, extras.get(key)); 156 | } else if (key.startsWith("FILE_SIZE")) { 157 | int index = Integer.parseInt(key.substring("FILE_SIZE".length())) - 1; 158 | setExpansionFileSize(index, Long.parseLong(extras.get(key))); 159 | } 160 | } 161 | } else if (response == Policy.NOT_LICENSED) { 162 | // Clear out stale policy data 163 | setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); 164 | setRetryUntil(DEFAULT_RETRY_UNTIL); 165 | setMaxRetries(DEFAULT_MAX_RETRIES); 166 | } 167 | 168 | setLastResponse(response); 169 | mPreferences.commit(); 170 | } 171 | 172 | /** 173 | * Set the last license response received from the server and add to 174 | * preferences. You must manually call PreferenceObfuscator.commit() to 175 | * commit these changes to disk. 176 | * 177 | * @param l the response 178 | */ 179 | private void setLastResponse(int l) { 180 | mLastResponseTime = System.currentTimeMillis(); 181 | mLastResponse = l; 182 | mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); 183 | } 184 | 185 | /** 186 | * Set the current retry count and add to preferences. You must manually 187 | * call PreferenceObfuscator.commit() to commit these changes to disk. 188 | * 189 | * @param c the new retry count 190 | */ 191 | private void setRetryCount(long c) { 192 | mRetryCount = c; 193 | mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); 194 | } 195 | 196 | public long getRetryCount() { 197 | return mRetryCount; 198 | } 199 | 200 | /** 201 | * Set the last validity timestamp (VT) received from the server and add to 202 | * preferences. You must manually call PreferenceObfuscator.commit() to 203 | * commit these changes to disk. 204 | * 205 | * @param validityTimestamp the VT string received 206 | */ 207 | private void setValidityTimestamp(String validityTimestamp) { 208 | Long lValidityTimestamp; 209 | try { 210 | lValidityTimestamp = Long.parseLong(validityTimestamp); 211 | } catch (NumberFormatException e) { 212 | // No response or not parseable, expire in one minute. 213 | Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); 214 | lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; 215 | validityTimestamp = Long.toString(lValidityTimestamp); 216 | } 217 | 218 | mValidityTimestamp = lValidityTimestamp; 219 | mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); 220 | } 221 | 222 | public long getValidityTimestamp() { 223 | return mValidityTimestamp; 224 | } 225 | 226 | /** 227 | * Set the retry until timestamp (GT) received from the server and add to 228 | * preferences. You must manually call PreferenceObfuscator.commit() to 229 | * commit these changes to disk. 230 | * 231 | * @param retryUntil the GT string received 232 | */ 233 | private void setRetryUntil(String retryUntil) { 234 | Long lRetryUntil; 235 | try { 236 | lRetryUntil = Long.parseLong(retryUntil); 237 | } catch (NumberFormatException e) { 238 | // No response or not parseable, expire immediately 239 | Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); 240 | retryUntil = "0"; 241 | lRetryUntil = 0l; 242 | } 243 | 244 | mRetryUntil = lRetryUntil; 245 | mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); 246 | } 247 | 248 | public long getRetryUntil() { 249 | return mRetryUntil; 250 | } 251 | 252 | /** 253 | * Set the max retries value (GR) as received from the server and add to 254 | * preferences. You must manually call PreferenceObfuscator.commit() to 255 | * commit these changes to disk. 256 | * 257 | * @param maxRetries the GR string received 258 | */ 259 | private void setMaxRetries(String maxRetries) { 260 | Long lMaxRetries; 261 | try { 262 | lMaxRetries = Long.parseLong(maxRetries); 263 | } catch (NumberFormatException e) { 264 | // No response or not parseable, expire immediately 265 | Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); 266 | maxRetries = "0"; 267 | lMaxRetries = 0l; 268 | } 269 | 270 | mMaxRetries = lMaxRetries; 271 | mPreferences.putString(PREF_MAX_RETRIES, maxRetries); 272 | } 273 | 274 | public long getMaxRetries() { 275 | return mMaxRetries; 276 | } 277 | 278 | /** 279 | * Gets the count of expansion URLs. Since expansionURLs are not committed 280 | * to preferences, this will return zero if there has been no LVL fetch 281 | * in the current session. 282 | * 283 | * @return the number of expansion URLs. (0,1,2) 284 | */ 285 | public int getExpansionURLCount() { 286 | return mExpansionURLs.size(); 287 | } 288 | 289 | /** 290 | * Gets the expansion URL. Since these URLs are not committed to 291 | * preferences, this will always return null if there has not been an LVL 292 | * fetch in the current session. 293 | * 294 | * @param index the index of the URL to fetch. This value will be either 295 | * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX 296 | */ 297 | public String getExpansionURL(int index) { 298 | if (index < mExpansionURLs.size()) { 299 | return mExpansionURLs.elementAt(index); 300 | } 301 | return null; 302 | } 303 | 304 | /** 305 | * Sets the expansion URL. Expansion URL's are not committed to preferences, 306 | * but are instead intended to be stored when the license response is 307 | * processed by the front-end. 308 | * 309 | * @param index the index of the expansion URL. This value will be either 310 | * MAIN_FILE_URL_INDEX or PATCH_FILE_URL_INDEX 311 | * @param URL the URL to set 312 | */ 313 | public void setExpansionURL(int index, String URL) { 314 | if (index >= mExpansionURLs.size()) { 315 | mExpansionURLs.setSize(index + 1); 316 | } 317 | mExpansionURLs.set(index, URL); 318 | } 319 | 320 | public String getExpansionFileName(int index) { 321 | if (index < mExpansionFileNames.size()) { 322 | return mExpansionFileNames.elementAt(index); 323 | } 324 | return null; 325 | } 326 | 327 | public void setExpansionFileName(int index, String name) { 328 | if (index >= mExpansionFileNames.size()) { 329 | mExpansionFileNames.setSize(index + 1); 330 | } 331 | mExpansionFileNames.set(index, name); 332 | } 333 | 334 | public long getExpansionFileSize(int index) { 335 | if (index < mExpansionFileSizes.size()) { 336 | return mExpansionFileSizes.elementAt(index); 337 | } 338 | return -1; 339 | } 340 | 341 | public void setExpansionFileSize(int index, long size) { 342 | if (index >= mExpansionFileSizes.size()) { 343 | mExpansionFileSizes.setSize(index + 1); 344 | } 345 | mExpansionFileSizes.set(index, size); 346 | } 347 | 348 | /** 349 | * {@inheritDoc} This implementation allows access if either:
350 | *
    351 | *
  1. a LICENSED response was received within the validity period 352 | *
  2. a RETRY response was received in the last minute, and we are under 353 | * the RETRY count or in the RETRY period. 354 | *
355 | */ 356 | public boolean allowAccess() { 357 | long ts = System.currentTimeMillis(); 358 | if (mLastResponse == Policy.LICENSED) { 359 | // Check if the LICENSED response occurred within the validity 360 | // timeout. 361 | if (ts <= mValidityTimestamp) { 362 | // Cached LICENSED response is still valid. 363 | return true; 364 | } 365 | } else if (mLastResponse == Policy.RETRY && 366 | ts < mLastResponseTime + MILLIS_PER_MINUTE) { 367 | // Only allow access if we are within the retry period or we haven't 368 | // used up our 369 | // max retries. 370 | return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); 371 | } 372 | return false; 373 | } 374 | 375 | private Map decodeExtras(String extras) { 376 | Map results = new HashMap(); 377 | try { 378 | URI rawExtras = new URI("?" + extras); 379 | URIQueryDecoder.DecodeQuery(rawExtras, results); 380 | } catch (URISyntaxException e) { 381 | Log.w(TAG, "Invalid syntax error while decoding extras data from server."); 382 | } 383 | return results; 384 | } 385 | 386 | } 387 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/DeviceLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | /** 20 | * Allows the developer to limit the number of devices using a single license. 21 | *

22 | * The LICENSED response from the server contains a user identifier unique to 23 | * the <application, user> pair. The developer can send this identifier 24 | * to their own server along with some device identifier (a random number 25 | * generated and stored once per application installation, 26 | * {@link android.telephony.TelephonyManager#getDeviceId getDeviceId}, 27 | * {@link android.provider.Settings.Secure#ANDROID_ID ANDROID_ID}, etc). 28 | * The more sources used to identify the device, the harder it will be for an 29 | * attacker to spoof. 30 | *

31 | * The server can look at the <application, user, device id> tuple and 32 | * restrict a user's application license to run on at most 10 different devices 33 | * in a week (for example). We recommend not being too restrictive because a 34 | * user might legitimately have multiple devices or be in the process of 35 | * changing phones. This will catch egregious violations of multiple people 36 | * sharing one license. 37 | */ 38 | public interface DeviceLimiter { 39 | 40 | /** 41 | * Checks if this device is allowed to use the given user's license. 42 | * 43 | * @param userId the user whose license the server responded with 44 | * @return LICENSED if the device is allowed, NOT_LICENSED if not, RETRY if an error occurs 45 | */ 46 | int isDeviceAllowed(String userId); 47 | } 48 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseCheckerCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | /** 20 | * Callback for the license checker library. 21 | *

22 | * Upon checking with the Market server and conferring with the {@link Policy}, 23 | * the library calls the appropriate callback method to communicate the result. 24 | *

25 | * The callback does not occur in the original checking thread. Your 26 | * application should post to the appropriate handling thread or lock 27 | * accordingly. 28 | *

29 | * The reason that is passed back with allow/dontAllow is the base status handed 30 | * to the policy for allowed/disallowing the license. Policy.RETRY will call 31 | * allow or dontAllow depending on other statistics associated with the policy, 32 | * while in most cases Policy.NOT_LICENSED will call dontAllow and 33 | * Policy.LICENSED will Allow. 34 | */ 35 | public interface LicenseCheckerCallback { 36 | 37 | /** 38 | * Allow use. App should proceed as normal. 39 | * 40 | * @param reason Policy.LICENSED or Policy.RETRY typically. (although in 41 | * theory the policy can return Policy.NOT_LICENSED here as well) 42 | */ 43 | public void allow(int reason); 44 | 45 | /** 46 | * Don't allow use. App should inform user and take appropriate action. 47 | * 48 | * @param reason Policy.NOT_LICENSED or Policy.RETRY. (although in theory 49 | * the policy can return Policy.LICENSED here as well --- 50 | * perhaps the call to the LVL took too long, for example) 51 | */ 52 | public void dontAllow(int reason); 53 | 54 | /** Application error codes. */ 55 | public static final int ERROR_INVALID_PACKAGE_NAME = 1; 56 | public static final int ERROR_NON_MATCHING_UID = 2; 57 | public static final int ERROR_NOT_MARKET_MANAGED = 3; 58 | public static final int ERROR_CHECK_IN_PROGRESS = 4; 59 | public static final int ERROR_INVALID_PUBLIC_KEY = 5; 60 | public static final int ERROR_MISSING_PERMISSION = 6; 61 | 62 | /** 63 | * Error in application code. Caller did not call or set up license checker 64 | * correctly. Should be considered fatal. 65 | */ 66 | public void applicationError(int errorCode); 67 | } 68 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | import com.google.android.vending.licensing.util.Base64; 20 | import com.google.android.vending.licensing.util.Base64DecoderException; 21 | 22 | import android.text.TextUtils; 23 | import android.util.Log; 24 | 25 | import java.security.InvalidKeyException; 26 | import java.security.NoSuchAlgorithmException; 27 | import java.security.PublicKey; 28 | import java.security.Signature; 29 | import java.security.SignatureException; 30 | 31 | /** 32 | * Contains data related to a licensing request and methods to verify 33 | * and process the response. 34 | */ 35 | class LicenseValidator { 36 | private static final String TAG = "LicenseValidator"; 37 | 38 | // Server response codes. 39 | private static final int LICENSED = 0x0; 40 | private static final int NOT_LICENSED = 0x1; 41 | private static final int LICENSED_OLD_KEY = 0x2; 42 | private static final int ERROR_NOT_MARKET_MANAGED = 0x3; 43 | private static final int ERROR_SERVER_FAILURE = 0x4; 44 | private static final int ERROR_OVER_QUOTA = 0x5; 45 | 46 | private static final int ERROR_CONTACTING_SERVER = 0x101; 47 | private static final int ERROR_INVALID_PACKAGE_NAME = 0x102; 48 | private static final int ERROR_NON_MATCHING_UID = 0x103; 49 | 50 | private final Policy mPolicy; 51 | private final LicenseCheckerCallback mCallback; 52 | private final int mNonce; 53 | private final String mPackageName; 54 | private final String mVersionCode; 55 | private final DeviceLimiter mDeviceLimiter; 56 | 57 | LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCheckerCallback callback, 58 | int nonce, String packageName, String versionCode) { 59 | mPolicy = policy; 60 | mDeviceLimiter = deviceLimiter; 61 | mCallback = callback; 62 | mNonce = nonce; 63 | mPackageName = packageName; 64 | mVersionCode = versionCode; 65 | } 66 | 67 | public LicenseCheckerCallback getCallback() { 68 | return mCallback; 69 | } 70 | 71 | public int getNonce() { 72 | return mNonce; 73 | } 74 | 75 | public String getPackageName() { 76 | return mPackageName; 77 | } 78 | 79 | private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; 80 | 81 | /** 82 | * Verifies the response from server and calls appropriate callback method. 83 | * 84 | * @param publicKey public key associated with the developer account 85 | * @param responseCode server response code 86 | * @param signedData signed data from server 87 | * @param signature server signature 88 | */ 89 | public void verify(PublicKey publicKey, int responseCode, String signedData, String signature) { 90 | String userId = null; 91 | // Skip signature check for unsuccessful requests 92 | ResponseData data = null; 93 | if (responseCode == LICENSED || responseCode == NOT_LICENSED || 94 | responseCode == LICENSED_OLD_KEY) { 95 | // Verify signature. 96 | try { 97 | if (TextUtils.isEmpty(signedData)) { 98 | Log.e(TAG, "Signature verification failed: signedData is empty. " + 99 | "(Device not signed-in to any Google accounts?)"); 100 | handleInvalidResponse(); 101 | return; 102 | } 103 | 104 | Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); 105 | sig.initVerify(publicKey); 106 | sig.update(signedData.getBytes()); 107 | 108 | if (!sig.verify(Base64.decode(signature))) { 109 | Log.e(TAG, "Signature verification failed."); 110 | handleInvalidResponse(); 111 | return; 112 | } 113 | } catch (NoSuchAlgorithmException e) { 114 | // This can't happen on an Android compatible device. 115 | throw new RuntimeException(e); 116 | } catch (InvalidKeyException e) { 117 | handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PUBLIC_KEY); 118 | return; 119 | } catch (SignatureException e) { 120 | throw new RuntimeException(e); 121 | } catch (Base64DecoderException e) { 122 | Log.e(TAG, "Could not Base64-decode signature."); 123 | handleInvalidResponse(); 124 | return; 125 | } 126 | 127 | // Parse and validate response. 128 | try { 129 | data = ResponseData.parse(signedData); 130 | } catch (IllegalArgumentException e) { 131 | Log.e(TAG, "Could not parse response."); 132 | handleInvalidResponse(); 133 | return; 134 | } 135 | 136 | if (data.responseCode != responseCode) { 137 | Log.e(TAG, "Response codes don't match."); 138 | handleInvalidResponse(); 139 | return; 140 | } 141 | 142 | if (data.nonce != mNonce) { 143 | Log.e(TAG, "Nonce doesn't match."); 144 | handleInvalidResponse(); 145 | return; 146 | } 147 | 148 | if (!data.packageName.equals(mPackageName)) { 149 | Log.e(TAG, "Package name doesn't match."); 150 | handleInvalidResponse(); 151 | return; 152 | } 153 | 154 | if (!data.versionCode.equals(mVersionCode)) { 155 | Log.e(TAG, "Version codes don't match."); 156 | handleInvalidResponse(); 157 | return; 158 | } 159 | 160 | // Application-specific user identifier. 161 | userId = data.userId; 162 | if (TextUtils.isEmpty(userId)) { 163 | Log.e(TAG, "User identifier is empty."); 164 | handleInvalidResponse(); 165 | return; 166 | } 167 | } 168 | 169 | switch (responseCode) { 170 | case LICENSED: 171 | case LICENSED_OLD_KEY: 172 | int limiterResponse = mDeviceLimiter.isDeviceAllowed(userId); 173 | handleResponse(limiterResponse, data); 174 | break; 175 | case NOT_LICENSED: 176 | handleResponse(Policy.NOT_LICENSED, data); 177 | break; 178 | case ERROR_CONTACTING_SERVER: 179 | Log.w(TAG, "Error contacting licensing server."); 180 | handleResponse(Policy.RETRY, data); 181 | break; 182 | case ERROR_SERVER_FAILURE: 183 | Log.w(TAG, "An error has occurred on the licensing server."); 184 | handleResponse(Policy.RETRY, data); 185 | break; 186 | case ERROR_OVER_QUOTA: 187 | Log.w(TAG, "Licensing server is refusing to talk to this device, over quota."); 188 | handleResponse(Policy.RETRY, data); 189 | break; 190 | case ERROR_INVALID_PACKAGE_NAME: 191 | handleApplicationError(LicenseCheckerCallback.ERROR_INVALID_PACKAGE_NAME); 192 | break; 193 | case ERROR_NON_MATCHING_UID: 194 | handleApplicationError(LicenseCheckerCallback.ERROR_NON_MATCHING_UID); 195 | break; 196 | case ERROR_NOT_MARKET_MANAGED: 197 | handleApplicationError(LicenseCheckerCallback.ERROR_NOT_MARKET_MANAGED); 198 | break; 199 | default: 200 | Log.e(TAG, "Unknown response code for license check."); 201 | handleInvalidResponse(); 202 | } 203 | } 204 | 205 | /** 206 | * Confers with policy and calls appropriate callback method. 207 | * 208 | * @param response 209 | * @param rawData 210 | */ 211 | private void handleResponse(int response, ResponseData rawData) { 212 | // Update policy data and increment retry counter (if needed) 213 | mPolicy.processServerResponse(response, rawData); 214 | 215 | // Given everything we know, including cached data, ask the policy if we should grant 216 | // access. 217 | if (mPolicy.allowAccess()) { 218 | mCallback.allow(response); 219 | } else { 220 | mCallback.dontAllow(response); 221 | } 222 | } 223 | 224 | private void handleApplicationError(int code) { 225 | mCallback.applicationError(code); 226 | } 227 | 228 | private void handleInvalidResponse() { 229 | mCallback.dontAllow(Policy.NOT_LICENSED); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/NullDeviceLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | /** 20 | * A DeviceLimiter that doesn't limit the number of devices that can use a 21 | * given user's license. 22 | *

23 | * Unless you have reason to believe that your application is being pirated 24 | * by multiple users using the same license (signing in to Market as the same 25 | * user), we recommend you use this implementation. 26 | */ 27 | public class NullDeviceLimiter implements DeviceLimiter { 28 | 29 | public int isDeviceAllowed(String userId) { 30 | return Policy.LICENSED; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/Obfuscator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | /** 20 | * Interface used as part of a {@link Policy} to allow application authors to obfuscate 21 | * licensing data that will be stored into a SharedPreferences file. 22 | *

23 | * Any transformation scheme must be reversable. Implementing classes may optionally implement an 24 | * integrity check to further prevent modification to preference data. Implementing classes 25 | * should use device-specific information as a key in the obfuscation algorithm to prevent 26 | * obfuscated preferences from being shared among devices. 27 | */ 28 | public interface Obfuscator { 29 | 30 | /** 31 | * Obfuscate a string that is being stored into shared preferences. 32 | * 33 | * @param original The data that is to be obfuscated. 34 | * @param key The key for the data that is to be obfuscated. 35 | * @return A transformed version of the original data. 36 | */ 37 | String obfuscate(String original, String key); 38 | 39 | /** 40 | * Undo the transformation applied to data by the obfuscate() method. 41 | * 42 | * @param original The data that is to be obfuscated. 43 | * @param key The key for the data that is to be obfuscated. 44 | * @return A transformed version of the original data. 45 | * @throws ValidationException Optionally thrown if a data integrity check fails. 46 | */ 47 | String unobfuscate(String obfuscated, String key) throws ValidationException; 48 | } 49 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/Policy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | /** 20 | * Policy used by {@link LicenseChecker} to determine whether a user should have 21 | * access to the application. 22 | */ 23 | public interface Policy { 24 | 25 | /** 26 | * Change these values to make it more difficult for tools to automatically 27 | * strip LVL protection from your APK. 28 | */ 29 | 30 | /** 31 | * LICENSED means that the server returned back a valid license response 32 | */ 33 | public static final int LICENSED = 0x0100; 34 | /** 35 | * NOT_LICENSED means that the server returned back a valid license response 36 | * that indicated that the user definitively is not licensed 37 | */ 38 | public static final int NOT_LICENSED = 0x0231; 39 | /** 40 | * RETRY means that the license response was unable to be determined --- 41 | * perhaps as a result of faulty networking 42 | */ 43 | public static final int RETRY = 0x0123; 44 | 45 | /** 46 | * Provide results from contact with the license server. Retry counts are 47 | * incremented if the current value of response is RETRY. Results will be 48 | * used for any future policy decisions. 49 | * 50 | * @param response the result from validating the server response 51 | * @param rawData the raw server response data, can be null for RETRY 52 | */ 53 | void processServerResponse(int response, ResponseData rawData); 54 | 55 | /** 56 | * Check if the user should be allowed access to the application. 57 | */ 58 | boolean allowAccess(); 59 | } 60 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/PreferenceObfuscator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | import android.content.SharedPreferences; 20 | import android.util.Log; 21 | 22 | /** 23 | * An wrapper for SharedPreferences that transparently performs data obfuscation. 24 | */ 25 | public class PreferenceObfuscator { 26 | 27 | private static final String TAG = "PreferenceObfuscator"; 28 | 29 | private final SharedPreferences mPreferences; 30 | private final Obfuscator mObfuscator; 31 | private SharedPreferences.Editor mEditor; 32 | 33 | /** 34 | * Constructor. 35 | * 36 | * @param sp A SharedPreferences instance provided by the system. 37 | * @param o The Obfuscator to use when reading or writing data. 38 | */ 39 | public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) { 40 | mPreferences = sp; 41 | mObfuscator = o; 42 | mEditor = null; 43 | } 44 | 45 | public void putString(String key, String value) { 46 | if (mEditor == null) { 47 | mEditor = mPreferences.edit(); 48 | } 49 | String obfuscatedValue = mObfuscator.obfuscate(value, key); 50 | mEditor.putString(key, obfuscatedValue); 51 | } 52 | 53 | public String getString(String key, String defValue) { 54 | String result; 55 | String value = mPreferences.getString(key, null); 56 | if (value != null) { 57 | try { 58 | result = mObfuscator.unobfuscate(value, key); 59 | } catch (ValidationException e) { 60 | // Unable to unobfuscate, data corrupt or tampered 61 | Log.w(TAG, "Validation error while reading preference: " + key); 62 | result = defValue; 63 | } 64 | } else { 65 | // Preference not found 66 | result = defValue; 67 | } 68 | return result; 69 | } 70 | 71 | public void commit() { 72 | if (mEditor != null) { 73 | mEditor.commit(); 74 | mEditor = null; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ResponseData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | import android.text.TextUtils; 20 | 21 | import java.util.regex.Pattern; 22 | 23 | /** 24 | * ResponseData from licensing server. 25 | */ 26 | public class ResponseData { 27 | 28 | public int responseCode; 29 | public int nonce; 30 | public String packageName; 31 | public String versionCode; 32 | public String userId; 33 | public long timestamp; 34 | /** Response-specific data. */ 35 | public String extra; 36 | 37 | /** 38 | * Parses response string into ResponseData. 39 | * 40 | * @param responseData response data string 41 | * @throws IllegalArgumentException upon parsing error 42 | * @return ResponseData object 43 | */ 44 | public static ResponseData parse(String responseData) { 45 | // Must parse out main response data and response-specific data. 46 | int index = responseData.indexOf(':'); 47 | String mainData, extraData; 48 | if (-1 == index) { 49 | mainData = responseData; 50 | extraData = ""; 51 | } else { 52 | mainData = responseData.substring(0, index); 53 | extraData = index >= responseData.length() ? "" : responseData.substring(index + 1); 54 | } 55 | 56 | String[] fields = TextUtils.split(mainData, Pattern.quote("|")); 57 | if (fields.length < 6) { 58 | throw new IllegalArgumentException("Wrong number of fields."); 59 | } 60 | 61 | ResponseData data = new ResponseData(); 62 | data.extra = extraData; 63 | data.responseCode = Integer.parseInt(fields[0]); 64 | data.nonce = Integer.parseInt(fields[1]); 65 | data.packageName = fields[2]; 66 | data.versionCode = fields[3]; 67 | // Application-specific user identifier. 68 | data.userId = fields[4]; 69 | data.timestamp = Long.parseLong(fields[5]); 70 | 71 | return data; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return TextUtils.join("|", new Object[] { 77 | responseCode, nonce, packageName, versionCode, 78 | userId, timestamp 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ServerManagedPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | import java.net.URI; 20 | import java.net.URISyntaxException; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | import android.content.Context; 25 | import android.content.SharedPreferences; 26 | import android.util.Log; 27 | 28 | import com.google.android.vending.licensing.util.URIQueryDecoder; 29 | 30 | /** 31 | * Default policy. All policy decisions are based off of response data received 32 | * from the licensing service. Specifically, the licensing server sends the 33 | * following information: response validity period, error retry period, and 34 | * error retry count. 35 | *

36 | * These values will vary based on the the way the application is configured in 37 | * the Google Play publishing console, such as whether the application is 38 | * marked as free or is within its refund period, as well as how often an 39 | * application is checking with the licensing service. 40 | *

41 | * Developers who need more fine grained control over their application's 42 | * licensing policy should implement a custom Policy. 43 | */ 44 | public class ServerManagedPolicy implements Policy { 45 | 46 | private static final String TAG = "ServerManagedPolicy"; 47 | private static final String PREFS_FILE = "com.google.android.vending.licensing.ServerManagedPolicy"; 48 | private static final String PREF_LAST_RESPONSE = "lastResponse"; 49 | private static final String PREF_VALIDITY_TIMESTAMP = "validityTimestamp"; 50 | private static final String PREF_RETRY_UNTIL = "retryUntil"; 51 | private static final String PREF_MAX_RETRIES = "maxRetries"; 52 | private static final String PREF_RETRY_COUNT = "retryCount"; 53 | private static final String DEFAULT_VALIDITY_TIMESTAMP = "0"; 54 | private static final String DEFAULT_RETRY_UNTIL = "0"; 55 | private static final String DEFAULT_MAX_RETRIES = "0"; 56 | private static final String DEFAULT_RETRY_COUNT = "0"; 57 | 58 | private static final long MILLIS_PER_MINUTE = 60 * 1000; 59 | 60 | private long mValidityTimestamp; 61 | private long mRetryUntil; 62 | private long mMaxRetries; 63 | private long mRetryCount; 64 | private long mLastResponseTime = 0; 65 | private int mLastResponse; 66 | private PreferenceObfuscator mPreferences; 67 | 68 | /** 69 | * @param context The context for the current application 70 | * @param obfuscator An obfuscator to be used with preferences. 71 | */ 72 | public ServerManagedPolicy(Context context, Obfuscator obfuscator) { 73 | // Import old values 74 | SharedPreferences sp = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE); 75 | mPreferences = new PreferenceObfuscator(sp, obfuscator); 76 | mLastResponse = Integer.parseInt( 77 | mPreferences.getString(PREF_LAST_RESPONSE, Integer.toString(Policy.RETRY))); 78 | mValidityTimestamp = Long.parseLong(mPreferences.getString(PREF_VALIDITY_TIMESTAMP, 79 | DEFAULT_VALIDITY_TIMESTAMP)); 80 | mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL)); 81 | mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES)); 82 | mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT)); 83 | } 84 | 85 | /** 86 | * Process a new response from the license server. 87 | *

88 | * This data will be used for computing future policy decisions. The 89 | * following parameters are processed: 90 | *

    91 | *
  • VT: the timestamp that the client should consider the response 92 | * valid until 93 | *
  • GT: the timestamp that the client should ignore retry errors until 94 | *
  • GR: the number of retry errors that the client should ignore 95 | *
96 | * 97 | * @param response the result from validating the server response 98 | * @param rawData the raw server response data 99 | */ 100 | public void processServerResponse(int response, ResponseData rawData) { 101 | 102 | // Update retry counter 103 | if (response != Policy.RETRY) { 104 | setRetryCount(0); 105 | } else { 106 | setRetryCount(mRetryCount + 1); 107 | } 108 | 109 | if (response == Policy.LICENSED) { 110 | // Update server policy data 111 | Map extras = decodeExtras(rawData.extra); 112 | mLastResponse = response; 113 | setValidityTimestamp(extras.get("VT")); 114 | setRetryUntil(extras.get("GT")); 115 | setMaxRetries(extras.get("GR")); 116 | } else if (response == Policy.NOT_LICENSED) { 117 | // Clear out stale policy data 118 | setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP); 119 | setRetryUntil(DEFAULT_RETRY_UNTIL); 120 | setMaxRetries(DEFAULT_MAX_RETRIES); 121 | } 122 | 123 | setLastResponse(response); 124 | mPreferences.commit(); 125 | } 126 | 127 | /** 128 | * Set the last license response received from the server and add to 129 | * preferences. You must manually call PreferenceObfuscator.commit() to 130 | * commit these changes to disk. 131 | * 132 | * @param l the response 133 | */ 134 | private void setLastResponse(int l) { 135 | mLastResponseTime = System.currentTimeMillis(); 136 | mLastResponse = l; 137 | mPreferences.putString(PREF_LAST_RESPONSE, Integer.toString(l)); 138 | } 139 | 140 | /** 141 | * Set the current retry count and add to preferences. You must manually 142 | * call PreferenceObfuscator.commit() to commit these changes to disk. 143 | * 144 | * @param c the new retry count 145 | */ 146 | private void setRetryCount(long c) { 147 | mRetryCount = c; 148 | mPreferences.putString(PREF_RETRY_COUNT, Long.toString(c)); 149 | } 150 | 151 | public long getRetryCount() { 152 | return mRetryCount; 153 | } 154 | 155 | /** 156 | * Set the last validity timestamp (VT) received from the server and add to 157 | * preferences. You must manually call PreferenceObfuscator.commit() to 158 | * commit these changes to disk. 159 | * 160 | * @param validityTimestamp the VT string received 161 | */ 162 | private void setValidityTimestamp(String validityTimestamp) { 163 | Long lValidityTimestamp; 164 | try { 165 | lValidityTimestamp = Long.parseLong(validityTimestamp); 166 | } catch (NumberFormatException e) { 167 | // No response or not parsable, expire in one minute. 168 | Log.w(TAG, "License validity timestamp (VT) missing, caching for a minute"); 169 | lValidityTimestamp = System.currentTimeMillis() + MILLIS_PER_MINUTE; 170 | validityTimestamp = Long.toString(lValidityTimestamp); 171 | } 172 | 173 | mValidityTimestamp = lValidityTimestamp; 174 | mPreferences.putString(PREF_VALIDITY_TIMESTAMP, validityTimestamp); 175 | } 176 | 177 | public long getValidityTimestamp() { 178 | return mValidityTimestamp; 179 | } 180 | 181 | /** 182 | * Set the retry until timestamp (GT) received from the server and add to 183 | * preferences. You must manually call PreferenceObfuscator.commit() to 184 | * commit these changes to disk. 185 | * 186 | * @param retryUntil the GT string received 187 | */ 188 | private void setRetryUntil(String retryUntil) { 189 | Long lRetryUntil; 190 | try { 191 | lRetryUntil = Long.parseLong(retryUntil); 192 | } catch (NumberFormatException e) { 193 | // No response or not parsable, expire immediately 194 | Log.w(TAG, "License retry timestamp (GT) missing, grace period disabled"); 195 | retryUntil = "0"; 196 | lRetryUntil = 0l; 197 | } 198 | 199 | mRetryUntil = lRetryUntil; 200 | mPreferences.putString(PREF_RETRY_UNTIL, retryUntil); 201 | } 202 | 203 | public long getRetryUntil() { 204 | return mRetryUntil; 205 | } 206 | 207 | /** 208 | * Set the max retries value (GR) as received from the server and add to 209 | * preferences. You must manually call PreferenceObfuscator.commit() to 210 | * commit these changes to disk. 211 | * 212 | * @param maxRetries the GR string received 213 | */ 214 | private void setMaxRetries(String maxRetries) { 215 | Long lMaxRetries; 216 | try { 217 | lMaxRetries = Long.parseLong(maxRetries); 218 | } catch (NumberFormatException e) { 219 | // No response or not parsable, expire immediately 220 | Log.w(TAG, "Licence retry count (GR) missing, grace period disabled"); 221 | maxRetries = "0"; 222 | lMaxRetries = 0l; 223 | } 224 | 225 | mMaxRetries = lMaxRetries; 226 | mPreferences.putString(PREF_MAX_RETRIES, maxRetries); 227 | } 228 | 229 | public long getMaxRetries() { 230 | return mMaxRetries; 231 | } 232 | 233 | /** 234 | * {@inheritDoc} 235 | * 236 | * This implementation allows access if either:
237 | *
    238 | *
  1. a LICENSED response was received within the validity period 239 | *
  2. a RETRY response was received in the last minute, and we are under 240 | * the RETRY count or in the RETRY period. 241 | *
242 | */ 243 | public boolean allowAccess() { 244 | long ts = System.currentTimeMillis(); 245 | if (mLastResponse == Policy.LICENSED) { 246 | // Check if the LICENSED response occurred within the validity timeout. 247 | if (ts <= mValidityTimestamp) { 248 | // Cached LICENSED response is still valid. 249 | return true; 250 | } 251 | } else if (mLastResponse == Policy.RETRY && 252 | ts < mLastResponseTime + MILLIS_PER_MINUTE) { 253 | // Only allow access if we are within the retry period or we haven't used up our 254 | // max retries. 255 | return (ts <= mRetryUntil || mRetryCount <= mMaxRetries); 256 | } 257 | return false; 258 | } 259 | 260 | private Map decodeExtras(String extras) { 261 | Map results = new HashMap(); 262 | try { 263 | URI rawExtras = new URI("?" + extras); 264 | URIQueryDecoder.DecodeQuery(rawExtras, results); 265 | } catch (URISyntaxException e) { 266 | Log.w(TAG, "Invalid syntax error while decoding extras data from server."); 267 | } 268 | return results; 269 | } 270 | 271 | } 272 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/StrictPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | /** 20 | * Non-caching policy. All requests will be sent to the licensing service, 21 | * and no local caching is performed. 22 | *

23 | * Using a non-caching policy ensures that there is no local preference data 24 | * for malicious users to tamper with. As a side effect, applications 25 | * will not be permitted to run while offline. Developers should carefully 26 | * weigh the risks of using this Policy over one which implements caching, 27 | * such as ServerManagedPolicy. 28 | *

29 | * Access to the application is only allowed if a LICESNED response is. 30 | * received. All other responses (including RETRY) will deny access. 31 | */ 32 | public class StrictPolicy implements Policy { 33 | 34 | private int mLastResponse; 35 | 36 | public StrictPolicy() { 37 | // Set default policy. This will force the application to check the policy on launch. 38 | mLastResponse = Policy.RETRY; 39 | } 40 | 41 | /** 42 | * Process a new response from the license server. Since we aren't 43 | * performing any caching, this equates to reading the LicenseResponse. 44 | * Any ResponseData provided is ignored. 45 | * 46 | * @param response the result from validating the server response 47 | * @param rawData the raw server response data 48 | */ 49 | public void processServerResponse(int response, ResponseData rawData) { 50 | mLastResponse = response; 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | * 56 | * This implementation allows access if and only if a LICENSED response 57 | * was received the last time the server was contacted. 58 | */ 59 | public boolean allowAccess() { 60 | return (mLastResponse == Policy.LICENSED); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ValidationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing; 18 | 19 | /** 20 | * Indicates that an error occurred while validating the integrity of data managed by an 21 | * {@link Obfuscator}.} 22 | */ 23 | public class ValidationException extends Exception { 24 | public ValidationException() { 25 | super(); 26 | } 27 | 28 | public ValidationException(String s) { 29 | super(s); 30 | } 31 | 32 | private static final long serialVersionUID = 1L; 33 | } 34 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/Base64DecoderException.java: -------------------------------------------------------------------------------- 1 | // Copyright 2002, Google, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package com.google.android.vending.licensing.util; 16 | 17 | /** 18 | * Exception thrown when encountering an invalid Base64 input character. 19 | * 20 | * @author nelson 21 | */ 22 | public class Base64DecoderException extends Exception { 23 | public Base64DecoderException() { 24 | super(); 25 | } 26 | 27 | public Base64DecoderException(String s) { 28 | super(s); 29 | } 30 | 31 | private static final long serialVersionUID = 1L; 32 | } 33 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/URIQueryDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.android.vending.licensing.util; 18 | 19 | import android.util.Log; 20 | 21 | import java.io.UnsupportedEncodingException; 22 | import java.net.URI; 23 | import java.net.URLDecoder; 24 | import java.util.Map; 25 | import java.util.Scanner; 26 | 27 | public class URIQueryDecoder { 28 | private static final String TAG = "URIQueryDecoder"; 29 | 30 | /** 31 | * Decodes the query portion of the passed-in URI. 32 | * 33 | * @param encodedURI the URI containing the query to decode 34 | * @param results a map containing all query parameters. Query parameters that do not have a 35 | * value will map to a null string 36 | */ 37 | static public void DecodeQuery(URI encodedURI, Map results) { 38 | Scanner scanner = new Scanner(encodedURI.getRawQuery()); 39 | scanner.useDelimiter("&"); 40 | try { 41 | while (scanner.hasNext()) { 42 | String param = scanner.next(); 43 | String[] valuePair = param.split("="); 44 | String name, value; 45 | if (valuePair.length == 1) { 46 | value = null; 47 | } else if (valuePair.length == 2) { 48 | value = URLDecoder.decode(valuePair[1], "UTF-8"); 49 | } else { 50 | throw new IllegalArgumentException("query parameter invalid"); 51 | } 52 | name = URLDecoder.decode(valuePair[0], "UTF-8"); 53 | results.put(name, value); 54 | } 55 | } catch (UnsupportedEncodingException e) { 56 | // This should never happen. 57 | Log.e(TAG, "UTF-8 Not Recognized as a charset. Device configuration Error."); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityAlarmReceiver.java: -------------------------------------------------------------------------------- 1 | package com.unity3d.plugin.downloader; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager.NameNotFoundException; 7 | 8 | import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; 9 | 10 | public class UnityAlarmReceiver extends BroadcastReceiver { 11 | 12 | @Override 13 | public void onReceive(Context context, Intent intent) { 14 | try { 15 | DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, UnityDownloaderService.class); 16 | } catch (NameNotFoundException e) { 17 | e.printStackTrace(); 18 | } 19 | } 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityDownloaderActivity.java: -------------------------------------------------------------------------------- 1 | package com.unity3d.plugin.downloader; 2 | 3 | import android.app.Activity; 4 | import android.app.PendingIntent; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | import android.content.Intent; 10 | import android.os.Messenger; 11 | import android.content.pm.PackageManager.NameNotFoundException; 12 | import android.view.View; 13 | import android.view.Window; 14 | import android.widget.Button; 15 | import android.widget.ImageView; 16 | import android.widget.ProgressBar; 17 | import android.widget.TextView; 18 | import android.provider.Settings; 19 | 20 | import java.io.InputStream; 21 | 22 | import com.google.android.vending.expansion.downloader.*; 23 | 24 | public class UnityDownloaderActivity extends Activity implements IDownloaderClient 25 | { 26 | private ProgressBar mPB; 27 | 28 | private TextView mStatusText; 29 | private TextView mProgressFraction; 30 | private TextView mProgressPercent; 31 | private TextView mAverageSpeed; 32 | private TextView mTimeRemaining; 33 | 34 | private View mDashboard; 35 | private View mCellMessage; 36 | 37 | private Button mPauseButton; 38 | private Button mWiFiSettingsButton; 39 | 40 | private boolean mStatePaused; 41 | private int mState; 42 | 43 | private IDownloaderService mRemoteService; 44 | 45 | private IStub mDownloaderClientStub; 46 | private static final String LOG_TAG = "OBB"; 47 | 48 | @Override 49 | public void onCreate(Bundle savedInstanceState) 50 | { 51 | super.onCreate(savedInstanceState); 52 | 53 | requestWindowFeature(Window.FEATURE_NO_TITLE); 54 | 55 | try { 56 | Intent launchIntent = getIntent(); 57 | Class mainActivity = Class.forName(launchIntent.getStringExtra("unityplayer.Activity")); 58 | Intent intentToLaunchMainActivityFromNotification = new Intent(this, mainActivity); 59 | intentToLaunchMainActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 60 | intentToLaunchMainActivityFromNotification.setAction("android.intent.action.MAIN"); 61 | intentToLaunchMainActivityFromNotification.addCategory("android.intent.category.LAUNCHER"); 62 | 63 | // Build PendingIntent used to open this activity from Notification 64 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intentToLaunchMainActivityFromNotification, PendingIntent.FLAG_UPDATE_CURRENT); 65 | // Request to start the download 66 | int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, UnityDownloaderService.class); 67 | 68 | if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { 69 | // The DownloaderService has started downloading the files, show progress 70 | initializeDownloadUI(); 71 | return; 72 | } // otherwise, download not needed so we fall through to starting the movie 73 | } catch (ClassNotFoundException e) { 74 | android.util.Log.e(LOG_TAG, "Cannot find own package! MAYDAY!"); 75 | e.printStackTrace(); 76 | } catch (NameNotFoundException e) { 77 | android.util.Log.e(LOG_TAG, "Cannot find own package! MAYDAY!"); 78 | e.printStackTrace(); 79 | } 80 | finish(); 81 | } 82 | 83 | /** 84 | * Connect the stub to our service on resume. 85 | */ 86 | @Override 87 | protected void onResume() { 88 | if (null != mDownloaderClientStub) { 89 | mDownloaderClientStub.connect(this); 90 | } 91 | super.onResume(); 92 | } 93 | 94 | /** 95 | * Disconnect the stub from our service on stop 96 | */ 97 | @Override 98 | protected void onStop() { 99 | if (null != mDownloaderClientStub) { 100 | mDownloaderClientStub.disconnect(this); 101 | } 102 | super.onStop(); 103 | } 104 | 105 | private void initializeDownloadUI() { 106 | mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, UnityDownloaderService.class); 107 | setContentView(Helpers.getLayoutResource(this, "main")); 108 | 109 | // Set the background to the splash image generated by Unity 110 | try { 111 | InputStream is = getAssets().open("bin/Data/splash.png"); 112 | Bitmap splashBitmap = null; 113 | BitmapFactory.Options options = new BitmapFactory.Options(); 114 | options.inPreferredConfig = Bitmap.Config.ARGB_8888; 115 | splashBitmap = BitmapFactory.decodeStream(is, null, options); 116 | is.close(); 117 | ImageView splashImage = (ImageView) findViewById(Helpers.getIdResource(this, "splashImage")); 118 | splashImage.setImageBitmap(splashBitmap); 119 | } catch(Exception e) { } 120 | 121 | mPB = (ProgressBar) findViewById(Helpers.getIdResource(this, "progressBar")); 122 | mStatusText = (TextView) findViewById(Helpers.getIdResource(this, "statusText")); 123 | mProgressFraction = (TextView) findViewById(Helpers.getIdResource(this, "progressAsFraction")); 124 | mProgressPercent = (TextView) findViewById(Helpers.getIdResource(this, "progressAsPercentage")); 125 | mAverageSpeed = (TextView) findViewById(Helpers.getIdResource(this, "progressAverageSpeed")); 126 | mTimeRemaining = (TextView) findViewById(Helpers.getIdResource(this, "progressTimeRemaining")); 127 | mDashboard = findViewById(Helpers.getIdResource(this, "downloaderDashboard")); 128 | mCellMessage = findViewById(Helpers.getIdResource(this, "approveCellular")); 129 | mPauseButton = (Button) findViewById(Helpers.getIdResource(this, "pauseButton")); 130 | mWiFiSettingsButton = (Button) findViewById(Helpers.getIdResource(this, "wifiSettingsButton")); 131 | 132 | mPauseButton.setOnClickListener(new View.OnClickListener() { 133 | @Override 134 | public void onClick(View view) { 135 | if (mStatePaused) { 136 | mRemoteService.requestContinueDownload(); 137 | } else { 138 | mRemoteService.requestPauseDownload(); 139 | } 140 | setButtonPausedState(!mStatePaused); 141 | } 142 | }); 143 | 144 | mWiFiSettingsButton.setOnClickListener(new View.OnClickListener() { 145 | 146 | @Override 147 | public void onClick(View v) { 148 | startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)); 149 | } 150 | }); 151 | 152 | Button resumeOnCell = (Button) findViewById(Helpers.getIdResource(this, "resumeOverCellular")); 153 | resumeOnCell.setOnClickListener(new View.OnClickListener() { 154 | @Override 155 | public void onClick(View view) { 156 | mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR); 157 | mRemoteService.requestContinueDownload(); 158 | mCellMessage.setVisibility(View.GONE); 159 | } 160 | }); 161 | 162 | } 163 | private void setState(int newState) { 164 | if (mState != newState) { 165 | mState = newState; 166 | mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(this, newState)); 167 | } 168 | } 169 | 170 | private void setButtonPausedState(boolean paused) { 171 | mStatePaused = paused; 172 | int stringResourceID = Helpers.getStringResource(this, paused ? "text_button_resume" : "text_button_pause"); 173 | mPauseButton.setText(stringResourceID); 174 | } 175 | 176 | @Override 177 | public void onServiceConnected(Messenger m) { 178 | mRemoteService = DownloaderServiceMarshaller.CreateProxy(m); 179 | mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); 180 | } 181 | @Override 182 | public void onDownloadStateChanged(int newState) { 183 | setState(newState); 184 | boolean showDashboard = true; 185 | boolean showCellMessage = false; 186 | boolean paused; 187 | boolean indeterminate; 188 | switch (newState) { 189 | case IDownloaderClient.STATE_IDLE: 190 | // STATE_IDLE means the service is listening, so it's 191 | // safe to start making calls via mRemoteService. 192 | paused = false; 193 | indeterminate = true; 194 | break; 195 | case IDownloaderClient.STATE_CONNECTING: 196 | case IDownloaderClient.STATE_FETCHING_URL: 197 | showDashboard = true; 198 | paused = false; 199 | indeterminate = true; 200 | break; 201 | case IDownloaderClient.STATE_DOWNLOADING: 202 | paused = false; 203 | showDashboard = true; 204 | indeterminate = false; 205 | break; 206 | 207 | case IDownloaderClient.STATE_FAILED_CANCELED: 208 | case IDownloaderClient.STATE_FAILED: 209 | case IDownloaderClient.STATE_FAILED_FETCHING_URL: 210 | case IDownloaderClient.STATE_FAILED_UNLICENSED: 211 | paused = true; 212 | showDashboard = false; 213 | indeterminate = false; 214 | break; 215 | case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION: 216 | case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION: 217 | showDashboard = false; 218 | paused = true; 219 | indeterminate = false; 220 | showCellMessage = true; 221 | break; 222 | case IDownloaderClient.STATE_PAUSED_BY_REQUEST: 223 | paused = true; 224 | indeterminate = false; 225 | break; 226 | case IDownloaderClient.STATE_PAUSED_ROAMING: 227 | case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE: 228 | paused = true; 229 | indeterminate = false; 230 | break; 231 | case IDownloaderClient.STATE_COMPLETED: 232 | showDashboard = false; 233 | paused = false; 234 | indeterminate = false; 235 | finish(); 236 | return; 237 | default: 238 | paused = true; 239 | indeterminate = true; 240 | showDashboard = true; 241 | } 242 | int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE; 243 | if (mDashboard.getVisibility() != newDashboardVisibility) { 244 | mDashboard.setVisibility(newDashboardVisibility); 245 | } 246 | int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE; 247 | if (mCellMessage.getVisibility() != cellMessageVisibility) { 248 | mCellMessage.setVisibility(cellMessageVisibility); 249 | } 250 | mPB.setIndeterminate(indeterminate); 251 | setButtonPausedState(paused); 252 | } 253 | @Override 254 | public void onDownloadProgress(DownloadProgressInfo progress) { 255 | mAverageSpeed.setText(getString(Helpers.getStringResource(this, "kilobytes_per_second"), 256 | Helpers.getSpeedString(progress.mCurrentSpeed))); 257 | mTimeRemaining.setText(getString(Helpers.getStringResource(this, "time_remaining"), 258 | Helpers.getTimeRemaining(progress.mTimeRemaining))); 259 | 260 | progress.mOverallTotal = progress.mOverallTotal; 261 | mPB.setMax((int) (progress.mOverallTotal >> 8)); 262 | mPB.setProgress((int) (progress.mOverallProgress >> 8)); 263 | mProgressPercent.setText(Long.toString(progress.mOverallProgress 264 | * 100 / 265 | progress.mOverallTotal) + "%"); 266 | mProgressFraction.setText(Helpers.getDownloadProgressString 267 | (progress.mOverallProgress, 268 | progress.mOverallTotal)); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityDownloaderService.java: -------------------------------------------------------------------------------- 1 | package com.unity3d.plugin.downloader; 2 | 3 | import com.google.android.vending.expansion.downloader.impl.DownloaderService; 4 | 5 | public class UnityDownloaderService extends DownloaderService { 6 | // stuff for LVL -- MODIFIED FROM C# SCRIPTS! 7 | static String BASE64_PUBLIC_KEY = "REPLACE THIS WITH YOUR PUBLIC KEY - DONE FROM C#"; 8 | // used by the preference obfuscater 9 | static byte[] SALT = new byte[] { 10 | 1, 43, -12, -1, 54, 98, 11 | -100, -12, 43, 2, -8, -4, 9, 5, -106, -108, -33, 45, -1, 84 12 | }; 13 | 14 | /** 15 | * This public key comes from your Android Market publisher account, and it 16 | * used by the LVL to validate responses from Market on your behalf. 17 | */ 18 | @Override 19 | public String getPublicKey() { 20 | return BASE64_PUBLIC_KEY; 21 | } 22 | 23 | /** 24 | * This is used by the preference obfuscater to make sure that your 25 | * obfuscated preferences are different than the ones used by other 26 | * applications. 27 | */ 28 | @Override 29 | public byte[] getSALT() { 30 | return SALT; 31 | } 32 | 33 | /** 34 | * Fill this in with the class name for your alarm receiver. We do this 35 | * because receivers must be unique across all of Android (it's a good idea 36 | * to make sure that your receiver is in your unique package) 37 | */ 38 | @Override 39 | public String getAlarmReceiverClassName() { 40 | return UnityAlarmReceiver.class.getName(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/res/drawable-hdpi/notify_panel_notification_icon_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Over17/UnityOBBDownloader/d77e66f64c2738b2642f39c5ac91bc31ad464f12/src/unityOBBDownloader/src/main/res/drawable-hdpi/notify_panel_notification_icon_bg.png -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/res/drawable-mdpi/notify_panel_notification_icon_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Over17/UnityOBBDownloader/d77e66f64c2738b2642f39c5ac91bc31ad464f12/src/unityOBBDownloader/src/main/res/drawable-mdpi/notify_panel_notification_icon_bg.png -------------------------------------------------------------------------------- /src/unityOBBDownloader/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 15 | 16 | 17 | 22 | 23 | 31 | 32 | 38 | 39 | 43 | 44 | 52 | 53 | 54 | 61 | 62 | 73 | 74 | 82 | 83 | 90 | 91 | 92 | 97 | 98 |