Full Code of Over17/UnityOBBDownloader for AI

master d77e66f64c27 cached
58 files
335.5 KB
76.3k tokens
401 symbols
1 requests
Download .txt
Showing preview only (359K chars total). Download the full file or copy to clipboard to get everything.
Repository: Over17/UnityOBBDownloader
Branch: master
Commit: d77e66f64c27
Files: 58
Total size: 335.5 KB

Directory structure:
gitextract_2arvo09o/

├── .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/
                    ├── layout/
                    │   ├── main.xml
                    │   └── status_bar_ongoing_event_progress_bar.xml
                    └── values/
                        ├── main-strings.xml
                        ├── strings.xml
                        └── styles.xml

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
Library/
Packages/
ProjectSettings/
src/unityOBBDownloader/.gradle/
src/unityOBBDownloader/build/
Temp/
UnityPackageManager/
*.meta
*.csproj
*.sln
.vs/


================================================
FILE: Assets/Scripts/DownloadObbExample.cs
================================================
using UnityEngine;
using System.Collections;

public class DownloadObbExample : MonoBehaviour
{
    private IGooglePlayObbDownloader m_obbDownloader;
    void Start()
    {
        m_obbDownloader = GooglePlayObbDownloadManager.GetGooglePlayObbDownloader();
        m_obbDownloader.PublicKey = ""; // YOUR PUBLIC KEY HERE
    }	

    void OnGUI()
    {
        if (!GooglePlayObbDownloadManager.IsDownloaderAvailable())
        {
            GUI.Label(new Rect(10, 10, Screen.width-10, 20), "Use GooglePlayDownloader only on Android device!");
            return;
        }
        
        string expPath = m_obbDownloader.GetExpansionFilePath();
        if (expPath == null)
        {
                GUI.Label(new Rect(10, 10, Screen.width-10, 20), "External storage is not available!");
        }
        else
        {
            var mainPath = m_obbDownloader.GetMainOBBPath();
            var patchPath = m_obbDownloader.GetPatchOBBPath();
            
            GUI.Label(new Rect(10, 10, Screen.width-10, 20), "Main = ..."  + ( mainPath == null ? " NOT AVAILABLE" :  mainPath.Substring(expPath.Length)));
            GUI.Label(new Rect(10, 25, Screen.width-10, 20), "Patch = ..." + (patchPath == null ? " NOT AVAILABLE" : patchPath.Substring(expPath.Length)));
            if (mainPath == null || patchPath == null)
                if (GUI.Button(new Rect(10, 100, 100, 100), "Fetch OBBs"))
                    m_obbDownloader.FetchOBB();
        }

    }
}


================================================
FILE: Assets/Scripts/GooglePlayDownloader.cs
================================================
using UnityEngine;
using System;

public interface IGooglePlayObbDownloader
{
    string PublicKey { get; set; }

    string GetExpansionFilePath();
    string GetMainOBBPath();
    string GetPatchOBBPath();
    void FetchOBB();
}

public class GooglePlayObbDownloadManager
{
    private static AndroidJavaClass m_AndroidOSBuildClass = new AndroidJavaClass("android.os.Build");
    private static IGooglePlayObbDownloader m_Instance;

    public static IGooglePlayObbDownloader GetGooglePlayObbDownloader()
    {
        if (m_Instance != null)
            return m_Instance;

        if (!IsDownloaderAvailable())
            return null;

        m_Instance = new GooglePlayObbDownloader();
        return m_Instance;
    }

    public static bool IsDownloaderAvailable()
    {
        return m_AndroidOSBuildClass.GetRawClass() != IntPtr.Zero;
    }
}


================================================
FILE: Assets/Scripts/GooglePlayDownloaderImpl.cs
================================================
using UnityEngine;
using System.IO;
using System;

internal class GooglePlayObbDownloader : IGooglePlayObbDownloader
{
    private static AndroidJavaClass EnvironmentClass = new AndroidJavaClass("android.os.Environment");
    private const string Environment_MediaMounted = "mounted";

    public string PublicKey { get; set; }

    private void ApplyPublicKey()
    {
        if (string.IsNullOrEmpty(PublicKey))
        {
            Debug.LogError("GooglePlayObbDownloader: The public key is not set - did you forget to set it in the script?\n");
        }
        using (var downloaderServiceClass = new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderService"))
        {
            downloaderServiceClass.SetStatic("BASE64_PUBLIC_KEY", PublicKey);
            // Used by the preference obfuscator
            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 });
        }
    }

    public void FetchOBB()
    {
        ApplyPublicKey();
        using (var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            var currentActivity = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
            var intent = new AndroidJavaObject("android.content.Intent",
                                                currentActivity,
                                                new AndroidJavaClass("com.unity3d.plugin.downloader.UnityDownloaderActivity"));

            const int Intent_FLAG_ACTIVITY_NO_ANIMATION = 0x10000;
            intent.Call<AndroidJavaObject>("addFlags", Intent_FLAG_ACTIVITY_NO_ANIMATION);
            intent.Call<AndroidJavaObject>("putExtra", "unityplayer.Activity",
                                                        currentActivity.Call<AndroidJavaObject>("getClass").Call<string>("getName"));
            try
            {
                currentActivity.Call("startActivity", intent);
            }
            catch (Exception ex)
            {
                Debug.LogError("GooglePlayObbDownloader: Exception occurred while attempting to start DownloaderActivity - is the AndroidManifest.xml incorrect?\n" + ex.Message);
            }
        }
    }

    private string m_ExpansionFilePath;
    public string GetExpansionFilePath()
    {
        if (EnvironmentClass.CallStatic<string>("getExternalStorageState") != Environment_MediaMounted)
        {
            m_ExpansionFilePath = null;
            return m_ExpansionFilePath;
        }

        if (string.IsNullOrEmpty(m_ExpansionFilePath))
        {
            const string obbPath = "Android/obb";
            using (var externalStorageDirectory = EnvironmentClass.CallStatic<AndroidJavaObject>("getExternalStorageDirectory"))
            {
                var externalRoot = externalStorageDirectory.Call<string>("getPath");
                m_ExpansionFilePath = string.Format("{0}/{1}/{2}", externalRoot, obbPath, ObbPackage);
            }
        }
        return m_ExpansionFilePath;
    }

    public string GetMainOBBPath()
    {
        return GetOBBPackagePath(GetExpansionFilePath(), "main");
    }

    public string GetPatchOBBPath()
    {
        return GetOBBPackagePath(GetExpansionFilePath(), "patch");
    }

    private static string GetOBBPackagePath(string expansionFilePath, string prefix)
    {
        if (string.IsNullOrEmpty(expansionFilePath))
            return null;

        var filePath = string.Format("{0}/{1}.{2}.{3}.obb", expansionFilePath, prefix, ObbVersion, ObbPackage);
        return File.Exists(filePath) ? filePath : null;
    }

    private static string m_ObbPackage;
    private static string ObbPackage
    {
        get
        {
            if (m_ObbPackage == null)
            {
                PopulateOBBProperties();
            }
            return m_ObbPackage;
        }
    }

    private static int m_ObbVersion;
    private static int ObbVersion
    {
        get
        {
            if (m_ObbVersion == 0)
            {
                PopulateOBBProperties();
            }
            return m_ObbVersion;
        }
    }

    // This code will reuse the package version from the .apk when looking for the .obb
    // Modify as appropriate
    private static void PopulateOBBProperties()
    {
        using (var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            var currentActivity = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
            m_ObbPackage = currentActivity.Call<string>("getPackageName");
            var packageInfo = currentActivity.Call<AndroidJavaObject>("getPackageManager").Call<AndroidJavaObject>("getPackageInfo", m_ObbPackage, 0);
            m_ObbVersion = packageInfo.Get<int>("versionCode");
        }
    }
}


================================================
FILE: CHANGELOG
================================================
Version 3.0 (2017-09-22)

* Scripts rewritten, with IGooglePlayObbDownloader interface extracted
* Sample updated to match the new API


Version 2.0 (2017-08-25)

* Updated the plugin to be an AAR
* Solved Android M compatibility issues (however min API level is now 11)
* Updated project structure and documentation


Version 1.0

* Initial version

================================================
FILE: LICENSE.txt
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   Copyright 2015-2016 Yury Habets

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# UnityOBBDownloader
This package is an adaption of the Google Play market_downloader library, for use with Unity Android (as a plugin).

This plugin does NOT solve splitting up a >100MB .apk into .obb (through asset bundles or similar techniques).
It merely handles the downloading of .obb files attached to a published .apk, on devices that don't support automatic downloading.

This software is free and published as is, with no warranty and responsibilities - use it at your own risk.

## Usage
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`.
The C# API of the plugin is available in `IGooglePlayObbDownloader` interface. Use `GooglePlayObbDownloadManager.GetGooglePlayObbDownloader()` to obtain an instance.

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`)
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
2.	Change the Bundle Identifier / Version Code so it matches the application already available on Google Play (that has .obb files attached)
3.	Build and Run on your Android device

For a script sample, please refer to `Assets/Scripts/DownloadObbExample.cs`.

## How to Build
Run `gradlew assemble` from src/UnityAndroidPermissions/

## License
Copyright (C) 2016-2017 Yury Habets

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

## See also
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

For more information on Unity Android plugins, please refer to http://docs.unity3d.com/Manual/PluginsForAndroid.html


================================================
FILE: src/unityOBBDownloader/build.gradle
================================================
apply plugin: 'com.android.library'

buildscript {
	repositories {
		jcenter()
		google()
	}

	dependencies {
		classpath 'com.android.tools.build:gradle:3.0.1'
	}
} 

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.2"

    defaultConfig {
        minSdkVersion 11
        targetSdkVersion 26
    }

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles 'proguard.cfg'
        }
    }
}


================================================
FILE: src/unityOBBDownloader/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip


================================================
FILE: src/unityOBBDownloader/gradlew
================================================
#!/usr/bin/env sh

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
    echo "$*"
}

die () {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Escape application args
save () {
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    echo " "
}
APP_ARGS=$(save "$@")

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
  cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"


================================================
FILE: src/unityOBBDownloader/gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: src/unityOBBDownloader/proguard.cfg
================================================
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends com.google.android.vending.expansion.downloader.impl.DownloaderService
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

# remove obfuscation on critical parts of the google play downloader
-keepclassmembers class * extends android.app.Service { <fields>; }
-flattenpackagehierarchy com.unity3d.plugin.downloader


================================================
FILE: src/unityOBBDownloader/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.unity3d.plugin.downloader"
    android:versionCode="1"
    android:versionName="1.0">
    <application>
        <activity android:name="com.unity3d.plugin.downloader.UnityDownloaderActivity" />
        <service android:name="com.unity3d.plugin.downloader.UnityDownloaderService" />
        <receiver android:name="com.unity3d.plugin.downloader.UnityAlarmReceiver" />
    </application>
    <uses-sdk android:minSdkVersion="11"/>
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>


================================================
FILE: src/unityOBBDownloader/src/main/aidl/com/android/vending/licensing/ILicenseResultListener.aidl
================================================
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.vending.licensing;

oneway interface ILicenseResultListener {
  void verifyLicense(int responseCode, String signedData, String signature);
}


================================================
FILE: src/unityOBBDownloader/src/main/aidl/com/android/vending/licensing/ILicensingService.aidl
================================================
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.vending.licensing;

import com.android.vending.licensing.ILicenseResultListener;

oneway interface ILicensingService {
  void checkLicense(long nonce, String packageName, in ILicenseResultListener listener);
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Constants.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import java.io.File;


/**
 * Contains the internal constants that are used in the download manager.
 * As a general rule, modifying these constants should be done with care.
 */
public class Constants {    
    /** Tag used for debugging/logging */
    public static final String TAG = "LVLDL";

    /**
     * Expansion path where we store obb files
     */
    public static final String EXP_PATH = File.separator + "Android"
            + File.separator + "obb" + File.separator;
    
    /** The intent that gets sent when the service must wake up for a retry */
    public static final String ACTION_RETRY = "android.intent.action.DOWNLOAD_WAKEUP";

    /** the intent that gets sent when clicking a successful download */
    public static final String ACTION_OPEN = "android.intent.action.DOWNLOAD_OPEN";

    /** the intent that gets sent when clicking an incomplete/failed download  */
    public static final String ACTION_LIST = "android.intent.action.DOWNLOAD_LIST";

    /** the intent that gets sent when deleting the notification of a completed download */
    public static final String ACTION_HIDE = "android.intent.action.DOWNLOAD_HIDE";

    /**
     * When a number has to be appended to the filename, this string is used to separate the
     * base filename from the sequence number
     */
    public static final String FILENAME_SEQUENCE_SEPARATOR = "-";

    /** The default user agent used for downloads */
    public static final String DEFAULT_USER_AGENT = "Android.LVLDM";

    /** The buffer size used to stream the data */
    public static final int BUFFER_SIZE = 4096;

    /** The minimum amount of progress that has to be done before the progress bar gets updated */
    public static final int MIN_PROGRESS_STEP = 4096;

    /** The minimum amount of time that has to elapse before the progress bar gets updated, in ms */
    public static final long MIN_PROGRESS_TIME = 1000;

    /** The maximum number of rows in the database (FIFO) */
    public static final int MAX_DOWNLOADS = 1000;

    /**
     * The number of times that the download manager will retry its network
     * operations when no progress is happening before it gives up.
     */
    public static final int MAX_RETRIES = 5;

    /**
     * The minimum amount of time that the download manager accepts for
     * a Retry-After response header with a parameter in delta-seconds.
     */
    public static final int MIN_RETRY_AFTER = 30; // 30s

    /**
     * The maximum amount of time that the download manager accepts for
     * a Retry-After response header with a parameter in delta-seconds.
     */
    public static final int MAX_RETRY_AFTER = 24 * 60 * 60; // 24h

    /**
     * The maximum number of redirects.
     */
    public static final int MAX_REDIRECTS = 5; // can't be more than 7.

    /**
     * The time between a failure and the first retry after an IOException.
     * Each subsequent retry grows exponentially, doubling each time.
     * The time is in seconds.
     */
    public static final int RETRY_FIRST_DELAY = 30;

    /** Enable separate connectivity logging */
    public static final boolean LOGX = true;

    /** Enable verbose logging */
    public static final boolean LOGV = false;
    
    /** Enable super-verbose logging */
    private static final boolean LOCAL_LOGVV = false;
    public static final boolean LOGVV = LOCAL_LOGVV && LOGV;
    
    /**
     * This download has successfully completed.
     * Warning: there might be other status values that indicate success
     * in the future.
     * Use isSucccess() to capture the entire category.
     */
    public static final int STATUS_SUCCESS = 200;

    /**
     * This request couldn't be parsed. This is also used when processing
     * requests with unknown/unsupported URI schemes.
     */
    public static final int STATUS_BAD_REQUEST = 400;

    /**
     * This download can't be performed because the content type cannot be
     * handled.
     */
    public static final int STATUS_NOT_ACCEPTABLE = 406;

    /**
     * This download cannot be performed because the length cannot be
     * determined accurately. This is the code for the HTTP error "Length
     * Required", which is typically used when making requests that require
     * a content length but don't have one, and it is also used in the
     * client when a response is received whose length cannot be determined
     * accurately (therefore making it impossible to know when a download
     * completes).
     */
    public static final int STATUS_LENGTH_REQUIRED = 411;

    /**
     * This download was interrupted and cannot be resumed.
     * This is the code for the HTTP error "Precondition Failed", and it is
     * also used in situations where the client doesn't have an ETag at all.
     */
    public static final int STATUS_PRECONDITION_FAILED = 412;

    /**
     * The lowest-valued error status that is not an actual HTTP status code.
     */
    public static final int MIN_ARTIFICIAL_ERROR_STATUS = 488;

    /**
     * The requested destination file already exists.
     */
    public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;

    /**
     * Some possibly transient error occurred, but we can't resume the download.
     */
    public static final int STATUS_CANNOT_RESUME = 489;

    /**
     * This download was canceled
     */
    public static final int STATUS_CANCELED = 490;

    /**
     * This download has completed with an error.
     * Warning: there will be other status values that indicate errors in
     * the future. Use isStatusError() to capture the entire category.
     */
    public static final int STATUS_UNKNOWN_ERROR = 491;

    /**
     * This download couldn't be completed because of a storage issue.
     * Typically, that's because the filesystem is missing or full.
     * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
     * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
     */
    public static final int STATUS_FILE_ERROR = 492;

    /**
     * This download couldn't be completed because of an HTTP
     * redirect response that the download manager couldn't
     * handle.
     */
    public static final int STATUS_UNHANDLED_REDIRECT = 493;

    /**
     * This download couldn't be completed because of an
     * unspecified unhandled HTTP code.
     */
    public static final int STATUS_UNHANDLED_HTTP_CODE = 494;

    /**
     * This download couldn't be completed because of an
     * error receiving or processing data at the HTTP level.
     */
    public static final int STATUS_HTTP_DATA_ERROR = 495;

    /**
     * This download couldn't be completed because of an
     * HttpException while setting up the request.
     */
    public static final int STATUS_HTTP_EXCEPTION = 496;

    /**
     * This download couldn't be completed because there were
     * too many redirects.
     */
    public static final int STATUS_TOO_MANY_REDIRECTS = 497;

    /**
     * This download couldn't be completed due to insufficient storage
     * space.  Typically, this is because the SD card is full.
     */
    public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;

    /**
     * This download couldn't be completed because no external storage
     * device was found.  Typically, this is because the SD card is not
     * mounted.
     */
    public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;

    /**
     * The wake duration to check to see if a download is possible.
     */
    public static final long WATCHDOG_WAKE_TIMER = 60*1000;    

    /**
     * The wake duration to check to see if the process was killed.
     */
    public static final long ACTIVE_THREAD_WATCHDOG = 5*1000;    

}

================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import android.os.Parcel;
import android.os.Parcelable;


/**
 * This class contains progress information about the active download(s).
 *
 * When you build the Activity that initiates a download and tracks the
 * progress by implementing the {@link IDownloaderClient} interface, you'll
 * receive a DownloadProgressInfo object in each call to the {@link
 * IDownloaderClient#onDownloadProgress} method. This allows you to update
 * your activity's UI with information about the download progress, such
 * as the progress so far, time remaining and current speed.
 */
public class DownloadProgressInfo implements Parcelable {
    public long mOverallTotal;
    public long mOverallProgress;
    public long mTimeRemaining; // time remaining
    public float mCurrentSpeed; // speed in KB/S

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel p, int i) {
        p.writeLong(mOverallTotal);
        p.writeLong(mOverallProgress);
        p.writeLong(mTimeRemaining);
        p.writeFloat(mCurrentSpeed);
    }

    public DownloadProgressInfo(Parcel p) {
        mOverallTotal = p.readLong();
        mOverallProgress = p.readLong();
        mTimeRemaining = p.readLong();
        mCurrentSpeed = p.readFloat();
    }

    public DownloadProgressInfo(long overallTotal, long overallProgress,
            long timeRemaining,
            float currentSpeed) {
        this.mOverallTotal = overallTotal;
        this.mOverallProgress = overallProgress;
        this.mTimeRemaining = timeRemaining;
        this.mCurrentSpeed = currentSpeed;
    }

    public static final Creator<DownloadProgressInfo> CREATOR = new Creator<DownloadProgressInfo>() {
        @Override
        public DownloadProgressInfo createFromParcel(Parcel parcel) {
            return new DownloadProgressInfo(parcel);
        }

        @Override
        public DownloadProgressInfo[] newArray(int i) {
            return new DownloadProgressInfo[i];
        }
    };

}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import com.google.android.vending.expansion.downloader.impl.DownloaderService;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;



/**
 * This class binds the service API to your application client.  It contains the IDownloaderClient proxy,
 * which is used to call functions in your client as well as the Stub, which is used to call functions
 * in the client implementation of IDownloaderClient.
 * 
 * <p>The IPC is implemented using an Android Messenger and a service Binder.  The connect method
 * should be called whenever the client wants to bind to the service.  It opens up a service connection
 * that ends up calling the onServiceConnected client API that passes the service messenger
 * in.  If the client wants to be notified by the service, it is responsible for then passing its
 * messenger to the service in a separate call.
 *
 * <p>Critical methods are {@link #startDownloadServiceIfRequired} and {@link #CreateStub}.
 *
 * <p>When your application first starts, you should first check whether your app's expansion files are
 * already on the device. If not, you should then call {@link #startDownloadServiceIfRequired}, which
 * starts your {@link impl.DownloaderService} to download the expansion files if necessary. The method
 * returns a value indicating whether download is required or not.
 *
 * <p>If a download is required, {@link #startDownloadServiceIfRequired} begins the download through
 * the specified service and you should then call {@link #CreateStub} to instantiate a member {@link
 * IStub} object that you need in order to receive calls through your {@link IDownloaderClient}
 * interface.
 */
public class DownloaderClientMarshaller {
    public static final int MSG_ONDOWNLOADSTATE_CHANGED = 10;
    public static final int MSG_ONDOWNLOADPROGRESS = 11;
    public static final int MSG_ONSERVICECONNECTED = 12;

    public static final String PARAM_NEW_STATE = "newState";
    public static final String PARAM_PROGRESS = "progress";
    public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;

    public static final int NO_DOWNLOAD_REQUIRED = DownloaderService.NO_DOWNLOAD_REQUIRED;
    public static final int LVL_CHECK_REQUIRED = DownloaderService.LVL_CHECK_REQUIRED;
    public static final int DOWNLOAD_REQUIRED = DownloaderService.DOWNLOAD_REQUIRED;

    private static class Proxy implements IDownloaderClient {
        private Messenger mServiceMessenger;

        @Override
        public void onDownloadStateChanged(int newState) {
            Bundle params = new Bundle(1);
            params.putInt(PARAM_NEW_STATE, newState);
            send(MSG_ONDOWNLOADSTATE_CHANGED, params);
        }

        @Override
        public void onDownloadProgress(DownloadProgressInfo progress) {
            Bundle params = new Bundle(1);
            params.putParcelable(PARAM_PROGRESS, progress);
            send(MSG_ONDOWNLOADPROGRESS, params);
        }

        private void send(int method, Bundle params) {
            Message m = Message.obtain(null, method);
            m.setData(params);
            try {
                mServiceMessenger.send(m);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        
        public Proxy(Messenger msg) {
            mServiceMessenger = msg;
        }

        @Override
        public void onServiceConnected(Messenger m) {
            /**
             * This is never called through the proxy.
             */
        }
    }

    private static class Stub implements IStub {
        private IDownloaderClient mItf = null;
        private Class<?> mDownloaderServiceClass;
        private boolean mBound;
        private Messenger mServiceMessenger;
        private Context mContext;
        /**
         * Target we publish for clients to send messages to IncomingHandler.
         */
        final Messenger mMessenger = new Messenger(new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_ONDOWNLOADPROGRESS:                        
                        Bundle bun = msg.getData();
                        if ( null != mContext ) {
                            bun.setClassLoader(mContext.getClassLoader());
                            DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData()
                                    .getParcelable(PARAM_PROGRESS);
                            mItf.onDownloadProgress(dpi);
                        }
                        break;
                    case MSG_ONDOWNLOADSTATE_CHANGED:
                        mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
                        break;
                    case MSG_ONSERVICECONNECTED:
                        mItf.onServiceConnected(
                                (Messenger) msg.getData().getParcelable(PARAM_MESSENGER));
                        break;
                }
            }
        });

        public Stub(IDownloaderClient itf, Class<?> downloaderService) {
            mItf = itf;
            mDownloaderServiceClass = downloaderService;
        }

        /**
         * Class for interacting with the main interface of the service.
         */
        private ServiceConnection mConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                // This is called when the connection with the service has been
                // established, giving us the object we can use to
                // interact with the service. We are communicating with the
                // service using a Messenger, so here we get a client-side
                // representation of that from the raw IBinder object.
                mServiceMessenger = new Messenger(service);
                mItf.onServiceConnected(
                        mServiceMessenger);
            }

            public void onServiceDisconnected(ComponentName className) {
                // This is called when the connection with the service has been
                // unexpectedly disconnected -- that is, its process crashed.
                mServiceMessenger = null;
            }
        };

        @Override
        public void connect(Context c) {
            mContext = c;
            Intent bindIntent = new Intent(c, mDownloaderServiceClass);
            bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
            if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
                if ( Constants.LOGVV ) {
                    Log.d(Constants.TAG, "Service Unbound");
                }
            } else {
                mBound = true;
            }
                
        }

        @Override
        public void disconnect(Context c) {
            if (mBound) {
                c.unbindService(mConnection);
                mBound = false;
            }
            mContext = null;
        }

        @Override
        public Messenger getMessenger() {
            return mMessenger;
        }
    }

    /**
     * Returns a proxy that will marshal calls to IDownloaderClient methods
     * 
     * @param msg
     * @return
     */
    public static IDownloaderClient CreateProxy(Messenger msg) {
        return new Proxy(msg);
    }

    /**
     * Returns a stub object that, when connected, will listen for marshaled
     * {@link IDownloaderClient} methods and translate them into calls to the supplied
     * interface.
     * 
     * @param itf An implementation of IDownloaderClient that will be called
     *            when remote method calls are unmarshaled.
     * @param downloaderService The class for your implementation of {@link
     * impl.DownloaderService}.
     * @return The {@link IStub} that allows you to connect to the service such that
     * your {@link IDownloaderClient} receives status updates.
     */
    public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) {
        return new Stub(itf, downloaderService);
    }
    
    /**
     * Starts the download if necessary. This function starts a flow that does `
     * many things. 1) Checks to see if the APK version has been checked and
     * the metadata database updated 2) If the APK version does not match,
     * checks the new LVL status to see if a new download is required 3) If the
     * APK version does match, then checks to see if the download(s) have been
     * completed 4) If the downloads have been completed, returns
     * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the
     * startup of an application to quickly ascertain if the application needs
     * to wait to hear about any updated APK expansion files. Note that this does
     * mean that the application MUST be run for the first time with a network
     * connection, even if Market delivers all of the files.
     * 
     * @param context Your application Context.
     * @param notificationClient A PendingIntent to start the Activity in your application
     * that shows the download progress and which will also start the application when download
     * completes.
     * @param serviceClass the class of your {@link imp.DownloaderService} implementation
     * @return whether the service was started and the reason for starting the service.
     * Either {@link #NO_DOWNLOAD_REQUIRED}, {@link #LVL_CHECK_REQUIRED}, or {@link
     * #DOWNLOAD_REQUIRED}.
     * @throws NameNotFoundException
     */
    public static int startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, 
            Class<?> serviceClass)
            throws NameNotFoundException {
        return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
                serviceClass);
    }
    
    /**
     * This version assumes that the intent contains the pending intent as a parameter. This
     * is used for responding to alarms.
     * <p>The pending intent must be in an extra with the key {@link 
     * impl.DownloaderService#EXTRA_PENDING_INTENT}.
     * 
     * @param context
     * @param notificationClient
     * @param serviceClass the class of the service to start
     * @return
     * @throws NameNotFoundException
     */
    public static int startDownloadServiceIfRequired(Context context, Intent notificationClient, 
            Class<?> serviceClass)
            throws NameNotFoundException {
        return DownloaderService.startDownloadServiceIfRequired(context, notificationClient,
                serviceClass);
    }    

}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import com.google.android.vending.expansion.downloader.impl.DownloaderService;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;



/**
 * This class is used by the client activity to proxy requests to the Downloader
 * Service.
 *
 * Most importantly, you must call {@link #CreateProxy} during the {@link
 * IDownloaderClient#onServiceConnected} callback in your activity in order to instantiate
 * an {@link IDownloaderService} object that you can then use to issue commands to the {@link
 * DownloaderService} (such as to pause and resume downloads).
 */
public class DownloaderServiceMarshaller {

    public static final int MSG_REQUEST_ABORT_DOWNLOAD =
            1;
    public static final int MSG_REQUEST_PAUSE_DOWNLOAD =
            2;
    public static final int MSG_SET_DOWNLOAD_FLAGS =
            3;
    public static final int MSG_REQUEST_CONTINUE_DOWNLOAD =
            4;
    public static final int MSG_REQUEST_DOWNLOAD_STATE =
            5;
    public static final int MSG_REQUEST_CLIENT_UPDATE =
            6;

    public static final String PARAMS_FLAGS = "flags";
    public static final String PARAM_MESSENGER = DownloaderService.EXTRA_MESSAGE_HANDLER;

    private static class Proxy implements IDownloaderService {
        private Messenger mMsg;

        private void send(int method, Bundle params) {
            Message m = Message.obtain(null, method);
            m.setData(params);
            try {
                mMsg.send(m);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public Proxy(Messenger msg) {
            mMsg = msg;
        }

        @Override
        public void requestAbortDownload() {
            send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle());
        }

        @Override
        public void requestPauseDownload() {
            send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle());
        }

        @Override
        public void setDownloadFlags(int flags) {
            Bundle params = new Bundle();
            params.putInt(PARAMS_FLAGS, flags);
            send(MSG_SET_DOWNLOAD_FLAGS, params);
        }

        @Override
        public void requestContinueDownload() {
            send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle());
        }

        @Override
        public void requestDownloadStatus() {
            send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle());
        }

        @Override
        public void onClientUpdated(Messenger clientMessenger) {
            Bundle bundle = new Bundle(1);
            bundle.putParcelable(PARAM_MESSENGER, clientMessenger);
            send(MSG_REQUEST_CLIENT_UPDATE, bundle);
        }
    }

    private static class Stub implements IStub {
        private IDownloaderService mItf = null;
        final Messenger mMessenger = new Messenger(new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_REQUEST_ABORT_DOWNLOAD:
                        mItf.requestAbortDownload();
                        break;
                    case MSG_REQUEST_CONTINUE_DOWNLOAD:
                        mItf.requestContinueDownload();
                        break;
                    case MSG_REQUEST_PAUSE_DOWNLOAD:
                        mItf.requestPauseDownload();
                        break;
                    case MSG_SET_DOWNLOAD_FLAGS:
                        mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
                        break;
                    case MSG_REQUEST_DOWNLOAD_STATE:
                        mItf.requestDownloadStatus();
                        break;
                    case MSG_REQUEST_CLIENT_UPDATE:
                        mItf.onClientUpdated((Messenger) msg.getData().getParcelable(
                                PARAM_MESSENGER));
                        break;
                }
            }
        });

        public Stub(IDownloaderService itf) {
            mItf = itf;
        }

        @Override
        public Messenger getMessenger() {
            return mMessenger;
        }

        @Override
        public void connect(Context c) {

        }

        @Override
        public void disconnect(Context c) {

        }
    }

    /**
     * Returns a proxy that will marshall calls to IDownloaderService methods
     * 
     * @param ctx
     * @return
     */
    public static IDownloaderService CreateProxy(Messenger msg) {
        return new Proxy(msg);
    }

    /**
     * Returns a stub object that, when connected, will listen for marshalled
     * IDownloaderService methods and translate them into calls to the supplied
     * interface.
     * 
     * @param itf An implementation of IDownloaderService that will be called
     *            when remote method calls are unmarshalled.
     * @return
     */
    public static IStub CreateStub(IDownloaderService itf) {
        return new Stub(itf);
    }

}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Helpers.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.os.SystemClock;
import android.util.Log;

//import com.android.vending.expansion.downloader.R;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Some helper functions for the download manager
 */
public class Helpers {

    public static Random sRandom = new Random(SystemClock.uptimeMillis());

    /** Regex used to parse content-disposition headers */
    private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern
            .compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");

    private Helpers() {
    }

    /*
     * Parse the Content-Disposition HTTP Header. The format of the header is defined here:
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html This header provides a filename for
     * content that is going to be downloaded to the file system. We only support the attachment
     * type.
     */
    static String parseContentDisposition(String contentDisposition) {
        try {
            Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
            if (m.find()) {
                return m.group(1);
            }
        } catch (IllegalStateException ex) {
            // This function is defined as returning null when it can't parse
            // the header
        }
        return null;
    }

    /**
     * @return the root of the filesystem containing the given path
     */
    public static File getFilesystemRoot(String path) {
        File cache = Environment.getDownloadCacheDirectory();
        if (path.startsWith(cache.getPath())) {
            return cache;
        }
        File external = Environment.getExternalStorageDirectory();
        if (path.startsWith(external.getPath())) {
            return external;
        }
        throw new IllegalArgumentException(
                "Cannot determine filesystem root for " + path);
    }

    public static boolean isExternalMediaMounted() {
        if (!Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            // No SD card found.
            if (Constants.LOGVV) {
                Log.d(Constants.TAG, "no external storage");
            }
            return false;
        }
        return true;
    }

    /**
     * @return the number of bytes available on the filesystem rooted at the given File
     */
    public static long getAvailableBytes(File root) {
        StatFs stat = new StatFs(root.getPath());
        // put a bit of margin (in case creating the file grows the system by a
        // few blocks)
        long availableBlocks = (long) stat.getAvailableBlocks() - 4;
        return stat.getBlockSize() * availableBlocks;
    }

    /**
     * Checks whether the filename looks legitimate
     */
    public static boolean isFilenameValid(String filename) {
        filename = filename.replaceFirst("/+", "/"); // normalize leading
                                                     // slashes
        return filename.startsWith(Environment.getDownloadCacheDirectory().toString())
                || filename.startsWith(Environment.getExternalStorageDirectory().toString());
    }

    /*
     * Delete the given file from device
     */
    /* package */static void deleteFile(String path) {
        try {
            File file = new File(path);
            file.delete();
        } catch (Exception e) {
            Log.w(Constants.TAG, "file: '" + path + "' couldn't be deleted", e);
        }
    }

    /**
     * Showing progress in MB here. It would be nice to choose the unit (KB, MB, GB) based on total
     * file size, but given what we know about the expected ranges of file sizes for APK expansion
     * files, it's probably not necessary.
     *
     * @param overallProgress
     * @param overallTotal
     * @return
     */

    static public String getDownloadProgressString(long overallProgress, long overallTotal) {
        if (overallTotal == 0) {
            if (Constants.LOGVV) {
                Log.e(Constants.TAG, "Notification called when total is zero");
            }
            return "";
        }
        return String.format("%.2f",
                (float) overallProgress / (1024.0f * 1024.0f))
                + "MB /" +
                String.format("%.2f", (float) overallTotal /
                        (1024.0f * 1024.0f))
                + "MB";
    }

    /**
     * Adds a percentile to getDownloadProgressString.
     *
     * @param overallProgress
     * @param overallTotal
     * @return
     */
    static public String getDownloadProgressStringNotification(long overallProgress,
            long overallTotal) {
        if (overallTotal == 0) {
            if (Constants.LOGVV) {
                Log.e(Constants.TAG, "Notification called when total is zero");
            }
            return "";
        }
        return getDownloadProgressString(overallProgress, overallTotal) + " (" +
                getDownloadProgressPercent(overallProgress, overallTotal) + ")";
    }

    public static String getDownloadProgressPercent(long overallProgress, long overallTotal) {
        if (overallTotal == 0) {
            if (Constants.LOGVV) {
                Log.e(Constants.TAG, "Notification called when total is zero");
            }
            return "";
        }
        return Long.toString(overallProgress * 100 / overallTotal) + "%";
    }

    public static String getSpeedString(float bytesPerMillisecond) {
        return String.format("%.2f", bytesPerMillisecond * 1000 / 1024);
    }

    public static String getTimeRemaining(long durationInMilliseconds) {
        SimpleDateFormat sdf;
        if (durationInMilliseconds > 1000 * 60 * 60) {
            sdf = new SimpleDateFormat("HH:mm", Locale.getDefault());
        } else {
            sdf = new SimpleDateFormat("mm:ss", Locale.getDefault());
        }
        return sdf.format(new Date(durationInMilliseconds - TimeZone.getDefault().getRawOffset()));
    }

    /**
     * Returns the file name (without full path) for an Expansion APK file from the given context.
     *
     * @param c the context
     * @param mainFile true for main file, false for patch file
     * @param versionCode the version of the file
     * @return String the file name of the expansion file
     */
    public static String getExpansionAPKFileName(Context c, boolean mainFile, int versionCode) {
        return (mainFile ? "main." : "patch.") + versionCode + "." + c.getPackageName() + ".obb";
    }

    /**
     * Returns the filename (where the file should be saved) from info about a download
     */
    static public String generateSaveFileName(Context c, String fileName) {
        String path = getSaveFilePath(c)
                + File.separator + fileName;
        return path;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    static public String getSaveFilePath(Context c) {
        // This technically existed since Honeycomb, but it is critical
        // on KitKat and greater versions since it will create the
        // directory if needed
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return c.getObbDir().toString();
        } else {
            File root = Environment.getExternalStorageDirectory();
            String path = root.toString() + Constants.EXP_PATH + c.getPackageName();
            return path;
        }
    }

    /**
     * Helper function to ascertain the existence of a file and return true/false appropriately
     *
     * @param c the app/activity/service context
     * @param fileName the name (sans path) of the file to query
     * @param fileSize the size that the file must match
     * @param deleteFileOnMismatch if the file sizes do not match, delete the file
     * @return true if it does exist, false otherwise
     */
    static public boolean doesFileExist(Context c, String fileName, long fileSize,
            boolean deleteFileOnMismatch) {
        // the file may have been delivered by Play --- let's make sure
        // it's the size we expect
        File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
        if (fileForNewFile.exists()) {
            if (fileForNewFile.length() == fileSize) {
                return true;
            }
            if (deleteFileOnMismatch) {
                // delete the file --- we won't be able to resume
                // because we cannot confirm the integrity of the file
                fileForNewFile.delete();
            }
        }
        return false;
    }

    public static final int FS_READABLE = 0;
    public static final int FS_DOES_NOT_EXIST = 1;
    public static final int FS_CANNOT_READ = 2;

    /**
     * Helper function to ascertain whether a file can be read.
     *
     * @param c the app/activity/service context
     * @param fileName the name (sans path) of the file to query
     * @return true if it does exist, false otherwise
     */
    static public int getFileStatus(Context c, String fileName) {
        // the file may have been delivered by Play --- let's make sure
        // it's the size we expect
        File fileForNewFile = new File(Helpers.generateSaveFileName(c, fileName));
        int returnValue;
        if (fileForNewFile.exists()) {
            if (fileForNewFile.canRead()) {
                returnValue = FS_READABLE;
            } else {
                returnValue = FS_CANNOT_READ;
            }
        } else {
            returnValue = FS_DOES_NOT_EXIST;
        }
        return returnValue;
    }

    /**
     * Helper function to ascertain whether the application has the correct access to the OBB
     * directory to allow an OBB file to be written.
     * 
     * @param c the app/activity/service context
     * @return true if the application can write an OBB file, false otherwise
     */
    static public boolean canWriteOBBFile(Context c) {
        String path = getSaveFilePath(c);
        File fileForNewFile = new File(path);
        boolean canWrite;
        if (fileForNewFile.exists()) {
            canWrite = fileForNewFile.isDirectory() && fileForNewFile.canWrite();
        } else {
            canWrite = fileForNewFile.mkdirs();
        }
        return canWrite;
    }

    /**
     * Converts download states that are returned by the
     * {@link IDownloaderClient#onDownloadStateChanged} callback into usable strings. This is useful
     * if using the state strings built into the library to display user messages.
     * 
     * @param state One of the STATE_* constants from {@link IDownloaderClient}.
     * @return string resource ID for the corresponding string.
     */
    static public int getDownloaderStringResourceIDFromState(Context ctx, int state) {
        switch (state) {
            case IDownloaderClient.STATE_IDLE:
                return getStringResource(ctx, "state_idle");
            case IDownloaderClient.STATE_FETCHING_URL:
                return getStringResource(ctx, "state_fetching_url");
            case IDownloaderClient.STATE_CONNECTING:
                return getStringResource(ctx, "state_connecting");
            case IDownloaderClient.STATE_DOWNLOADING:
                return getStringResource(ctx, "state_downloading");
            case IDownloaderClient.STATE_COMPLETED:
                return getStringResource(ctx, "state_completed");
            case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE:
                return getStringResource(ctx, "state_paused_network_unavailable");
            case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
                return getStringResource(ctx, "state_paused_by_request");
            case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
                return getStringResource(ctx, "state_paused_wifi_disabled");
            case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
                return getStringResource(ctx, "state_paused_wifi_unavailable");
            case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED:
                return getStringResource(ctx, "state_paused_wifi_disabled");
            case IDownloaderClient.STATE_PAUSED_NEED_WIFI:
                return getStringResource(ctx, "state_paused_wifi_unavailable");
            case IDownloaderClient.STATE_PAUSED_ROAMING:
                return getStringResource(ctx, "state_paused_roaming");
            case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE:
                return getStringResource(ctx, "state_paused_network_setup_failure");
            case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
                return getStringResource(ctx, "state_paused_sdcard_unavailable");
            case IDownloaderClient.STATE_FAILED_UNLICENSED:
                return getStringResource(ctx, "state_failed_unlicensed");
            case IDownloaderClient.STATE_FAILED_FETCHING_URL:
                return getStringResource(ctx, "state_failed_fetching_url");
            case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
                return getStringResource(ctx, "state_failed_sdcard_full");
            case IDownloaderClient.STATE_FAILED_CANCELED:
                return getStringResource(ctx, "state_failed_cancelled");
            default:
                return getStringResource(ctx, "state_unknown");
        }
    }

	static public int getStringResource(Context ctx, String name) {
		return ctx.getResources().getIdentifier(name, "string", ctx.getPackageName());
	}
	static public int getLayoutResource(Context ctx, String name) {
		return ctx.getResources().getIdentifier(name, "layout", ctx.getPackageName());
	}
	static public int getIdResource(Context ctx, String name) {
		return ctx.getResources().getIdentifier(name, "id", ctx.getPackageName());
	}
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderClient.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import android.os.Messenger;

/**
 * This interface should be implemented by the client activity for the
 * downloader. It is used to pass status from the service to the client.
 */
public interface IDownloaderClient {
    static final int STATE_IDLE = 1;
    static final int STATE_FETCHING_URL = 2;
    static final int STATE_CONNECTING = 3;
    static final int STATE_DOWNLOADING = 4;
    static final int STATE_COMPLETED = 5;

    static final int STATE_PAUSED_NETWORK_UNAVAILABLE = 6;
    static final int STATE_PAUSED_BY_REQUEST = 7;

    /**
     * Both STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION and
     * STATE_PAUSED_NEED_CELLULAR_PERMISSION imply that Wi-Fi is unavailable and
     * cellular permission will restart the service. Wi-Fi disabled means that
     * the Wi-Fi manager is returning that Wi-Fi is not enabled, while in the
     * other case Wi-Fi is enabled but not available.
     */
    static final int STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8;
    static final int STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9;

    /**
     * Both STATE_PAUSED_WIFI_DISABLED and STATE_PAUSED_NEED_WIFI imply that
     * Wi-Fi is unavailable and cellular permission will NOT restart the
     * service. Wi-Fi disabled means that the Wi-Fi manager is returning that
     * Wi-Fi is not enabled, while in the other case Wi-Fi is enabled but not
     * available.
     * <p>
     * The service does not return these values. We recommend that app
     * developers with very large payloads do not allow these payloads to be
     * downloaded over cellular connections.
     */
    static final int STATE_PAUSED_WIFI_DISABLED = 10;
    static final int STATE_PAUSED_NEED_WIFI = 11;

    static final int STATE_PAUSED_ROAMING = 12;

    /**
     * Scary case. We were on a network that redirected us to another website
     * that delivered us the wrong file.
     */
    static final int STATE_PAUSED_NETWORK_SETUP_FAILURE = 13;

    static final int STATE_PAUSED_SDCARD_UNAVAILABLE = 14;

    static final int STATE_FAILED_UNLICENSED = 15;
    static final int STATE_FAILED_FETCHING_URL = 16;
    static final int STATE_FAILED_SDCARD_FULL = 17;
    static final int STATE_FAILED_CANCELED = 18;

    static final int STATE_FAILED = 19;

    /**
     * Called internally by the stub when the service is bound to the client.
     * <p>
     * Critical implementation detail. In onServiceConnected we create the
     * remote service and marshaler. This is how we pass the client information
     * back to the service so the client can be properly notified of changes. We
     * must do this every time we reconnect to the service.
     * <p>
     * That is, when you receive this callback, you should call
     * {@link DownloaderServiceMarshaller#CreateProxy} to instantiate a member
     * instance of {@link IDownloaderService}, then call
     * {@link IDownloaderService#onClientUpdated} with the Messenger retrieved
     * from your {@link IStub} proxy object.
     * 
     * @param m the service Messenger. This Messenger is used to call the
     *            service API from the client.
     */
    void onServiceConnected(Messenger m);

    /**
     * Called when the download state changes. Depending on the state, there may
     * be user requests. The service is free to change the download state in the
     * middle of a user request, so the client should be able to handle this.
     * <p>
     * The Downloader Library includes a collection of string resources that
     * correspond to each of the states, which you can use to provide users a
     * useful message based on the state provided in this callback. To fetch the
     * appropriate string for a state, call
     * {@link Helpers#getDownloaderStringResourceIDFromState}.
     * <p>
     * What this means to the developer: The application has gotten a message
     * that the download has paused due to lack of WiFi. The developer should
     * then show UI asking the user if they want to enable downloading over
     * cellular connections with appropriate warnings. If the application
     * suddenly starts downloading, the application should revert to showing the
     * progress again, rather than leaving up the download over cellular UI up.
     * 
     * @param newState one of the STATE_* values defined in IDownloaderClient
     */
    void onDownloadStateChanged(int newState);

    /**
     * Shows the download progress. This is intended to be used to fill out a
     * client UI. This progress should only be shown in a few states such as
     * STATE_DOWNLOADING.
     * 
     * @param progress the DownloadProgressInfo object containing the current
     *            progress of all downloads.
     */
    void onDownloadProgress(DownloadProgressInfo progress);
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderService.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import com.google.android.vending.expansion.downloader.impl.DownloaderService;
import android.os.Messenger;

/**
 * This interface is implemented by the DownloaderService and by the
 * DownloaderServiceMarshaller. It contains functions to control the service.
 * When a client binds to the service, it must call the onClientUpdated
 * function.
 * <p>
 * You can acquire a proxy that implements this interface for your service by
 * calling {@link DownloaderServiceMarshaller#CreateProxy} during the
 * {@link IDownloaderClient#onServiceConnected} callback. At which point, you
 * should immediately call {@link #onClientUpdated}.
 */
public interface IDownloaderService {
    /**
     * Set this flag in response to the
     * IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION state and then
     * call RequestContinueDownload to resume a download
     */
    public static final int FLAGS_DOWNLOAD_OVER_CELLULAR = 1;

    /**
     * Request that the service abort the current download. The service should
     * respond by changing the state to {@link IDownloaderClient.STATE_ABORTED}.
     */
    void requestAbortDownload();

    /**
     * Request that the service pause the current download. The service should
     * respond by changing the state to
     * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
     */
    void requestPauseDownload();

    /**
     * Request that the service continue a paused download, when in any paused
     * or failed state, including
     * {@link IDownloaderClient.STATE_PAUSED_BY_REQUEST}.
     */
    void requestContinueDownload();

    /**
     * Set the flags for this download (e.g.
     * {@link DownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR}).
     * 
     * @param flags
     */
    void setDownloadFlags(int flags);

    /**
     * Requests that the download status be sent to the client.
     */
    void requestDownloadStatus();

    /**
     * Call this when you get {@link
     * IDownloaderClient.onServiceConnected(Messenger m)} from the
     * DownloaderClient to register the client with the service. It will
     * automatically send the current status to the client.
     * 
     * @param clientMessenger
     */
    void onClientUpdated(Messenger clientMessenger);
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IStub.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import android.content.Context;
import android.os.Messenger;

/**
 * This is the interface that is used to connect/disconnect from the downloader
 * service.
 * <p>
 * You should get a proxy object that implements this interface by calling
 * {@link DownloaderClientMarshaller#CreateStub} in your activity when the
 * downloader service starts. Then, call {@link #connect} during your activity's
 * onResume() and call {@link #disconnect} during onStop().
 * <p>
 * Then during the {@link IDownloaderClient#onServiceConnected} callback, you
 * should call {@link #getMessenger} to pass the stub's Messenger object to
 * {@link IDownloaderService#onClientUpdated}.
 */
public interface IStub {
    Messenger getMessenger();

    void connect(Context c);

    void disconnect(Context c);
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/SystemFacade.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader;

import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;
import android.util.Log;

/**
 * Contains useful helper functions, typically tied to the application context.
 */
class SystemFacade {
    private Context mContext;
    private NotificationManager mNotificationManager;

    public SystemFacade(Context context) {
        mContext = context;
        mNotificationManager = (NotificationManager)
                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    }

    public long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    public Integer getActiveNetworkType() {
        ConnectivityManager connectivity =
                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivity == null) {
            Log.w(Constants.TAG, "couldn't get connectivity manager");
            return null;
        }

        NetworkInfo activeInfo = connectivity.getActiveNetworkInfo();
        if (activeInfo == null) {
            if (Constants.LOGVV) {
                Log.v(Constants.TAG, "network is not available");
            }
            return null;
        }
        return activeInfo.getType();
    }

    public boolean isNetworkRoaming() {
        ConnectivityManager connectivity =
                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivity == null) {
            Log.w(Constants.TAG, "couldn't get connectivity manager");
            return false;
        }

        NetworkInfo info = connectivity.getActiveNetworkInfo();
        boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE);
        TelephonyManager tm = (TelephonyManager) mContext
                .getSystemService(Context.TELEPHONY_SERVICE);
        if (null == tm) {
            Log.w(Constants.TAG, "couldn't get telephony manager");
            return false;
        }
        boolean isRoaming = isMobile && tm.isNetworkRoaming();
        if (Constants.LOGVV && isRoaming) {
            Log.v(Constants.TAG, "network is roaming");
        }
        return isRoaming;
    }

    public Long getMaxBytesOverMobile() {
        return (long) Integer.MAX_VALUE;
    }

    public Long getRecommendedMaxBytesOverMobile() {
        return 2097152L;
    }

    public void sendBroadcast(Intent intent) {
        mContext.sendBroadcast(intent);
    }

    public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException {
        return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid;
    }

    public void postNotification(long id, Notification notification) {
        /**
         * TODO: The system notification manager takes ints, not longs, as IDs,
         * but the download manager uses IDs take straight from the database,
         * which are longs. This will have to be dealt with at some point.
         */
        mNotificationManager.notify((int) id, notification);
    }

    public void cancelNotification(long id) {
        mNotificationManager.cancel((int) id);
    }

    public void cancelAllNotifications() {
        mNotificationManager.cancelAll();
    }

    public void startThread(Thread thread) {
        thread.start();
    }
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader.impl;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

/**
 * This service differs from IntentService in a few minor ways/ It will not
 * auto-stop itself after the intent is handled unless the target returns "true"
 * in should stop. Since the goal of this service is to handle a single kind of
 * intent, it does not queue up batches of intents of the same type.
 */
public abstract class CustomIntentService extends Service {
    private String mName;
    private boolean mRedelivery;
    private volatile ServiceHandler mServiceHandler;
    private volatile Looper mServiceLooper;
    private static final String LOG_TAG = "CustomIntentService";
    private static final int WHAT_MESSAGE = -10;

    public CustomIntentService(String paramString) {
        this.mName = paramString;
    }

    @Override
    public IBinder onBind(Intent paramIntent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread localHandlerThread = new HandlerThread("IntentService["
                + this.mName + "]");
        localHandlerThread.start();
        this.mServiceLooper = localHandlerThread.getLooper();
        this.mServiceHandler = new ServiceHandler(this.mServiceLooper);
    }

    @Override
    public void onDestroy() {
        Thread localThread = this.mServiceLooper.getThread();
        if ((localThread != null) && (localThread.isAlive())) {
            localThread.interrupt();
        }
        this.mServiceLooper.quit();
        Log.d(LOG_TAG, "onDestroy");
    }

    protected abstract void onHandleIntent(Intent paramIntent);

    protected abstract boolean shouldStop();

    @Override
    public void onStart(Intent paramIntent, int startId) {
        if (!this.mServiceHandler.hasMessages(WHAT_MESSAGE)) {
            Message localMessage = this.mServiceHandler.obtainMessage();
            localMessage.arg1 = startId;
            localMessage.obj = paramIntent;
            localMessage.what = WHAT_MESSAGE;
            this.mServiceHandler.sendMessage(localMessage);
        }
    }

    @Override
    public int onStartCommand(Intent paramIntent, int flags, int startId) {
        onStart(paramIntent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    public void setIntentRedelivery(boolean enabled) {
        this.mRedelivery = enabled;
    }

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message paramMessage) {
            CustomIntentService.this
                    .onHandleIntent((Intent) paramMessage.obj);
            if (shouldStop()) {
                Log.d(LOG_TAG, "stopSelf");
                CustomIntentService.this.stopSelf(paramMessage.arg1);
                Log.d(LOG_TAG, "afterStopSelf");
            }
        }
    }
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader.impl;

import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.Helpers;

import android.util.Log;

/**
 * Representation of information about an individual download from the database.
 */
public class DownloadInfo {
    public String mUri;
    public final int mIndex;
    public final String mFileName;
    public String mETag;
    public long mTotalBytes;
    public long mCurrentBytes;
    public long mLastMod;
    public int mStatus;
    public int mControl;
    public int mNumFailed;
    public int mRetryAfter;
    public int mRedirectCount;

    boolean mInitialized;

    public int mFuzz;

    public DownloadInfo(int index, String fileName, String pkg) {
        mFuzz = Helpers.sRandom.nextInt(1001);
        mFileName = fileName;
        mIndex = index;
    }

    public void resetDownload() {
        mCurrentBytes = 0;
        mETag = "";
        mLastMod = 0;
        mStatus = 0;
        mControl = 0;
        mNumFailed = 0;
        mRetryAfter = 0;
        mRedirectCount = 0;
    }

    /**
     * Returns the time when a download should be restarted.
     */
    public long restartTime(long now) {
        if (mNumFailed == 0) {
            return now;
        }
        if (mRetryAfter > 0) {
            return mLastMod + mRetryAfter;
        }
        return mLastMod +
                Constants.RETRY_FIRST_DELAY *
                (1000 + mFuzz) * (1 << (mNumFailed - 1));
    }

    public void logVerboseInfo() {
        Log.v(Constants.TAG, "Service adding new entry");
        Log.v(Constants.TAG, "FILENAME: " + mFileName);
        Log.v(Constants.TAG, "URI     : " + mUri);
        Log.v(Constants.TAG, "FILENAME: " + mFileName);
        Log.v(Constants.TAG, "CONTROL : " + mControl);
        Log.v(Constants.TAG, "STATUS  : " + mStatus);
        Log.v(Constants.TAG, "FAILED_C: " + mNumFailed);
        Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter);
        Log.v(Constants.TAG, "REDIRECT: " + mRedirectCount);
        Log.v(Constants.TAG, "LAST_MOD: " + mLastMod);
        Log.v(Constants.TAG, "TOTAL   : " + mTotalBytes);
        Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes);
        Log.v(Constants.TAG, "ETAG    : " + mETag);
    }
}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader.impl;

import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import android.os.Messenger;

/**
 * This class handles displaying the notification associated with the download
 * queue going on in the download manager. It handles multiple status types;
 * Some require user interaction and some do not. Some of the user interactions
 * may be transient. (for example: the user is queried to continue the download
 * on 3G when it started on WiFi, but then the phone locks onto WiFi again so
 * the prompt automatically goes away)
 * <p/>
 * The application interface for the downloader also needs to understand and
 * handle these transient states.
 */
public class DownloadNotification implements IDownloaderClient {

    private int mState;
    private final Context mContext;
    private final NotificationManager mNotificationManager;
    private CharSequence mCurrentTitle;

    private IDownloaderClient mClientProxy;
    private Notification.Builder mActiveDownloadBuilder;
    private Notification.Builder mBuilder;
    private Notification.Builder mCurrentBuilder;
    private CharSequence mLabel;
    private String mCurrentText;
    private DownloadProgressInfo mProgressInfo;
    private PendingIntent mContentIntent;

    private static final String LOGTAG = "DownloadNotification";
    private static final int NOTIFICATION_ID = LOGTAG.hashCode();

    public void setClientIntent(PendingIntent clientIntent) {
        this.mBuilder.setContentIntent(clientIntent);
        this.mActiveDownloadBuilder.setContentIntent(clientIntent);
        this.mContentIntent = clientIntent;
    }

    public void resendState() {
        if (null != mClientProxy) {
            mClientProxy.onDownloadStateChanged(mState);
        }
    }

    @Override
    public void onDownloadStateChanged(int newState) {
        if (null != mClientProxy) {
            mClientProxy.onDownloadStateChanged(newState);
        }
        if (newState != mState) {
            mState = newState;
            if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) {
                return;
            }
            int stringDownloadID;
            int iconResource;
            boolean ongoingEvent;

            // get the new title string and paused text
            switch (newState) {
                case 0:
                    iconResource = android.R.drawable.stat_sys_warning;
                    stringDownloadID = Helpers.getStringResource(mContext, "state_unknown");
                    ongoingEvent = false;
                    break;

                case IDownloaderClient.STATE_DOWNLOADING:
                    iconResource = android.R.drawable.stat_sys_download;
                    stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState);
                    ongoingEvent = true;
                    break;

                case IDownloaderClient.STATE_FETCHING_URL:
                case IDownloaderClient.STATE_CONNECTING:
                    iconResource = android.R.drawable.stat_sys_download_done;
                    stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState);
                    ongoingEvent = true;
                    break;

                case IDownloaderClient.STATE_COMPLETED:
                case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
                    iconResource = android.R.drawable.stat_sys_download_done;
                    stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState);
                    ongoingEvent = false;
                    break;

                case IDownloaderClient.STATE_FAILED:
                case IDownloaderClient.STATE_FAILED_CANCELED:
                case IDownloaderClient.STATE_FAILED_FETCHING_URL:
                case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
                case IDownloaderClient.STATE_FAILED_UNLICENSED:
                    iconResource = android.R.drawable.stat_sys_warning;
                    stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState);
                    ongoingEvent = false;
                    break;

                default:
                    iconResource = android.R.drawable.stat_sys_warning;
                    stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(mContext, newState);
                    ongoingEvent = true;
                    break;
            }

            mCurrentText = mContext.getString(stringDownloadID);
            mCurrentTitle = mLabel;
            mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText)
            .setSmallIcon(iconResource)
            .setContentTitle(mCurrentTitle)
            .setContentText(mCurrentText);
            if (ongoingEvent) {
                mCurrentBuilder.setOngoing(true);
            } else {
                mCurrentBuilder.setOngoing(false);
                mCurrentBuilder.setAutoCancel(true);
            }
            Notification notification;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                notification = mCurrentBuilder.build();
            } else {
                notification = mCurrentBuilder.getNotification();
            }
            mNotificationManager.notify(NOTIFICATION_ID, notification);
        }
    }

    @Override
    public void onDownloadProgress(DownloadProgressInfo progress) {
        mProgressInfo = progress;
        if (null != mClientProxy) {
            mClientProxy.onDownloadProgress(progress);
        }
        if (progress.mOverallTotal <= 0) {
            // we just show the text
            mBuilder.setTicker(mCurrentTitle)
            .setSmallIcon(android.R.drawable.stat_sys_download)
            .setContentTitle(mCurrentTitle)
            .setContentText(mCurrentText);
            mCurrentBuilder = mBuilder;
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                mActiveDownloadBuilder.setProgress((int) progress.mOverallTotal, (int) progress.mOverallProgress, false);
            }
            mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal))
            .setSmallIcon(android.R.drawable.stat_sys_download)
            .setTicker(mLabel + ": " + mCurrentText)
            .setContentTitle(mLabel)
            .setContentInfo(mContext.getString(Helpers.getStringResource(mContext, "time_remaining_notification"),
                    Helpers.getTimeRemaining(progress.mTimeRemaining)));
            mCurrentBuilder = mActiveDownloadBuilder;
        }
        Notification notification;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            notification = mCurrentBuilder.build();
        } else {
            notification = mCurrentBuilder.getNotification();
        }
        mNotificationManager.notify(NOTIFICATION_ID, notification);
    }

    /**
     * Called in response to onClientUpdated. Creates a new proxy and notifies
     * it of the current state.
     *
     * @param msg the client Messenger to notify
     */
    public void setMessenger(Messenger msg) {
        mClientProxy = DownloaderClientMarshaller.CreateProxy(msg);
        if (null != mProgressInfo) {
            mClientProxy.onDownloadProgress(mProgressInfo);
        }
        if (mState != -1) {
            mClientProxy.onDownloadStateChanged(mState);
        }
    }

    /**
     * Constructor
     *
     * @param ctx The context to use to obtain access to the Notification
     *            Service
     */
    DownloadNotification(Context ctx, CharSequence applicationLabel) {
        mState = -1;
        mContext = ctx;
        mLabel = applicationLabel;
        mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        mActiveDownloadBuilder = new Notification.Builder(ctx);
        mBuilder = new Notification.Builder(ctx);

        // Set Notification category and priorities to something that makes sense for a long
        // lived background task.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mActiveDownloadBuilder.setPriority(Notification.PRIORITY_LOW);
            mBuilder.setPriority(Notification.PRIORITY_LOW);
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mActiveDownloadBuilder.setCategory(Notification.CATEGORY_PROGRESS);
            mBuilder.setCategory(Notification.CATEGORY_PROGRESS);
        }

        mCurrentBuilder = mBuilder;
    }

    @Override
    public void onServiceConnected(Messenger m) {
    }

}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadThread.java
================================================
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader.impl;

import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;

import android.content.Context;
import android.os.PowerManager;
import android.os.Process;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SyncFailedException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Locale;

/**
 * Runs an actual download
 */
public class DownloadThread {

    private Context mContext;
    private DownloadInfo mInfo;
    private DownloaderService mService;
    private final DownloadsDB mDB;
    private final DownloadNotification mNotification;
    private String mUserAgent;

    public DownloadThread(DownloadInfo info, DownloaderService service,
            DownloadNotification notification) {
        mContext = service;
        mInfo = info;
        mService = service;
        mNotification = notification;
        mDB = DownloadsDB.getDB(service);
        mUserAgent = "APKXDL (Linux; U; Android " + android.os.Build.VERSION.RELEASE + ";"
                + Locale.getDefault().toString() + "; " + android.os.Build.DEVICE + "/"
                + android.os.Build.ID + ")" +
                service.getPackageName();
    }

    /**
     * Returns the default user agent
     */
    private String userAgent() {
        return mUserAgent;
    }

    /**
     * State for the entire run() method.
     */
    private static class State {
        public String mFilename;
        public FileOutputStream mStream;
        public boolean mCountRetry = false;
        public int mRetryAfter = 0;
        public int mRedirectCount = 0;
        public String mNewUri;
        public boolean mGotData = false;
        public String mRequestUri;

        public State(DownloadInfo info, DownloaderService service) {
            mRedirectCount = info.mRedirectCount;
            mRequestUri = info.mUri;
            mFilename = service.generateTempSaveFileName(info.mFileName);
        }
    }

    /**
     * State within executeDownload()
     */
    private static class InnerState {
        public int mBytesSoFar = 0;
        public int mBytesThisSession = 0;
        public String mHeaderETag;
        public boolean mContinuingDownload = false;
        public String mHeaderContentLength;
        public String mHeaderContentDisposition;
        public String mHeaderContentLocation;
        public int mBytesNotified = 0;
        public long mTimeLastNotification = 0;
    }

    /**
     * Raised from methods called by run() to indicate that the current request
     * should be stopped immediately. Note the message passed to this exception
     * will be logged and therefore must be guaranteed not to contain any PII,
     * meaning it generally can't include any information about the request URI,
     * headers, or destination filename.
     */
    private class StopRequest extends Throwable {
    	
        private static final long serialVersionUID = 6338592678988347973L;
        public int mFinalStatus;

        public StopRequest(int finalStatus, String message) {
            super(message);
            mFinalStatus = finalStatus;
        }

        public StopRequest(int finalStatus, String message, Throwable throwable) {
            super(message, throwable);
            mFinalStatus = finalStatus;
        }
    }

    /**
     * Raised from methods called by executeDownload() to indicate that the
     * download should be retried immediately.
     */
    private class RetryDownload extends Throwable {

        private static final long serialVersionUID = 6196036036517540229L;
    }

    /**
     * Executes the download in a separate thread
     */
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        State state = new State(mInfo, mService);
        PowerManager.WakeLock wakeLock = null;
        int finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR;

        try {
            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
            wakeLock.acquire();

            if (Constants.LOGV) {
                Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName);
                Log.v(Constants.TAG, "  at " + mInfo.mUri);
            }

            boolean finished = false;
            while (!finished) {
                if (Constants.LOGV) {
                    Log.v(Constants.TAG, "initiating download for " + mInfo.mFileName);
                    Log.v(Constants.TAG, "  at " + mInfo.mUri);
                }
                // Set or unset proxy, which may have changed since last GET
                // request.
                // setDefaultProxy() supports null as proxy parameter.
                URL url = new URL(state.mRequestUri);
                HttpURLConnection request = (HttpURLConnection)url.openConnection();
                request.setRequestProperty("User-Agent", userAgent());
                try {
                    executeDownload(state, request);
                    finished = true;
                } catch (RetryDownload exc) {
                    // fall through
                } finally {
                    request.disconnect();
                    request = null;
                }
            }

            if (Constants.LOGV) {
                Log.v(Constants.TAG, "download completed for " + mInfo.mFileName);
                Log.v(Constants.TAG, "  at " + mInfo.mUri);
            }
            finalizeDestinationFile(state);
            finalStatus = DownloaderService.STATUS_SUCCESS;
        } catch (StopRequest error) {
            // remove the cause before printing, in case it contains PII
            Log.w(Constants.TAG,
                    "Aborting request for download " + mInfo.mFileName + ": " + error.getMessage());
            error.printStackTrace();
            finalStatus = error.mFinalStatus;
            // fall through to finally block
        } catch (Throwable ex) { // sometimes the socket code throws unchecked
                                 // exceptions
            Log.w(Constants.TAG, "Exception for " + mInfo.mFileName + ": " + ex);
            finalStatus = DownloaderService.STATUS_UNKNOWN_ERROR;
            // falls through to the code that reports an error
        } finally {
            if (wakeLock != null) {
                wakeLock.release();
                wakeLock = null;
            }
            cleanupDestination(state, finalStatus);
            notifyDownloadCompleted(finalStatus, state.mCountRetry, state.mRetryAfter,
                    state.mRedirectCount, state.mGotData, state.mFilename);
        }
    }

    /**
     * Fully execute a single download request - setup and send the request,
     * handle the response, and transfer the data to the destination file.
     */
    private void executeDownload(State state, HttpURLConnection request)
            throws StopRequest, RetryDownload {
        InnerState innerState = new InnerState();
        byte data[] = new byte[Constants.BUFFER_SIZE];

        checkPausedOrCanceled(state);

        setupDestinationFile(state, innerState);
        addRequestHeaders(innerState, request);

        // check just before sending the request to avoid using an invalid
        // connection at all
        checkConnectivity(state);

        mNotification.onDownloadStateChanged(IDownloaderClient.STATE_CONNECTING);
        int responseCode = sendRequest(state, request);
        handleExceptionalStatus(state, innerState, request, responseCode);

        if (Constants.LOGV) {
            Log.v(Constants.TAG, "received response for " + mInfo.mUri);
        }

        processResponseHeaders(state, innerState, request);
        InputStream entityStream = openResponseEntity(state, request);
        mNotification.onDownloadStateChanged(IDownloaderClient.STATE_DOWNLOADING);
        transferData(state, innerState, data, entityStream);
    }

    /**
     * Check if current connectivity is valid for this request.
     */
    private void checkConnectivity(State state) throws StopRequest {
        switch (mService.getNetworkAvailabilityState(mDB)) {
            case DownloaderService.NETWORK_OK:
                return;
            case DownloaderService.NETWORK_NO_CONNECTION:
                throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK,
                        "waiting for network to return");
            case DownloaderService.NETWORK_TYPE_DISALLOWED_BY_REQUESTOR:
                throw new StopRequest(
                        DownloaderService.STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION,
                        "waiting for wifi or for download over cellular to be authorized");
            case DownloaderService.NETWORK_CANNOT_USE_ROAMING:
                throw new StopRequest(DownloaderService.STATUS_WAITING_FOR_NETWORK,
                        "roaming is not allowed");
            case DownloaderService.NETWORK_UNUSABLE_DUE_TO_SIZE:
                throw new StopRequest(DownloaderService.STATUS_QUEUED_FOR_WIFI, "waiting for wifi");
        }
    }

    /**
     * Transfer as much data as possible from the HTTP response to the
     * destination file.
     *
     * @param data buffer to use to read data
     * @param entityStream stream for reading the HTTP response entity
     */
    private void transferData(State state, InnerState innerState, byte[] data,
            InputStream entityStream) throws StopRequest {
        for (;;) {
            int bytesRead = readFromResponse(state, innerState, data, entityStream);
            if (bytesRead == -1) { // success, end of stream already reached
                handleEndOfStream(state, innerState);
                return;
            }

            state.mGotData = true;
            writeDataToDestination(state, data, bytesRead);
            innerState.mBytesSoFar += bytesRead;
            innerState.mBytesThisSession += bytesRead;
            reportProgress(state, innerState);

            checkPausedOrCanceled(state);
        }
    }

    /**
     * Called after a successful completion to take any necessary action on the
     * downloaded file.
     */
    private void finalizeDestinationFile(State state) throws StopRequest {
        syncDestination(state);
        String tempFilename = state.mFilename;
        String finalFilename = Helpers.generateSaveFileName(mService, mInfo.mFileName);
        if (!state.mFilename.equals(finalFilename)) {
            File startFile = new File(tempFilename);
            File destFile = new File(finalFilename);
            if (mInfo.mTotalBytes != -1 && mInfo.mCurrentBytes == mInfo.mTotalBytes) {
                if (!startFile.renameTo(destFile)) {
                    throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
                            "unable to finalize destination file");
                }
            } else {
                throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY,
                        "file delivered with incorrect size. probably due to network not browser configured");
            }
        }
    }

    /**
     * Called just before the thread finishes, regardless of status, to take any
     * necessary action on the downloaded file.
     */
    private void cleanupDestination(State state, int finalStatus) {
        closeDestination(state);
        if (state.mFilename != null && DownloaderService.isStatusError(finalStatus)) {
            new File(state.mFilename).delete();
            state.mFilename = null;
        }
    }

    /**
     * Sync the destination file to storage.
     */
    private void syncDestination(State state) {
        FileOutputStream downloadedFileStream = null;
        try {
            downloadedFileStream = new FileOutputStream(state.mFilename, true);
            downloadedFileStream.getFD().sync();
        } catch (FileNotFoundException ex) {
            Log.w(Constants.TAG, "file " + state.mFilename + " not found: " + ex);
        } catch (SyncFailedException ex) {
            Log.w(Constants.TAG, "file " + state.mFilename + " sync failed: " + ex);
        } catch (IOException ex) {
            Log.w(Constants.TAG, "IOException trying to sync " + state.mFilename + ": " + ex);
        } catch (RuntimeException ex) {
            Log.w(Constants.TAG, "exception while syncing file: ", ex);
        } finally {
            if (downloadedFileStream != null) {
                try {
                    downloadedFileStream.close();
                } catch (IOException ex) {
                    Log.w(Constants.TAG, "IOException while closing synced file: ", ex);
                } catch (RuntimeException ex) {
                    Log.w(Constants.TAG, "exception while closing file: ", ex);
                }
            }
        }
    }

    /**
     * Close the destination output stream.
     */
    private void closeDestination(State state) {
        try {
            // close the file
            if (state.mStream != null) {
                state.mStream.close();
                state.mStream = null;
            }
        } catch (IOException ex) {
            if (Constants.LOGV) {
                Log.v(Constants.TAG, "exception when closing the file after download : " + ex);
            }
            // nothing can really be done if the file can't be closed
        }
    }

    /**
     * Check if the download has been paused or canceled, stopping the request
     * appropriately if it has been.
     */
    private void checkPausedOrCanceled(State state) throws StopRequest {
        if (mService.getControl() == DownloaderService.CONTROL_PAUSED) {
            int status = mService.getStatus();
            switch (status) {
                case DownloaderService.STATUS_PAUSED_BY_APP:
                    throw new StopRequest(mService.getStatus(),
                            "download paused");
            }
        }
    }

    /**
     * Report download progress through the database if necessary.
     */
    private void reportProgress(State state, InnerState innerState) {
        long now = System.currentTimeMillis();
        if (innerState.mBytesSoFar - innerState.mBytesNotified
                > Constants.MIN_PROGRESS_STEP
                && now - innerState.mTimeLastNotification
                > Constants.MIN_PROGRESS_TIME) {
            // we store progress updates to the database here
            mInfo.mCurrentBytes = innerState.mBytesSoFar;
            mDB.updateDownloadCurrentBytes(mInfo);

            innerState.mBytesNotified = innerState.mBytesSoFar;
            innerState.mTimeLastNotification = now;

            long totalBytesSoFar = innerState.mBytesThisSession + mService.mBytesSoFar;

            if (Constants.LOGVV) {
                Log.v(Constants.TAG, "downloaded " + mInfo.mCurrentBytes + " out of "
                        + mInfo.mTotalBytes);
                Log.v(Constants.TAG, "     total " + totalBytesSoFar + " out of "
                        + mService.mTotalLength);
            }

            mService.notifyUpdateBytes(totalBytesSoFar);
        }
    }

    /**
     * Write a data buffer to the destination file.
     *
     * @param data buffer containing the data to write
     * @param bytesRead how many bytes to write from the buffer
     */
    private void writeDataToDestination(State state, byte[] data, int bytesRead)
            throws StopRequest {
        for (;;) {
            try {
                if (state.mStream == null) {
                    state.mStream = new FileOutputStream(state.mFilename, true);
                }
                state.mStream.write(data, 0, bytesRead);
                // we close after every write --- this may be too inefficient
                closeDestination(state);
                return;
            } catch (IOException ex) {
                if (!Helpers.isExternalMediaMounted()) {
                    throw new StopRequest(DownloaderService.STATUS_DEVICE_NOT_FOUND_ERROR,
                            "external media not mounted while writing destination file");
                }

                long availableBytes =
                        Helpers.getAvailableBytes(Helpers.getFilesystemRoot(state.mFilename));
                if (availableBytes < bytesRead) {
                    throw new StopRequest(DownloaderService.STATUS_INSUFFICIENT_SPACE_ERROR,
                            "insufficient space while writing destination file", ex);
                }
                throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
                        "while writing destination file: " + ex.toString(), ex);
            }
        }
    }

    /**
     * Called when we've reached the end of the HTTP response stream, to update
     * the database and check for consistency.
     */
    private void handleEndOfStream(State state, InnerState innerState) throws StopRequest {
        mInfo.mCurrentBytes = innerState.mBytesSoFar;
        // this should always be set from the market
        // if ( innerState.mHeaderContentLength == null ) {
        // mInfo.mTotalBytes = innerState.mBytesSoFar;
        // }
        mDB.updateDownload(mInfo);

        boolean lengthMismatched = (innerState.mHeaderContentLength != null)
                && (innerState.mBytesSoFar != Integer.parseInt(innerState.mHeaderContentLength));
        if (lengthMismatched) {
            if (cannotResume(innerState)) {
                throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME,
                        "mismatched content length");
            } else {
                throw new StopRequest(getFinalStatusForHttpError(state),
                        "closed socket before end of file");
            }
        }
    }

    private boolean cannotResume(InnerState innerState) {
        return innerState.mBytesSoFar > 0 && innerState.mHeaderETag == null;
    }

    /**
     * Read some data from the HTTP response stream, handling I/O errors.
     *
     * @param data buffer to use to read data
     * @param entityStream stream for reading the HTTP response entity
     * @return the number of bytes actually read or -1 if the end of the stream
     *         has been reached
     */
    private int readFromResponse(State state, InnerState innerState, byte[] data,
            InputStream entityStream) throws StopRequest {
        try {
            return entityStream.read(data);
        } catch (IOException ex) {
            logNetworkState();
            mInfo.mCurrentBytes = innerState.mBytesSoFar;
            mDB.updateDownload(mInfo);
            if (cannotResume(innerState)) {
                String message = "while reading response: " + ex.toString()
                        + ", can't resume interrupted download with no ETag";
                throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME,
                        message, ex);
            } else {
                throw new StopRequest(getFinalStatusForHttpError(state),
                        "while reading response: " + ex.toString(), ex);
            }
        }
    }

    /**
     * Open a stream for the HTTP response entity, handling I/O errors.
     *
     * @return an InputStream to read the response entity
     */
    private InputStream openResponseEntity(State state, HttpURLConnection response)
            throws StopRequest {
        try {
            return response.getInputStream();
        } catch (IOException ex) {
            logNetworkState();
            throw new StopRequest(getFinalStatusForHttpError(state),
                    "while getting entity: " + ex.toString(), ex);
        }
    }

    private void logNetworkState() {
        if (Constants.LOGX) {
            Log.i(Constants.TAG,
                    "Net "
                            + (mService.getNetworkAvailabilityState(mDB) == DownloaderService.NETWORK_OK ? "Up"
                                    : "Down"));
        }
    }

    /**
     * Read HTTP response headers and take appropriate action, including setting
     * up the destination file and updating the database.
     */
    private void processResponseHeaders(State state, InnerState innerState, HttpURLConnection response)
            throws StopRequest {
        if (innerState.mContinuingDownload) {
            // ignore response headers on resume requests
            return;
        }

        readResponseHeaders(state, innerState, response);

        try {
            state.mFilename = mService.generateSaveFile(mInfo.mFileName, mInfo.mTotalBytes);
        } catch (DownloaderService.GenerateSaveFileError exc) {
            throw new StopRequest(exc.mStatus, exc.mMessage);
        }
        try {
            state.mStream = new FileOutputStream(state.mFilename);
        } catch (FileNotFoundException exc) {
            // make sure the directory exists
            File pathFile = new File(Helpers.getSaveFilePath(mService));
            try {
                if (pathFile.mkdirs()) {
                    state.mStream = new FileOutputStream(state.mFilename);
                }
            } catch (Exception ex) {
                throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
                        "while opening destination file: " + exc.toString(), exc);
            }
        }
        if (Constants.LOGV) {
            Log.v(Constants.TAG, "writing " + mInfo.mUri + " to " + state.mFilename);
        }

        updateDatabaseFromHeaders(state, innerState);
        // check connectivity again now that we know the total size
        checkConnectivity(state);
    }

    /**
     * Update necessary database fields based on values of HTTP response headers
     * that have been read.
     */
    private void updateDatabaseFromHeaders(State state, InnerState innerState) {
        mInfo.mETag = innerState.mHeaderETag;
        mDB.updateDownload(mInfo);
    }

    /**
     * Read headers from the HTTP response and store them into local state.
     */
    private void readResponseHeaders(State state, InnerState innerState, HttpURLConnection response)
            throws StopRequest {
        String value = response.getHeaderField("Content-Disposition");
        if (value != null) {
            innerState.mHeaderContentDisposition = value;
        }
        value = response.getHeaderField("Content-Location");
        if (value != null) {
            innerState.mHeaderContentLocation = value;
        }
        value = response.getHeaderField("ETag");
        if (value != null) {
            innerState.mHeaderETag = value;
        }
        String headerTransferEncoding = null;
        value = response.getHeaderField("Transfer-Encoding");
        if (value != null) {
            headerTransferEncoding = value;
        }
        String headerContentType = null;
        value = response.getHeaderField("Content-Type");
        if (value != null) {
            headerContentType = value;
            if (!headerContentType.equals("application/vnd.android.obb")) {
                throw new StopRequest(DownloaderService.STATUS_FILE_DELIVERED_INCORRECTLY,
                        "file delivered with incorrect Mime type");
            }
        }

        if (headerTransferEncoding == null) {
            long contentLength = response.getContentLength();
            if (value != null) {
                // this is always set from Market
                if (contentLength != -1 && contentLength != mInfo.mTotalBytes) {
                    // we're most likely on a bad wifi connection -- we should
                    // probably
                    // also look at the mime type --- but the size mismatch is
                    // enough
                    // to tell us that something is wrong here
                    Log.e(Constants.TAG, "Incorrect file size delivered.");
                } else {
                    innerState.mHeaderContentLength = Long.toString(contentLength);
                }
            }
        } else {
            // Ignore content-length with transfer-encoding - 2616 4.4 3
            if (Constants.LOGVV) {
                Log.v(Constants.TAG,
                        "ignoring content-length because of xfer-encoding");
            }
        }
        if (Constants.LOGVV) {
            Log.v(Constants.TAG, "Content-Disposition: " +
                    innerState.mHeaderContentDisposition);
            Log.v(Constants.TAG, "Content-Length: " + innerState.mHeaderContentLength);
            Log.v(Constants.TAG, "Content-Location: " + innerState.mHeaderContentLocation);
            Log.v(Constants.TAG, "ETag: " + innerState.mHeaderETag);
            Log.v(Constants.TAG, "Transfer-Encoding: " + headerTransferEncoding);
        }

        boolean noSizeInfo = innerState.mHeaderContentLength == null
                && (headerTransferEncoding == null
                || !headerTransferEncoding.equalsIgnoreCase("chunked"));
        if (noSizeInfo) {
            throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR,
                    "can't know size of download, giving up");
        }
    }

    /**
     * Check the HTTP response status and handle anything unusual (e.g. not
     * 200/206).
     */
    private void handleExceptionalStatus(State state, InnerState innerState, HttpURLConnection connection, int responseCode)
            throws StopRequest, RetryDownload {
        if (responseCode == 503 && mInfo.mNumFailed < Constants.MAX_RETRIES) {
            handleServiceUnavailable(state, connection);
        }
        int expectedStatus = innerState.mContinuingDownload ? 206
                : DownloaderService.STATUS_SUCCESS;
        if (responseCode != expectedStatus) {
            handleOtherStatus(state, innerState, responseCode);
        } else {
            // no longer redirected
            state.mRedirectCount = 0;
        }
    }

    /**
     * Handle a status that we don't know how to deal with properly.
     */
    private void handleOtherStatus(State state, InnerState innerState, int statusCode)
            throws StopRequest {
        int finalStatus;
        if (DownloaderService.isStatusError(statusCode)) {
            finalStatus = statusCode;
        } else if (statusCode >= 300 && statusCode < 400) {
            finalStatus = DownloaderService.STATUS_UNHANDLED_REDIRECT;
        } else if (innerState.mContinuingDownload && statusCode == DownloaderService.STATUS_SUCCESS) {
            finalStatus = DownloaderService.STATUS_CANNOT_RESUME;
        } else {
            finalStatus = DownloaderService.STATUS_UNHANDLED_HTTP_CODE;
        }
        throw new StopRequest(finalStatus, "http error " + statusCode);
    }

    /**
     * Add headers for this download to the HTTP request to allow for resume.
     */
    private void addRequestHeaders(InnerState innerState, HttpURLConnection request) {
        if (innerState.mContinuingDownload) {
            if (innerState.mHeaderETag != null) {
                request.setRequestProperty("If-Match", innerState.mHeaderETag);
            }
            request.setRequestProperty("Range", "bytes=" + innerState.mBytesSoFar + "-");
        }
    }

    /**
     * Handle a 503 Service Unavailable status by processing the Retry-After
     * header.
     */
    private void handleServiceUnavailable(State state, HttpURLConnection connection) throws StopRequest {
        if (Constants.LOGVV) {
            Log.v(Constants.TAG, "got HTTP response code 503");
        }
        state.mCountRetry = true;
        String retryAfterValue = connection.getHeaderField("Retry-After");
        if (retryAfterValue != null) {
            try {
                if (Constants.LOGVV) {
                    Log.v(Constants.TAG, "Retry-After :" + retryAfterValue);
                }
                state.mRetryAfter = Integer.parseInt(retryAfterValue);
                if (state.mRetryAfter < 0) {
                    state.mRetryAfter = 0;
                } else {
                    if (state.mRetryAfter < Constants.MIN_RETRY_AFTER) {
                        state.mRetryAfter = Constants.MIN_RETRY_AFTER;
                    } else if (state.mRetryAfter > Constants.MAX_RETRY_AFTER) {
                        state.mRetryAfter = Constants.MAX_RETRY_AFTER;
                    }
                    state.mRetryAfter += Helpers.sRandom.nextInt(Constants.MIN_RETRY_AFTER + 1);
                    state.mRetryAfter *= 1000;
                }
            } catch (NumberFormatException ex) {
                // ignored - retryAfter stays 0 in this case.
            }
        }
        throw new StopRequest(DownloaderService.STATUS_WAITING_TO_RETRY,
                "got 503 Service Unavailable, will retry later");
    }

    /**
     * Send the request to the server, handling any I/O exceptions.
     */
    private int sendRequest(State state, HttpURLConnection request)
            throws StopRequest {
        try {
            return request.getResponseCode();
        } catch (IllegalArgumentException ex) {
            throw new StopRequest(DownloaderService.STATUS_HTTP_DATA_ERROR,
                    "while trying to execute request: " + ex.toString(), ex);
        } catch (IOException ex) {
            logNetworkState();
            throw new StopRequest(getFinalStatusForHttpError(state),
                    "while trying to execute request: " + ex.toString(), ex);
        }
    }

    private int getFinalStatusForHttpError(State state) {
        if (mService.getNetworkAvailabilityState(mDB) != DownloaderService.NETWORK_OK) {
            return DownloaderService.STATUS_WAITING_FOR_NETWORK;
        } else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
            state.mCountRetry = true;
            return DownloaderService.STATUS_WAITING_TO_RETRY;
        } else {
            Log.w(Constants.TAG, "reached max retries for " + mInfo.mNumFailed);
            return DownloaderService.STATUS_HTTP_DATA_ERROR;
        }
    }

    /**
     * Prepare the destination file to receive data. If the file already exists,
     * we'll set up appropriately for resumption.
     */
    private void setupDestinationFile(State state, InnerState innerState)
            throws StopRequest {
        if (state.mFilename != null) { // only true if we've already run a
                                       // thread for this download
            if (!Helpers.isFilenameValid(state.mFilename)) {
                // this should never happen
                throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
                        "found invalid internal destination filename");
            }
            // We're resuming a download that got interrupted
            File f = new File(state.mFilename);
            if (f.exists()) {
                long fileLength = f.length();
                if (fileLength == 0) {
                    // The download hadn't actually started, we can restart from
                    // scratch
                    f.delete();
                    state.mFilename = null;
                } else if (mInfo.mETag == null) {
                    // This should've been caught upon failure
                    f.delete();
                    throw new StopRequest(DownloaderService.STATUS_CANNOT_RESUME,
                            "Trying to resume a download that can't be resumed");
                } else {
                    // All right, we'll be able to resume this download
                    try {
                        state.mStream = new FileOutputStream(state.mFilename, true);
                    } catch (FileNotFoundException exc) {
                        throw new StopRequest(DownloaderService.STATUS_FILE_ERROR,
                                "while opening destination for resuming: " + exc.toString(), exc);
                    }
                    innerState.mBytesSoFar = (int) fileLength;
                    if (mInfo.mTotalBytes != -1) {
                        innerState.mHeaderContentLength = Long.toString(mInfo.mTotalBytes);
                    }
                    innerState.mHeaderETag = mInfo.mETag;
                    innerState.mContinuingDownload = true;
                }
            }
        }

        if (state.mStream != null) {
            closeDestination(state);
        }
    }

    /**
     * Stores information about the completed download, and notifies the
     * initiating application.
     */
    private void notifyDownloadCompleted(
            int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
            String filename) {
        updateDownloadDatabase(
                status, countRetry, retryAfter, redirectCount, gotData, filename);
        if (DownloaderService.isStatusCompleted(status)) {
            // TBD: send status update?
        }
    }

    private void updateDownloadDatabase(
            int status, boolean countRetry, int retryAfter, int redirectCount, boolean gotData,
            String filename) {
        mInfo.mStatus = status;
        mInfo.mRetryAfter = retryAfter;
        mInfo.mRedirectCount = redirectCount;
        mInfo.mLastMod = System.currentTimeMillis();
        if (!countRetry) {
            mInfo.mNumFailed = 0;
        } else if (gotData) {
            mInfo.mNumFailed = 1;
        } else {
            mInfo.mNumFailed++;
        }
        mDB.updateDownload(mInfo);
    }

}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloaderService.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader.impl;

import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
import com.google.android.vending.licensing.AESObfuscator;
import com.google.android.vending.licensing.APKExpansionPolicy;
import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;
import com.google.android.vending.licensing.Policy;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Messenger;
import android.os.SystemClock;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;

import java.io.File;

/**
 * Performs the background downloads requested by applications that use the
 * Downloads provider. This service does not run as a foreground task, so
 * Android may kill it off at will, but it will try to restart itself if it can.
 * Note that Android by default will kill off any process that has an open file
 * handle on the shared (SD Card) partition if the partition is unmounted.
 */
public abstract class DownloaderService extends CustomIntentService implements IDownloaderService {

    public DownloaderService() {
        super("LVLDownloadService");
    }

    private static final String LOG_TAG = "LVLDL";

    // the following NETWORK_* constants are used to indicates specific reasons
    // for disallowing a
    // download from using a network, since specific causes can require special
    // handling

    /**
     * The network is usable for the given download.
     */
    public static final int NETWORK_OK = 1;

    /**
     * There is no network connectivity.
     */
    public static final int NETWORK_NO_CONNECTION = 2;

    /**
     * The download exceeds the maximum size for this network.
     */
    public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3;

    /**
     * The download exceeds the recommended maximum size for this network, the
     * user must confirm for this download to proceed without WiFi.
     */
    public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4;

    /**
     * The current connection is roaming, and the download can't proceed over a
     * roaming connection.
     */
    public static final int NETWORK_CANNOT_USE_ROAMING = 5;

    /**
     * The app requesting the download specific that it can't use the current
     * network connection.
     */
    public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6;

    /**
     * For intents used to notify the user that a download exceeds a size
     * threshold, if this extra is true, WiFi is required for this download
     * size; otherwise, it is only recommended.
     */
    public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired";
    public static final String EXTRA_FILE_NAME = "downloadId";

    /**
     * Used with DOWNLOAD_STATUS
     */
    public static final String EXTRA_STATUS_STATE = "ESS";
    public static final String EXTRA_STATUS_TOTAL_SIZE = "ETS";
    public static final String EXTRA_STATUS_CURRENT_FILE_SIZE = "CFS";
    public static final String EXTRA_STATUS_TOTAL_PROGRESS = "TFP";
    public static final String EXTRA_STATUS_CURRENT_PROGRESS = "CFP";

    public static final String ACTION_DOWNLOADS_CHANGED = "downloadsChanged";

    /**
     * Broadcast intent action sent by the download manager when a download
     * completes.
     */
    public final static String ACTION_DOWNLOAD_COMPLETE = "lvldownloader.intent.action.DOWNLOAD_COMPLETE";

    /**
     * Broadcast intent action sent by the download manager when download status
     * changes.
     */
    public final static String ACTION_DOWNLOAD_STATUS = "lvldownloader.intent.action.DOWNLOAD_STATUS";

    /*
     * Lists the states that the download manager can set on a download to
     * notify applications of the download progress. The codes follow the HTTP
     * families:<br> 1xx: informational<br> 2xx: success<br> 3xx: redirects (not
     * used by the download manager)<br> 4xx: client errors<br> 5xx: server
     * errors
     */

    /**
     * Returns whether the status is informational (i.e. 1xx).
     */
    public static boolean isStatusInformational(int status) {
        return (status >= 100 && status < 200);
    }

    /**
     * Returns whether the status is a success (i.e. 2xx).
     */
    public static boolean isStatusSuccess(int status) {
        return (status >= 200 && status < 300);
    }

    /**
     * Returns whether the status is an error (i.e. 4xx or 5xx).
     */
    public static boolean isStatusError(int status) {
        return (status >= 400 && status < 600);
    }

    /**
     * Returns whether the status is a client error (i.e. 4xx).
     */
    public static boolean isStatusClientError(int status) {
        return (status >= 400 && status < 500);
    }

    /**
     * Returns whether the status is a server error (i.e. 5xx).
     */
    public static boolean isStatusServerError(int status) {
        return (status >= 500 && status < 600);
    }

    /**
     * Returns whether the download has completed (either with success or
     * error).
     */
    public static boolean isStatusCompleted(int status) {
        return (status >= 200 && status < 300)
                || (status >= 400 && status < 600);
    }

    /**
     * This download hasn't stated yet
     */
    public static final int STATUS_PENDING = 190;

    /**
     * This download has started
     */
    public static final int STATUS_RUNNING = 192;

    /**
     * This download has been paused by the owning app.
     */
    public static final int STATUS_PAUSED_BY_APP = 193;

    /**
     * This download encountered some network error and is waiting before
     * retrying the request.
     */
    public static final int STATUS_WAITING_TO_RETRY = 194;

    /**
     * This download is waiting for network connectivity to proceed.
     */
    public static final int STATUS_WAITING_FOR_NETWORK = 195;

    /**
     * This download is waiting for a Wi-Fi connection to proceed or for
     * permission to download over cellular.
     */
    public static final int STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION = 196;

    /**
     * This download is waiting for a Wi-Fi connection to proceed.
     */
    public static final int STATUS_QUEUED_FOR_WIFI = 197;

    /**
     * This download has successfully completed. Warning: there might be other
     * status values that indicate success in the future. Use isSucccess() to
     * capture the entire category.
     *
     * @hide
     */
    public static final int STATUS_SUCCESS = 200;

    /**
     * The requested URL is no longer available
     */
    public static final int STATUS_FORBIDDEN = 403;

    /**
     * The file was delivered incorrectly
     */
    public static final int STATUS_FILE_DELIVERED_INCORRECTLY = 487;

    /**
     * The requested destination file already exists.
     */
    public static final int STATUS_FILE_ALREADY_EXISTS_ERROR = 488;

    /**
     * Some possibly transient error occurred, but we can't resume the download.
     */
    public static final int STATUS_CANNOT_RESUME = 489;

    /**
     * This download was canceled
     *
     * @hide
     */
    public static final int STATUS_CANCELED = 490;

    /**
     * This download has completed with an error. Warning: there will be other
     * status values that indicate errors in the future. Use isStatusError() to
     * capture the entire category.
     */
    public static final int STATUS_UNKNOWN_ERROR = 491;

    /**
     * This download couldn't be completed because of a storage issue.
     * Typically, that's because the filesystem is missing or full. Use the more
     * specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR} and
     * {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
     *
     * @hide
     */
    public static final int STATUS_FILE_ERROR = 492;

    /**
     * This download couldn't be completed because of an HTTP redirect response
     * that the download manager couldn't handle.
     *
     * @hide
     */
    public static final int STATUS_UNHANDLED_REDIRECT = 493;

    /**
     * This download couldn't be completed because of an unspecified unhandled
     * HTTP code.
     *
     * @hide
     */
    public static final int STATUS_UNHANDLED_HTTP_CODE = 494;

    /**
     * This download couldn't be completed because of an error receiving or
     * processing data at the HTTP level.
     *
     * @hide
     */
    public static final int STATUS_HTTP_DATA_ERROR = 495;

    /**
     * This download couldn't be completed because of an HttpException while
     * setting up the request.
     *
     * @hide
     */
    public static final int STATUS_HTTP_EXCEPTION = 496;

    /**
     * This download couldn't be completed because there were too many
     * redirects.
     *
     * @hide
     */
    public static final int STATUS_TOO_MANY_REDIRECTS = 497;

    /**
     * This download couldn't be completed due to insufficient storage space.
     * Typically, this is because the SD card is full.
     *
     * @hide
     */
    public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;

    /**
     * This download couldn't be completed because no external storage device
     * was found. Typically, this is because the SD card is not mounted.
     *
     * @hide
     */
    public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;

    /**
     * This download is allowed to run.
     *
     * @hide
     */
    public static final int CONTROL_RUN = 0;

    /**
     * This download must pause at the first opportunity.
     *
     * @hide
     */
    public static final int CONTROL_PAUSED = 1;

    /**
     * This download is visible but only shows in the notifications while it's
     * in progress.
     *
     * @hide
     */
    public static final int VISIBILITY_VISIBLE = 0;

    /**
     * This download is visible and shows in the notifications while in progress
     * and after completion.
     *
     * @hide
     */
    public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;

    /**
     * This download doesn't show in the UI or in the notifications.
     *
     * @hide
     */
    public static final int VISIBILITY_HIDDEN = 2;

    /**
     * Bit flag for setAllowedNetworkTypes corresponding to
     * {@link ConnectivityManager#TYPE_MOBILE}.
     */
    public static final int NETWORK_MOBILE = 1 << 0;

    /**
     * Bit flag for setAllowedNetworkTypes corresponding to
     * {@link ConnectivityManager#TYPE_WIFI}.
     */
    public static final int NETWORK_WIFI = 1 << 1;

    private final static String TEMP_EXT = ".tmp";

    /**
     * Service thread status
     */
    private static boolean sIsRunning;

    @Override
    public IBinder onBind(Intent paramIntent) {
        Log.d(Constants.TAG, "Service Bound");
        return this.mServiceMessenger.getBinder();
    }

    /**
     * Network state.
     */
    private boolean mIsConnected;
    private boolean mIsFailover;
    private boolean mIsCellularConnection;
    private boolean mIsRoaming;
    private boolean mIsAtLeast3G;
    private boolean mIsAtLeast4G;
    private boolean mStateChanged;

    /**
     * Download state
     */
    private int mControl;
    private int mStatus;

    public boolean isWiFi() {
        return mIsConnected && !mIsCellularConnection;
    }

    /**
     * Bindings to important services
     */
    private ConnectivityManager mConnectivityManager;
    private WifiManager mWifiManager;

    /**
     * Package we are downloading for (defaults to package of application)
     */
    private PackageInfo mPackageInfo;

    /**
     * Byte counts
     */
    long mBytesSoFar;
    long mTotalLength;
    int mFileCount;

    /**
     * Used for calculating time remaining and speed
     */
    long mBytesAtSample;
    long mMillisecondsAtSample;
    float mAverageDownloadSpeed;

    /**
     * Our binding to the network state broadcasts
     */
    private BroadcastReceiver mConnReceiver;
    final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this);
    final private Messenger mServiceMessenger = mServiceStub.getMessenger();
    private Messenger mClientMessenger;
    private DownloadNotification mNotification;
    private PendingIntent mPendingIntent;
    private PendingIntent mAlarmIntent;

    /**
     * Updates the network type based upon the type and subtype returned from
     * the connectivity manager. Subtype is only used for cellular signals.
     *
     * @param type
     * @param subType
     */
    private void updateNetworkType(int type, int subType) {
        switch (type) {
            case ConnectivityManager.TYPE_WIFI:
            case ConnectivityManager.TYPE_ETHERNET:
            case ConnectivityManager.TYPE_BLUETOOTH:
                mIsCellularConnection = false;
                mIsAtLeast3G = false;
                mIsAtLeast4G = false;
                break;
            case ConnectivityManager.TYPE_WIMAX:
                mIsCellularConnection = true;
                mIsAtLeast3G = true;
                mIsAtLeast4G = true;
                break;
            case ConnectivityManager.TYPE_MOBILE:
                mIsCellularConnection = true;
                switch (subType) {
                    case TelephonyManager.NETWORK_TYPE_1xRTT:
                    case TelephonyManager.NETWORK_TYPE_CDMA:
                    case TelephonyManager.NETWORK_TYPE_EDGE:
                    case TelephonyManager.NETWORK_TYPE_GPRS:
                    case TelephonyManager.NETWORK_TYPE_IDEN:
                        mIsAtLeast3G = false;
                        mIsAtLeast4G = false;
                        break;
                    case TelephonyManager.NETWORK_TYPE_HSDPA:
                    case TelephonyManager.NETWORK_TYPE_HSUPA:
                    case TelephonyManager.NETWORK_TYPE_HSPA:
                    case TelephonyManager.NETWORK_TYPE_EVDO_0:
                    case TelephonyManager.NETWORK_TYPE_EVDO_A:
                    case TelephonyManager.NETWORK_TYPE_UMTS:
                        mIsAtLeast3G = true;
                        mIsAtLeast4G = false;
                        break;
                    case TelephonyManager.NETWORK_TYPE_LTE: // 4G
                    case TelephonyManager.NETWORK_TYPE_EHRPD: // 3G ++ interop
                                                              // with 4G
                    case TelephonyManager.NETWORK_TYPE_HSPAP: // 3G ++ but
                                                              // marketed as
                                                              // 4G
                        mIsAtLeast3G = true;
                        mIsAtLeast4G = true;
                        break;
                    default:
                        mIsCellularConnection = false;
                        mIsAtLeast3G = false;
                        mIsAtLeast4G = false;
                }
        }
    }

    private void updateNetworkState(NetworkInfo info) {
        boolean isConnected = mIsConnected;
        boolean isFailover = mIsFailover;
        boolean isCellularConnection = mIsCellularConnection;
        boolean isRoaming = mIsRoaming;
        boolean isAtLeast3G = mIsAtLeast3G;
        if (null != info) {
            mIsRoaming = info.isRoaming();
            mIsFailover = info.isFailover();
            mIsConnected = info.isConnected();
            updateNetworkType(info.getType(), info.getSubtype());
        } else {
            mIsRoaming = false;
            mIsFailover = false;
            mIsConnected = false;
            updateNetworkType(-1, -1);
        }
        mStateChanged = (mStateChanged || isConnected != mIsConnected
                || isFailover != mIsFailover
                || isCellularConnection != mIsCellularConnection
                || isRoaming != mIsRoaming || isAtLeast3G != mIsAtLeast3G);
        if (Constants.LOGVV) {
            if (mStateChanged) {
                Log.v(LOG_TAG, "Network state changed: ");
                Log.v(LOG_TAG, "Starting State: " +
                        (isConnected ? "Connected " : "Not Connected ") +
                        (isCellularConnection ? "Cellular " : "WiFi ") +
                        (isRoaming ? "Roaming " : "Local ") +
                        (isAtLeast3G ? "3G+ " : "<3G "));
                Log.v(LOG_TAG, "Ending State: " +
                        (mIsConnected ? "Connected " : "Not Connected ") +
                        (mIsCellularConnection ? "Cellular " : "WiFi ") +
                        (mIsRoaming ? "Roaming " : "Local ") +
                        (mIsAtLeast3G ? "3G+ " : "<3G "));

                if (isServiceRunning()) {
                    if (mIsRoaming) {
                        mStatus = STATUS_WAITING_FOR_NETWORK;
                        mControl = CONTROL_PAUSED;
                    } else if (mIsCellularConnection) {
                        DownloadsDB db = DownloadsDB.getDB(this);
                        int flags = db.getFlags();
                        if (0 == (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) {
                            mStatus = STATUS_QUEUED_FOR_WIFI;
                            mControl = CONTROL_PAUSED;
                        }
                    }
                }

            }
        }
    }

    /**
     * Polls the network state, setting the flags appropriately.
     */
    void pollNetworkState() {
        if (null == mConnectivityManager) {
            mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        }
        if (null == mWifiManager) {
            mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        }
        if (mConnectivityManager == null) {
            Log.w(Constants.TAG,
                    "couldn't get connectivity manager to poll network state");
        } else {
            NetworkInfo activeInfo = mConnectivityManager
                    .getActiveNetworkInfo();
            updateNetworkState(activeInfo);
        }
    }

    public static final int NO_DOWNLOAD_REQUIRED = 0;
    public static final int LVL_CHECK_REQUIRED = 1;
    public static final int DOWNLOAD_REQUIRED = 2;

    public static final String EXTRA_PACKAGE_NAME = "EPN";
    public static final String EXTRA_PENDING_INTENT = "EPI";
    public static final String EXTRA_MESSAGE_HANDLER = "EMH";

    /**
     * Returns true if the LVL check is required
     *
     * @param db a downloads DB synchronized with the latest state
     * @param pi the package info for the project
     * @return returns true if the filenames need to be returned
     */
    private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo pi) {
        // we need to update the LVL check and get a successful status to
        // proceed
        if (db.mVersionCode != pi.versionCode) {
            return true;
        }
        return false;
    }

    /**
     * Careful! Only use this internally.
     *
     * @return whether we think the service is running
     */
    private static synchronized boolean isServiceRunning() {
        return sIsRunning;
    }

    private static synchronized void setServiceRunning(boolean isRunning) {
        sIsRunning = isRunning;
    }

    public static int startDownloadServiceIfRequired(Context context,
            Intent intent, Class<?> serviceClass) throws NameNotFoundException {
        final PendingIntent pendingIntent = (PendingIntent) intent
                .getParcelableExtra(EXTRA_PENDING_INTENT);
        return startDownloadServiceIfRequired(context, pendingIntent,
                serviceClass);
    }

    public static int startDownloadServiceIfRequired(Context context,
            PendingIntent pendingIntent, Class<?> serviceClass)
            throws NameNotFoundException
    {
        String packageName = context.getPackageName();
        String className = serviceClass.getName();

        return startDownloadServiceIfRequired(context, pendingIntent,
                packageName, className);
    }

    /**
     * Starts the download if necessary. This function starts a flow that does `
     * many things. 1) Checks to see if the APK version has been checked and the
     * metadata database updated 2) If the APK version does not match, checks
     * the new LVL status to see if a new download is required 3) If the APK
     * version does match, then checks to see if the download(s) have been
     * completed 4) If the downloads have been completed, returns
     * NO_DOWNLOAD_REQUIRED The idea is that this can be called during the
     * startup of an application to quickly ascertain if the application needs
     * to wait to hear about any updated APK expansion files. Note that this
     * does mean that the application MUST be run for the first time with a
     * network connection, even if Market delivers all of the files.
     *
     * @param context
     * @param pendingIntent
     * @return true if the app should wait for more guidance from the
     *         downloader, false if the app can continue
     * @throws NameNotFoundException
     */
    public static int startDownloadServiceIfRequired(Context context,
            PendingIntent pendingIntent, String classPackage, String className)
            throws NameNotFoundException {
        // first: do we need to do an LVL update?
        // we begin by getting our APK version from the package manager
        final PackageInfo pi = context.getPackageManager().getPackageInfo(
                context.getPackageName(), 0);

        int status = NO_DOWNLOAD_REQUIRED;

        // the database automatically reads the metadata for version code
        // and download status when the instance is created
        DownloadsDB db = DownloadsDB.getDB(context);

        // we need to update the LVL check and get a successful status to
        // proceed
        if (isLVLCheckRequired(db, pi)) {
            status = LVL_CHECK_REQUIRED;
        }
        // we don't have to update LVL. do we still have a download to start?
        if (db.mStatus == 0) {
            DownloadInfo[] infos = db.getDownloads();
            if (null != infos) {
                for (DownloadInfo info : infos) {
                    if (!Helpers.doesFileExist(context, info.mFileName, info.mTotalBytes, true)) {
                        status = DOWNLOAD_REQUIRED;
                        db.updateStatus(-1);
                        break;
                    }
                }
            }
        } else {
            status = DOWNLOAD_REQUIRED;
        }
        switch (status) {
            case DOWNLOAD_REQUIRED:
            case LVL_CHECK_REQUIRED:
                Intent fileIntent = new Intent();
                fileIntent.setClassName(classPackage, className);
                fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
                context.startService(fileIntent);
                break;
        }
        return status;
    }

    @Override
    public void requestAbortDownload() {
        mControl = CONTROL_PAUSED;
        mStatus = STATUS_CANCELED;
    }

    @Override
    public void requestPauseDownload() {
        mControl = CONTROL_PAUSED;
        mStatus = STATUS_PAUSED_BY_APP;
    }

    @Override
    public void setDownloadFlags(int flags) {
        DownloadsDB.getDB(this).updateFlags(flags);
    }

    @Override
    public void requestContinueDownload() {
        if (mControl == CONTROL_PAUSED) {
            mControl = CONTROL_RUN;
        }
        Intent fileIntent = new Intent(this, this.getClass());
        fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
        this.startService(fileIntent);
    }

    public abstract String getPublicKey();

    public abstract byte[] getSALT();

    public abstract String getAlarmReceiverClassName();

    private class LVLRunnable implements Runnable {
        LVLRunnable(Context context, PendingIntent intent) {
            mContext = context;
            mPendingIntent = intent;
        }

        final Context mContext;

        @Override
        public void run() {
            setServiceRunning(true);
            mNotification.onDownloadStateChanged(IDownloaderClient.STATE_FETCHING_URL);
            String deviceId = Secure.getString(mContext.getContentResolver(),
                    Secure.ANDROID_ID);

            final APKExpansionPolicy aep = new APKExpansionPolicy(mContext,
                    new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId));

            // reset our policy back to the start of the world to force a
            // re-check
            aep.resetPolicy();

            // let's try and get the OBB file from LVL first
            // Construct the LicenseChecker with a Policy.
            final LicenseChecker checker = new LicenseChecker(mContext, aep,
                    getPublicKey() // Your public licensing key.
            );
            checker.checkAccess(new LicenseCheckerCallback() {

                @Override
                public void allow(int reason) {
                    try {
                        int count = aep.getExpansionURLCount();
                        DownloadsDB db = DownloadsDB.getDB(mContext);
                        int status = 0;
                        if (count != 0) {
                            for (int i = 0; i < count; i++) {
                                String currentFileName = aep
                                        .getExpansionFileName(i);
                                if (null != currentFileName) {
                                    DownloadInfo di = new DownloadInfo(i,
                                            currentFileName, mContext.getPackageName());

                                    long fileSize = aep.getExpansionFileSize(i);
                                    if (handleFileUpdated(db, i, currentFileName,
                                            fileSize)) {
                                        status |= -1;
                                        di.resetDownload();
                                        di.mUri = aep.getExpansionURL(i);
                                        di.mTotalBytes = fileSize;
                                        di.mStatus = status;
                                        db.updateDownload(di);
                                    } else {
                                        // we need to read the download
                                        // information
                                        // from
                                        // the database
                                        DownloadInfo dbdi = db
                                                .getDownloadInfoByFileName(di.mFileName);
                                        if (null == dbdi) {
                                            // the file exists already and is
                                            // the
                                            // correct size
                                            // was delivered by Market or
                                            // through
                                            // another mechanism
                                            Log.d(LOG_TAG, "file " + di.mFileName
                                                    + " found. Not downloading.");
                                            di.mStatus = STATUS_SUCCESS;
                                            di.mTotalBytes = fileSize;
                                            di.mCurrentBytes = fileSize;
                                            di.mUri = aep.getExpansionURL(i);
                                            db.updateDownload(di);
                                        } else if (dbdi.mStatus != STATUS_SUCCESS) {
                                            // we just update the URL
                                            dbdi.mUri = aep.getExpansionURL(i);
                                            db.updateDownload(dbdi);
                                            status |= -1;
                                        }
                                    }
                                }
                            }
                        }
                        // first: do we need to do an LVL update?
                        // we begin by getting our APK version from the package
                        // manager
                        PackageInfo pi;
                        try {
                            pi = mContext.getPackageManager().getPackageInfo(
                                    mContext.getPackageName(), 0);
                            db.updateMetadata(pi.versionCode, status);
                            Class<?> serviceClass = DownloaderService.this.getClass();
                            switch (startDownloadServiceIfRequired(mContext, mPendingIntent,
                                    serviceClass)) {
                                case NO_DOWNLOAD_REQUIRED:
                                    mNotification
                                            .onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED);
                                    break;
                                case LVL_CHECK_REQUIRED:
                                    // DANGER WILL ROBINSON!
                                    Log.e(LOG_TAG, "In LVL checking loop!");
                                    mNotification
                                            .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED);
                                    throw new RuntimeException(
                                            "Error with LVL checking and database integrity");
                                case DOWNLOAD_REQUIRED:
                                    // do nothing. the download will notify the
                                    // application
                                    // when things are done
                                    break;
                            }
                        } catch (NameNotFoundException e1) {
                            e1.printStackTrace();
                            throw new RuntimeException(
                                    "Error with getting information from package name");
                        }
                    } finally {
                        setServiceRunning(false);
                    }
                }

                @Override
                public void dontAllow(int reason) {
                    try
                    {
                        switch (reason) {
                            case Policy.NOT_LICENSED:
                                mNotification
                                        .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_UNLICENSED);
                                break;
                            case Policy.RETRY:
                                mNotification
                                        .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL);
                                break;
                        }
                    } finally {
                        setServiceRunning(false);
                    }

                }

                @Override
                public void applicationError(int errorCode) {
                    try {
                        mNotification
                                .onDownloadStateChanged(IDownloaderClient.STATE_FAILED_FETCHING_URL);
                    } finally {
                        setServiceRunning(false);
                    }
                }

            });

        }

    };

    /**
     * Updates the LVL information from the server.
     *
     * @param context
     */
    public void updateLVL(final Context context) {
        Context c = context.getApplicationContext();
        Handler h = new Handler(c.getMainLooper());
        h.post(new LVLRunnable(c, mPendingIntent));
    }

    /**
     * The APK has been updated and a filename has been sent down from the
     * Market call. If the file has the same name as the previous file, we do
     * nothing as the file is guaranteed to be the same. If the file does not
     * have the same name, we download it if it hasn't already been delivered by
     * Market.
     *
     * @param index the index of the file from market (0 = main, 1 = patch)
     * @param filename the name of the new file
     * @param fileSize the size of the new file
     * @return
     */
    public boolean handleFileUpdated(DownloadsDB db, int index,
            String filename, long fileSize) {
        DownloadInfo di = db.getDownloadInfoByFileName(filename);
        if (null != di) {
            String oldFile = di.mFileName;
            // cleanup
            if (null != oldFile) {
                if (filename.equals(oldFile)) {
                    return false;
                }

                // remove partially downloaded file if it is there
                String deleteFile = Helpers.generateSaveFileName(this, oldFile);
                File f = new File(deleteFile);
                if (f.exists())
                    f.delete();
            }
        }
        return !Helpers.doesFileExist(this, filename, fileSize, true);
    }

    private void scheduleAlarm(long wakeUp) {
        AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        if (alarms == null) {
            Log.e(Constants.TAG, "couldn't get alarm manager");
            return;
        }

        if (Constants.LOGV) {
            Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
        }

        String className = getAlarmReceiverClassName();
        Intent intent = new Intent(Constants.ACTION_RETRY);
        intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
        intent.setClassName(this.getPackageName(),
                className);
        mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent,
                PendingIntent.FLAG_ONE_SHOT);
        alarms.set(
                AlarmManager.RTC_WAKEUP,
                System.currentTimeMillis() + wakeUp, mAlarmIntent
                );
    }

    private void cancelAlarms() {
        if (null != mAlarmIntent) {
            AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
            if (alarms == null) {
                Log.e(Constants.TAG, "couldn't get alarm manager");
                return;
            }
            alarms.cancel(mAlarmIntent);
            mAlarmIntent = null;
        }
    }

    /**
     * We use this to track network state, such as when WiFi, Cellular, etc. is
     * enabled when downloads are paused or in progress.
     */
    private class InnerBroadcastReceiver extends BroadcastReceiver {
        final Service mService;

        InnerBroadcastReceiver(Service service) {
            mService = service;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            pollNetworkState();
            if (mStateChanged
                    && !isServiceRunning()) {
                Log.d(Constants.TAG, "InnerBroadcastReceiver Called");
                Intent fileIntent = new Intent(context, mService.getClass());
                fileIntent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
                // send a new intent to the service
                context.startService(fileIntent);
            }
        }
    };

    /**
     * This is the main thread for the Downloader. This thread is responsible
     * for queuing up downloads and other goodness.
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        setServiceRunning(true);
        try {
            // the database automatically reads the metadata for version code
            // and download status when the instance is created
            DownloadsDB db = DownloadsDB.getDB(this);
            final PendingIntent pendingIntent = (PendingIntent) intent
                    .getParcelableExtra(EXTRA_PENDING_INTENT);

            if (null != pendingIntent)
            {
                mNotification.setClientIntent(pendingIntent);
                mPendingIntent = pendingIntent;
            } else if (null != mPendingIntent) {
                mNotification.setClientIntent(mPendingIntent);
            } else {
                Log.e(LOG_TAG, "Downloader started in bad state without notification intent.");
                return;
            }

            // when the LVL check completes, a successful response will update
            // the service
            if (isLVLCheckRequired(db, mPackageInfo)) {
                updateLVL(this);
                return;
            }

            // get each download
            DownloadInfo[] infos = db.getDownloads();
            mBytesSoFar = 0;
            mTotalLength = 0;
            mFileCount = infos.length;
            for (DownloadInfo info : infos) {
                // We do an (simple) integrity check on each file, just to make
                // sure
                if (info.mStatus == STATUS_SUCCESS) {
                    // verify that the file matches the state
                    if (!Helpers.doesFileExist(this, info.mFileName, info.mTotalBytes, true)) {
                        info.mStatus = 0;
                        info.mCurrentBytes = 0;
                    }
                }
                // get aggregate data
                mTotalLength += info.mTotalBytes;
                mBytesSoFar += info.mCurrentBytes;
            }

            // loop through all downloads and fetch them
            pollNetworkState();
            if (null == mConnReceiver) {

                /**
                 * We use this to track network state, such as when WiFi,
                 * Cellular, etc. is enabled when downloads are paused or in
                 * progress.
                 */
                mConnReceiver = new InnerBroadcastReceiver(this);
                IntentFilter intentFilter = new IntentFilter(
                        ConnectivityManager.CONNECTIVITY_ACTION);
                intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
                registerReceiver(mConnReceiver, intentFilter);
            }

            for (DownloadInfo info : infos) {
                long startingCount = info.mCurrentBytes;

                if (info.mStatus != STATUS_SUCCESS) {
                    DownloadThread dt = new DownloadThread(info, this, mNotification);
                    cancelAlarms();
                    scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG);
                    dt.run();
                    cancelAlarms();
                }
                db.updateFromDb(info);
                boolean setWakeWatchdog = false;
                int notifyStatus;
                switch (info.mStatus) {
                    case STATUS_FORBIDDEN:
                        // the URL is out of date
                        updateLVL(this);
                        return;
                    case STATUS_SUCCESS:
                        mBytesSoFar += info.mCurrentBytes - startingCount;
                        db.updateMetadata(mPackageInfo.versionCode, 0);
                        continue;
                    case STATUS_FILE_DELIVERED_INCORRECTLY:
                        // we may be on a network that is returning us a web
                        // page on redirect
                        notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE;
                        info.mCurrentBytes = 0;
                        db.updateDownload(info);
                        setWakeWatchdog = true;
                        break;
                    case STATUS_PAUSED_BY_APP:
                        notifyStatus = IDownloaderClient.STATE_PAUSED_BY_REQUEST;
                        break;
                    case STATUS_WAITING_FOR_NETWORK:
                    case STATUS_WAITING_TO_RETRY:
                        notifyStatus = IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE;
                        setWakeWatchdog = true;
                        break;
                    case STATUS_QUEUED_FOR_WIFI_OR_CELLULAR_PERMISSION:
                    case STATUS_QUEUED_FOR_WIFI:
                        // look for more detail here
                        if (null != mWifiManager) {
                            if (!mWifiManager.isWifiEnabled()) {
                                notifyStatus = IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION;
                                setWakeWatchdog = true;
                                break;
                            }
                        }
                        notifyStatus = IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION;
                        setWakeWatchdog = true;
                        break;
                    case STATUS_CANCELED:
                        notifyStatus = IDownloaderClient.STATE_FAILED_CANCELED;
                        setWakeWatchdog = true;
                        break;

                    case STATUS_INSUFFICIENT_SPACE_ERROR:
                        notifyStatus = IDownloaderClient.STATE_FAILED_SDCARD_FULL;
                        setWakeWatchdog = true;
                        break;

                    case STATUS_DEVICE_NOT_FOUND_ERROR:
                        notifyStatus = IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE;
                        setWakeWatchdog = true;
                        break;

                    default:
                        notifyStatus = IDownloaderClient.STATE_FAILED;
                        break;
                }
                if (setWakeWatchdog) {
                    scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER);
                } else {
                    cancelAlarms();
                }
                // failure or pause state
                mNotification.onDownloadStateChanged(notifyStatus);
                return;
            }

            // all downloads complete
            mNotification.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED);
        } finally {
            setServiceRunning(false);
        }
    }

    @Override
    public void onDestroy() {
        if (null != mConnReceiver) {
            unregisterReceiver(mConnReceiver);
            mConnReceiver = null;
        }
        mServiceStub.disconnect(this);
        super.onDestroy();
    }

    public int getNetworkAvailabilityState(DownloadsDB db) {
        if (mIsConnected) {
            if (!mIsCellularConnection)
                return NETWORK_OK;
            int flags = db.mFlags;
            if (mIsRoaming)
                return NETWORK_CANNOT_USE_ROAMING;
            if (0 != (flags & FLAGS_DOWNLOAD_OVER_CELLULAR)) {
                return NETWORK_OK;
            } else {
                return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR;
            }
        }
        return NETWORK_NO_CONNECTION;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        try {
            mPackageInfo = getPackageManager().getPackageInfo(
                    getPackageName(), 0);
            ApplicationInfo ai = getApplicationInfo();
            CharSequence applicationLabel = getPackageManager().getApplicationLabel(ai);
            mNotification = new DownloadNotification(this, applicationLabel);

        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * Exception thrown from methods called by generateSaveFile() for any fatal
     * error.
     */
    public static class GenerateSaveFileError extends Exception {
        private static final long serialVersionUID = 3465966015408936540L;
        int mStatus;
        String mMessage;

        public GenerateSaveFileError(int status, String message) {
            mStatus = status;
            mMessage = message;
        }
    }

    /**
     * Returns the filename (where the file should be saved) from info about a
     * download
     */
    public String generateTempSaveFileName(String fileName) {
        String path = Helpers.getSaveFilePath(this)
                + File.separator + fileName + TEMP_EXT;
        return path;
    }

    /**
     * Creates a filename (where the file should be saved) from info about a
     * download.
     */
    public String generateSaveFile(String filename, long filesize)
            throws GenerateSaveFileError {
        String path = generateTempSaveFileName(filename);
        File expPath = new File(path);
        if (!Helpers.isExternalMediaMounted()) {
            Log.d(Constants.TAG, "External media not mounted: " + path);
            throw new GenerateSaveFileError(STATUS_DEVICE_NOT_FOUND_ERROR,
                    "external media is not yet mounted");

        }
        if (expPath.exists()) {
            Log.d(Constants.TAG, "File already exists: " + path);
            throw new GenerateSaveFileError(STATUS_FILE_ALREADY_EXISTS_ERROR,
                    "requested destination file already exists");
        }
        if (Helpers.getAvailableBytes(Helpers.getFilesystemRoot(path)) < filesize) {
            throw new GenerateSaveFileError(STATUS_INSUFFICIENT_SPACE_ERROR,
                    "insufficient space on external storage");
        }
        return path;
    }

    /**
     * @return a non-localized string appropriate for logging corresponding to
     *         one of the NETWORK_* constants.
     */
    public String getLogMessageForNetworkError(int networkError) {
        switch (networkError) {
            case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE:
                return "download size exceeds recommended limit for mobile network";

            case NETWORK_UNUSABLE_DUE_TO_SIZE:
                return "download size exceeds limit for mobile network";

            case NETWORK_NO_CONNECTION:
                return "no network connection available";

            case NETWORK_CANNOT_USE_ROAMING:
                return "download cannot use the current network connection because it is roaming";

            case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR:
                return "download was requested to not use the current network type";

            default:
                return "unknown error with network connectivity";
        }
    }

    public int getControl() {
        return mControl;
    }

    public int getStatus() {
        return mStatus;
    }

    /**
     * Calculating a moving average for the speed so we don't get jumpy
     * calculations for time etc.
     */
    static private final float SMOOTHING_FACTOR = 0.005f;

    public void notifyUpdateBytes(long totalBytesSoFar) {
        long timeRemaining;
        long currentTime = SystemClock.uptimeMillis();
        if (0 != mMillisecondsAtSample) {
            // we have a sample.
            long timePassed = currentTime - mMillisecondsAtSample;
            long bytesInSample = totalBytesSoFar - mBytesAtSample;
            float currentSpeedSample = (float) bytesInSample / (float) timePassed;
            if (0 != mAverageDownloadSpeed) {
                mAverageDownloadSpeed = SMOOTHING_FACTOR * currentSpeedSample
                        + (1 - SMOOTHING_FACTOR) * mAverageDownloadSpeed;
            } else {
                mAverageDownloadSpeed = currentSpeedSample;
            }
            timeRemaining = (long) ((mTotalLength - totalBytesSoFar) / mAverageDownloadSpeed);
        } else {
            timeRemaining = -1;
        }
        mMillisecondsAtSample = currentTime;
        mBytesAtSample = totalBytesSoFar;
        mNotification.onDownloadProgress(
                new DownloadProgressInfo(mTotalLength,
                        totalBytesSoFar,
                        timeRemaining,
                        mAverageDownloadSpeed)
                );

    }

    @Override
    protected boolean shouldStop() {
        // the database automatically reads the metadata for version code
        // and download status when the instance is created
        DownloadsDB db = DownloadsDB.getDB(this);
        if (db.mStatus == 0) {
            return true;
        }
        return false;
    }

    @Override
    public void requestDownloadStatus() {
        mNotification.resendState();
    }

    @Override
    public void onClientUpdated(Messenger clientMessenger) {
        this.mClientMessenger = clientMessenger;
        mNotification.setMessenger(mClientMessenger);
    }

}


================================================
FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java
================================================
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.vending.expansion.downloader.impl;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.provider.BaseColumns;
import android.util.Log;

public class DownloadsDB {
    private static final String DATABASE_NAME = "DownloadsDB";
    private static final int DATABASE_VERSION = 7;
    public static final String LOG_TAG = DownloadsDB.class.getName();
    final SQLiteOpenHelper mHelper;
    SQLiteStatement mGetDownloadByIndex;
    SQLiteStatement mUpdateCurrentBytes;
    private static DownloadsDB mDownloadsDB;
    long mMetadataRowID = -1;
    int mVersionCode = -1;
    int mStatus = -1;
    int mFlags;

    static public synchronized DownloadsDB getDB(Context paramContext) {
        if (null == mDownloadsDB) {
            return new DownloadsDB(paramContext);
        }
        return mDownloadsDB;
    }

    private SQLiteStatement getDownloadByIndexStatement() {
        if (null == mGetDownloadByIndex) {
            mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement(
                    "SELECT " + BaseColumns._ID + " FROM "
                            + DownloadColumns.TABLE_NAME + " WHERE "
                            + DownloadColumns.INDEX + " = ?");
        }
        return mGetDownloadByIndex;
    }

    private SQLiteStatement getUpdateCurrentBytesStatement() {
        if (null == mUpdateCurrentBytes) {
            mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement(
                    "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES
                            + " = ?" +
                            " WHERE " + DownloadColumns.INDEX + " = ?");
        }
        return mUpdateCurrentBytes;
    }

    private DownloadsDB(Context paramContext) {
        this.mHelper = new DownloadsContentDBHelper(paramContext);
        final SQLiteDatabase sqldb = mHelper.getReadableDatabase();
        // Query for the version code, the row ID of the metadata (for future
        // updating) the status and the flags
        Cursor cur = sqldb.rawQuery("SELECT " +
                MetadataColumns.APKVERSION + "," +
                BaseColumns._ID + "," +
                MetadataColumns.DOWNLOAD_STATUS + "," +
                MetadataColumns.FLAGS +
                " FROM "
                + MetadataColumns.TABLE_NAME + " LIMIT 1", null);
        if (null != cur && cur.moveToFirst()) {
            mVersionCode = cur.getInt(0);
            mMetadataRowID = cur.getLong(1);
            mStatus = cur.getInt(2);
            mFlags = cur.getInt(3);
            cur.clos
Download .txt
gitextract_2arvo09o/

├── .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/
                    ├── layout/
                    │   ├── main.xml
                    │   └── status_bar_ongoing_event_progress_bar.xml
                    └── values/
                        ├── main-strings.xml
                        ├── strings.xml
                        └── styles.xml
Download .txt
SYMBOL INDEX (401 symbols across 39 files)

FILE: Assets/Scripts/DownloadObbExample.cs
  class DownloadObbExample (line 4) | public class DownloadObbExample : MonoBehaviour
    method Start (line 7) | void Start()
    method OnGUI (line 13) | void OnGUI()

FILE: Assets/Scripts/GooglePlayDownloader.cs
  type IGooglePlayObbDownloader (line 4) | public interface IGooglePlayObbDownloader
    method GetExpansionFilePath (line 8) | string GetExpansionFilePath();
    method GetMainOBBPath (line 9) | string GetMainOBBPath();
    method GetPatchOBBPath (line 10) | string GetPatchOBBPath();
    method FetchOBB (line 11) | void FetchOBB();
  class GooglePlayObbDownloadManager (line 14) | public class GooglePlayObbDownloadManager
    method GetGooglePlayObbDownloader (line 19) | public static IGooglePlayObbDownloader GetGooglePlayObbDownloader()
    method IsDownloaderAvailable (line 31) | public static bool IsDownloaderAvailable()

FILE: Assets/Scripts/GooglePlayDownloaderImpl.cs
  class GooglePlayObbDownloader (line 5) | internal class GooglePlayObbDownloader : IGooglePlayObbDownloader
    method ApplyPublicKey (line 12) | private void ApplyPublicKey()
    method FetchOBB (line 26) | public void FetchOBB()
    method GetExpansionFilePath (line 52) | public string GetExpansionFilePath()
    method GetMainOBBPath (line 72) | public string GetMainOBBPath()
    method GetPatchOBBPath (line 77) | public string GetPatchOBBPath()
    method GetOBBPackagePath (line 82) | private static string GetOBBPackagePath(string expansionFilePath, stri...
    method PopulateOBBProperties (line 119) | private static void PopulateOBBProperties()

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Constants.java
  class Constants (line 26) | public class Constants {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java
  class DownloadProgressInfo (line 33) | public class DownloadProgressInfo implements Parcelable {
    method describeContents (line 39) | @Override
    method writeToParcel (line 44) | @Override
    method DownloadProgressInfo (line 52) | public DownloadProgressInfo(Parcel p) {
    method DownloadProgressInfo (line 59) | public DownloadProgressInfo(long overallTotal, long overallProgress,
    method createFromParcel (line 69) | @Override
    method newArray (line 74) | @Override

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java
  class DownloaderClientMarshaller (line 60) | public class DownloaderClientMarshaller {
    class Proxy (line 73) | private static class Proxy implements IDownloaderClient {
      method onDownloadStateChanged (line 76) | @Override
      method onDownloadProgress (line 83) | @Override
      method send (line 90) | private void send(int method, Bundle params) {
      method Proxy (line 100) | public Proxy(Messenger msg) {
      method onServiceConnected (line 104) | @Override
    class Stub (line 112) | private static class Stub implements IStub {
      method handleMessage (line 122) | @Override
      method Stub (line 145) | public Stub(IDownloaderClient itf, Class<?> downloaderService) {
      method onServiceConnected (line 154) | public void onServiceConnected(ComponentName className, IBinder serv...
      method onServiceDisconnected (line 165) | public void onServiceDisconnected(ComponentName className) {
      method connect (line 172) | @Override
      method disconnect (line 187) | @Override
      method getMessenger (line 196) | @Override
    method CreateProxy (line 208) | public static IDownloaderClient CreateProxy(Messenger msg) {
    method CreateStub (line 224) | public static IStub CreateStub(IDownloaderClient itf, Class<?> downloa...
    method startDownloadServiceIfRequired (line 251) | public static int startDownloadServiceIfRequired(Context context, Pend...
    method startDownloadServiceIfRequired (line 270) | public static int startDownloadServiceIfRequired(Context context, Inte...

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java
  class DownloaderServiceMarshaller (line 39) | public class DownloaderServiceMarshaller {
    class Proxy (line 57) | private static class Proxy implements IDownloaderService {
      method send (line 60) | private void send(int method, Bundle params) {
      method Proxy (line 70) | public Proxy(Messenger msg) {
      method requestAbortDownload (line 74) | @Override
      method requestPauseDownload (line 79) | @Override
      method setDownloadFlags (line 84) | @Override
      method requestContinueDownload (line 91) | @Override
      method requestDownloadStatus (line 96) | @Override
      method onClientUpdated (line 101) | @Override
    class Stub (line 109) | private static class Stub implements IStub {
      method handleMessage (line 112) | @Override
      method Stub (line 138) | public Stub(IDownloaderService itf) {
      method getMessenger (line 142) | @Override
      method connect (line 147) | @Override
      method disconnect (line 152) | @Override
    method CreateProxy (line 164) | public static IDownloaderService CreateProxy(Messenger msg) {
    method CreateStub (line 177) | public static IStub CreateStub(IDownloaderService itf) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Helpers.java
  class Helpers (line 41) | public class Helpers {
    method Helpers (line 49) | private Helpers() {
    method parseContentDisposition (line 58) | static String parseContentDisposition(String contentDisposition) {
    method getFilesystemRoot (line 74) | public static File getFilesystemRoot(String path) {
    method isExternalMediaMounted (line 87) | public static boolean isExternalMediaMounted() {
    method getAvailableBytes (line 102) | public static long getAvailableBytes(File root) {
    method isFilenameValid (line 113) | public static boolean isFilenameValid(String filename) {
    method deleteFile (line 123) | static void deleteFile(String path) {
    method getDownloadProgressString (line 142) | static public String getDownloadProgressString(long overallProgress, l...
    method getDownloadProgressStringNotification (line 164) | static public String getDownloadProgressStringNotification(long overal...
    method getDownloadProgressPercent (line 176) | public static String getDownloadProgressPercent(long overallProgress, ...
    method getSpeedString (line 186) | public static String getSpeedString(float bytesPerMillisecond) {
    method getTimeRemaining (line 190) | public static String getTimeRemaining(long durationInMilliseconds) {
    method getExpansionAPKFileName (line 208) | public static String getExpansionAPKFileName(Context c, boolean mainFi...
    method generateSaveFileName (line 215) | static public String generateSaveFileName(Context c, String fileName) {
    method getSaveFilePath (line 221) | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    method doesFileExist (line 244) | static public boolean doesFileExist(Context c, String fileName, long f...
    method getFileStatus (line 273) | static public int getFileStatus(Context c, String fileName) {
    method canWriteOBBFile (line 297) | static public boolean canWriteOBBFile(Context c) {
    method getDownloaderStringResourceIDFromState (line 317) | static public int getDownloaderStringResourceIDFromState(Context ctx, ...
    method getStringResource (line 360) | static public int getStringResource(Context ctx, String name) {
    method getLayoutResource (line 363) | static public int getLayoutResource(Context ctx, String name) {
    method getIdResource (line 366) | static public int getIdResource(Context ctx, String name) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderClient.java
  type IDownloaderClient (line 25) | public interface IDownloaderClient {
    method onServiceConnected (line 93) | void onServiceConnected(Messenger m);
    method onDownloadStateChanged (line 115) | void onDownloadStateChanged(int newState);
    method onDownloadProgress (line 125) | void onDownloadProgress(DownloadProgressInfo progress);

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderService.java
  type IDownloaderService (line 33) | public interface IDownloaderService {
    method requestAbortDownload (line 45) | void requestAbortDownload();
    method requestPauseDownload (line 52) | void requestPauseDownload();
    method requestContinueDownload (line 59) | void requestContinueDownload();
    method setDownloadFlags (line 67) | void setDownloadFlags(int flags);
    method requestDownloadStatus (line 72) | void requestDownloadStatus();
    method onClientUpdated (line 82) | void onClientUpdated(Messenger clientMessenger);

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IStub.java
  type IStub (line 35) | public interface IStub {
    method getMessenger (line 36) | Messenger getMessenger();
    method connect (line 38) | void connect(Context c);
    method disconnect (line 40) | void disconnect(Context c);

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/SystemFacade.java
  class SystemFacade (line 32) | class SystemFacade {
    method SystemFacade (line 36) | public SystemFacade(Context context) {
    method currentTimeMillis (line 42) | public long currentTimeMillis() {
    method getActiveNetworkType (line 46) | public Integer getActiveNetworkType() {
    method isNetworkRoaming (line 64) | public boolean isNetworkRoaming() {
    method getMaxBytesOverMobile (line 87) | public Long getMaxBytesOverMobile() {
    method getRecommendedMaxBytesOverMobile (line 91) | public Long getRecommendedMaxBytesOverMobile() {
    method sendBroadcast (line 95) | public void sendBroadcast(Intent intent) {
    method userOwnsPackage (line 99) | public boolean userOwnsPackage(int uid, String packageName) throws Nam...
    method postNotification (line 103) | public void postNotification(long id, Notification notification) {
    method cancelNotification (line 112) | public void cancelNotification(long id) {
    method cancelAllNotifications (line 116) | public void cancelAllNotifications() {
    method startThread (line 120) | public void startThread(Thread thread) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java
  class CustomIntentService (line 34) | public abstract class CustomIntentService extends Service {
    method CustomIntentService (line 42) | public CustomIntentService(String paramString) {
    method onBind (line 46) | @Override
    method onCreate (line 51) | @Override
    method onDestroy (line 61) | @Override
    method onHandleIntent (line 71) | protected abstract void onHandleIntent(Intent paramIntent);
    method shouldStop (line 73) | protected abstract boolean shouldStop();
    method onStart (line 75) | @Override
    method onStartCommand (line 86) | @Override
    method setIntentRedelivery (line 92) | public void setIntentRedelivery(boolean enabled) {
    class ServiceHandler (line 96) | private final class ServiceHandler extends Handler {
      method ServiceHandler (line 97) | public ServiceHandler(Looper looper) {
      method handleMessage (line 101) | @Override

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java
  class DownloadInfo (line 27) | public class DownloadInfo {
    method DownloadInfo (line 45) | public DownloadInfo(int index, String fileName, String pkg) {
    method resetDownload (line 51) | public void resetDownload() {
    method restartTime (line 65) | public long restartTime(long now) {
    method logVerboseInfo (line 77) | public void logVerboseInfo() {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java
  class DownloadNotification (line 42) | public class DownloadNotification implements IDownloaderClient {
    method setClientIntent (line 61) | public void setClientIntent(PendingIntent clientIntent) {
    method resendState (line 67) | public void resendState() {
    method onDownloadStateChanged (line 73) | @Override
    method onDownloadProgress (line 154) | @Override
    method setMessenger (line 194) | public void setMessenger(Messenger msg) {
    method DownloadNotification (line 210) | DownloadNotification(Context ctx, CharSequence applicationLabel) {
    method onServiceConnected (line 232) | @Override

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadThread.java
  class DownloadThread (line 41) | public class DownloadThread {
    method DownloadThread (line 50) | public DownloadThread(DownloadInfo info, DownloaderService service,
    method userAgent (line 66) | private String userAgent() {
    class State (line 73) | private static class State {
      method State (line 83) | public State(DownloadInfo info, DownloaderService service) {
    class InnerState (line 93) | private static class InnerState {
    class StopRequest (line 112) | private class StopRequest extends Throwable {
      method StopRequest (line 117) | public StopRequest(int finalStatus, String message) {
      method StopRequest (line 122) | public StopRequest(int finalStatus, String message, Throwable throwa...
    class RetryDownload (line 132) | private class RetryDownload extends Throwable {
    method run (line 140) | public void run() {
    method executeDownload (line 213) | private void executeDownload(State state, HttpURLConnection request)
    method checkConnectivity (line 244) | private void checkConnectivity(State state) throws StopRequest {
    method transferData (line 270) | private void transferData(State state, InnerState innerState, byte[] d...
    method finalizeDestinationFile (line 293) | private void finalizeDestinationFile(State state) throws StopRequest {
    method cleanupDestination (line 316) | private void cleanupDestination(State state, int finalStatus) {
    method syncDestination (line 327) | private void syncDestination(State state) {
    method closeDestination (line 356) | private void closeDestination(State state) {
    method checkPausedOrCanceled (line 375) | private void checkPausedOrCanceled(State state) throws StopRequest {
    method reportProgress (line 389) | private void reportProgress(State state, InnerState innerState) {
    method writeDataToDestination (line 421) | private void writeDataToDestination(State state, byte[] data, int byte...
    method handleEndOfStream (line 454) | private void handleEndOfStream(State state, InnerState innerState) thr...
    method cannotResume (line 475) | private boolean cannotResume(InnerState innerState) {
    method readFromResponse (line 487) | private int readFromResponse(State state, InnerState innerState, byte[...
    method openResponseEntity (line 512) | private InputStream openResponseEntity(State state, HttpURLConnection ...
    method logNetworkState (line 523) | private void logNetworkState() {
    method processResponseHeaders (line 536) | private void processResponseHeaders(State state, InnerState innerState...
    method updateDatabaseFromHeaders (line 577) | private void updateDatabaseFromHeaders(State state, InnerState innerSt...
    method readResponseHeaders (line 585) | private void readResponseHeaders(State state, InnerState innerState, H...
    method handleExceptionalStatus (line 658) | private void handleExceptionalStatus(State state, InnerState innerStat...
    method handleOtherStatus (line 676) | private void handleOtherStatus(State state, InnerState innerState, int...
    method addRequestHeaders (line 694) | private void addRequestHeaders(InnerState innerState, HttpURLConnectio...
    method handleServiceUnavailable (line 707) | private void handleServiceUnavailable(State state, HttpURLConnection c...
    method sendRequest (line 741) | private int sendRequest(State state, HttpURLConnection request)
    method getFinalStatusForHttpError (line 755) | private int getFinalStatusForHttpError(State state) {
    method setupDestinationFile (line 771) | private void setupDestinationFile(State state, InnerState innerState)
    method notifyDownloadCompleted (line 821) | private void notifyDownloadCompleted(
    method updateDownloadDatabase (line 831) | private void updateDownloadDatabase(

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloaderService.java
  class DownloaderService (line 62) | public abstract class DownloaderService extends CustomIntentService impl...
    method DownloaderService (line 64) | public DownloaderService() {
    method isStatusInformational (line 150) | public static boolean isStatusInformational(int status) {
    method isStatusSuccess (line 157) | public static boolean isStatusSuccess(int status) {
    method isStatusError (line 164) | public static boolean isStatusError(int status) {
    method isStatusClientError (line 171) | public static boolean isStatusClientError(int status) {
    method isStatusServerError (line 178) | public static boolean isStatusServerError(int status) {
    method isStatusCompleted (line 186) | public static boolean isStatusCompleted(int status) {
    method onBind (line 393) | @Override
    method isWiFi (line 416) | public boolean isWiFi() {
    method updateNetworkType (line 463) | private void updateNetworkType(int type, int subType) {
    method updateNetworkState (line 514) | private void updateNetworkState(NetworkInfo info) {
    method pollNetworkState (line 570) | void pollNetworkState() {
    method isLVLCheckRequired (line 602) | private static boolean isLVLCheckRequired(DownloadsDB db, PackageInfo ...
    method isServiceRunning (line 616) | private static synchronized boolean isServiceRunning() {
    method setServiceRunning (line 620) | private static synchronized void setServiceRunning(boolean isRunning) {
    method startDownloadServiceIfRequired (line 624) | public static int startDownloadServiceIfRequired(Context context,
    method startDownloadServiceIfRequired (line 632) | public static int startDownloadServiceIfRequired(Context context,
    method startDownloadServiceIfRequired (line 662) | public static int startDownloadServiceIfRequired(Context context,
    method requestAbortDownload (line 708) | @Override
    method requestPauseDownload (line 714) | @Override
    method setDownloadFlags (line 720) | @Override
    method requestContinueDownload (line 725) | @Override
    method getPublicKey (line 735) | public abstract String getPublicKey();
    method getSALT (line 737) | public abstract byte[] getSALT();
    method getAlarmReceiverClassName (line 739) | public abstract String getAlarmReceiverClassName();
    class LVLRunnable (line 741) | private class LVLRunnable implements Runnable {
      method LVLRunnable (line 742) | LVLRunnable(Context context, PendingIntent intent) {
      method run (line 749) | @Override
    method updateLVL (line 903) | public void updateLVL(final Context context) {
    method handleFileUpdated (line 921) | public boolean handleFileUpdated(DownloadsDB db, int index,
    method scheduleAlarm (line 942) | private void scheduleAlarm(long wakeUp) {
    method cancelAlarms (line 966) | private void cancelAlarms() {
    class InnerBroadcastReceiver (line 982) | private class InnerBroadcastReceiver extends BroadcastReceiver {
      method InnerBroadcastReceiver (line 985) | InnerBroadcastReceiver(Service service) {
      method onReceive (line 989) | @Override
    method onHandleIntent (line 1007) | @Override
    method onDestroy (line 1158) | @Override
    method getNetworkAvailabilityState (line 1168) | public int getNetworkAvailabilityState(DownloadsDB db) {
    method onCreate (line 1184) | @Override
    class GenerateSaveFileError (line 1203) | public static class GenerateSaveFileError extends Exception {
      method GenerateSaveFileError (line 1208) | public GenerateSaveFileError(int status, String message) {
    method generateTempSaveFileName (line 1218) | public String generateTempSaveFileName(String fileName) {
    method generateSaveFile (line 1228) | public String generateSaveFile(String filename, long filesize)
    method getLogMessageForNetworkError (line 1254) | public String getLogMessageForNetworkError(int networkError) {
    method getControl (line 1276) | public int getControl() {
    method getStatus (line 1280) | public int getStatus() {
    method notifyUpdateBytes (line 1290) | public void notifyUpdateBytes(long totalBytesSoFar) {
    method shouldStop (line 1319) | @Override
    method requestDownloadStatus (line 1330) | @Override
    method onClientUpdated (line 1335) | @Override

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java
  class DownloadsDB (line 29) | public class DownloadsDB {
    method getDB (line 42) | static public synchronized DownloadsDB getDB(Context paramContext) {
    method getDownloadByIndexStatement (line 49) | private SQLiteStatement getDownloadByIndexStatement() {
    method getUpdateCurrentBytesStatement (line 59) | private SQLiteStatement getUpdateCurrentBytesStatement() {
    method DownloadsDB (line 69) | private DownloadsDB(Context paramContext) {
    method getDownloadInfoByFileName (line 91) | protected DownloadInfo getDownloadInfoByFileName(String fileName) {
    method getIDForDownloadInfo (line 110) | public long getIDForDownloadInfo(final DownloadInfo di) {
    method getIDByIndex (line 114) | public long getIDByIndex(int index) {
    method updateDownloadCurrentBytes (line 125) | public void updateDownloadCurrentBytes(final DownloadInfo di) {
    method close (line 133) | public void close() {
    class DownloadsContentDBHelper (line 137) | protected static class DownloadsContentDBHelper extends SQLiteOpenHelp...
      method DownloadsContentDBHelper (line 138) | DownloadsContentDBHelper(Context paramContext) {
      method createTableQueryFromArray (line 142) | private String createTableQueryFromArray(String paramString,
      method dropTables (line 181) | private void dropTables(SQLiteDatabase paramSQLiteDatabase) {
      method onCreate (line 196) | public void onCreate(SQLiteDatabase paramSQLiteDatabase) {
      method onUpgrade (line 210) | public void onUpgrade(SQLiteDatabase paramSQLiteDatabase,
    class MetadataColumns (line 220) | public static class MetadataColumns implements BaseColumns {
    class DownloadColumns (line 242) | public static class DownloadColumns implements BaseColumns {
    method updateDownload (line 326) | public boolean updateDownload(DownloadInfo di) {
    method updateDownload (line 343) | public boolean updateDownload(DownloadInfo di, ContentValues cv) {
    method getLastCheckedVersionCode (line 362) | public int getLastCheckedVersionCode() {
    method isDownloadRequired (line 366) | public boolean isDownloadRequired() {
    method getFlags (line 382) | public int getFlags() {
    method updateFlags (line 386) | public boolean updateFlags(int flags) {
    method updateStatus (line 401) | public boolean updateStatus(int status) {
    method updateMetadata (line 416) | public boolean updateMetadata(ContentValues cv) {
    method updateMetadata (line 432) | public boolean updateMetadata(int apkVersion, int downloadStatus) {
    method updateFromDb (line 445) | public boolean updateFromDb(DownloadInfo di) {
    method setDownloadInfoFromCursor (line 466) | public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) {
    method getDownloadInfoFromCursor (line 479) | public DownloadInfo getDownloadInfoFromCursor(Cursor cur) {
    method getDownloads (line 487) | public DownloadInfo[] getDownloads() {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java
  class HttpDateTime (line 28) | public final class HttpDateTime {
    class TimeOfDay (line 56) | private static class TimeOfDay {
      method TimeOfDay (line 57) | TimeOfDay(int h, int m, int s) {
    method parse (line 68) | public static long parse(String timeString)
    method getDate (line 107) | private static int getDate(String dateString) {
    method getMonth (line 122) | private static int getMonth(String monthString) {
    method getYear (line 156) | private static int getYear(String yearString) {
    method getTime (line 181) | private static TimeOfDay getTime(String timeString) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/AESObfuscator.java
  class AESObfuscator (line 38) | public class AESObfuscator implements Obfuscator {
    method AESObfuscator (line 55) | public AESObfuscator(byte[] salt, String applicationId, String deviceI...
    method obfuscate (line 72) | public String obfuscate(String original, String key) {
    method unobfuscate (line 86) | public String unobfuscate(String obfuscated, String key) throws Valida...

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/APKExpansionPolicy.java
  class APKExpansionPolicy (line 47) | public class APKExpansionPolicy implements Policy {
    method APKExpansionPolicy (line 86) | public APKExpansionPolicy(Context context, Obfuscator obfuscator) {
    method resetPolicy (line 103) | public void resetPolicy() {
    method processServerResponse (line 127) | public void processServerResponse(int response,
    method setLastResponse (line 179) | private void setLastResponse(int l) {
    method setRetryCount (line 191) | private void setRetryCount(long c) {
    method getRetryCount (line 196) | public long getRetryCount() {
    method setValidityTimestamp (line 207) | private void setValidityTimestamp(String validityTimestamp) {
    method getValidityTimestamp (line 222) | public long getValidityTimestamp() {
    method setRetryUntil (line 233) | private void setRetryUntil(String retryUntil) {
    method getRetryUntil (line 248) | public long getRetryUntil() {
    method setMaxRetries (line 259) | private void setMaxRetries(String maxRetries) {
    method getMaxRetries (line 274) | public long getMaxRetries() {
    method getExpansionURLCount (line 285) | public int getExpansionURLCount() {
    method getExpansionURL (line 297) | public String getExpansionURL(int index) {
    method setExpansionURL (line 313) | public void setExpansionURL(int index, String URL) {
    method getExpansionFileName (line 320) | public String getExpansionFileName(int index) {
    method setExpansionFileName (line 327) | public void setExpansionFileName(int index, String name) {
    method getExpansionFileSize (line 334) | public long getExpansionFileSize(int index) {
    method setExpansionFileSize (line 341) | public void setExpansionFileSize(int index, long size) {
    method allowAccess (line 356) | public boolean allowAccess() {
    method decodeExtras (line 375) | private Map<String, String> decodeExtras(String extras) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/DeviceLimiter.java
  type DeviceLimiter (line 38) | public interface DeviceLimiter {
    method isDeviceAllowed (line 46) | int isDeviceAllowed(String userId);

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseChecker.java
  class LicenseChecker (line 59) | public class LicenseChecker implements ServiceConnection {
    method LicenseChecker (line 91) | public LicenseChecker(Context context, Policy policy, String encodedPu...
    method generatePublicKey (line 108) | private static PublicKey generatePublicKey(String encodedPublicKey) {
    method checkAccess (line 138) | public synchronized void checkAccess(LicenseCheckerCallback callback) {
    method runChecks (line 198) | private void runChecks() {
    method finishCheck (line 214) | private synchronized void finishCheck(LicenseValidator validator) {
    class ResultListener (line 221) | private class ResultListener extends ILicenseResultListener.Stub {
      method ResultListener (line 225) | public ResultListener(LicenseValidator validator) {
      method verifyLicense (line 243) | public void verifyLicense(final int responseCode, final String signe...
      method startTimeout (line 288) | private void startTimeout() {
      method clearTimeout (line 293) | private void clearTimeout() {
    method onServiceConnected (line 299) | public synchronized void onServiceConnected(ComponentName name, IBinde...
    method onServiceDisconnected (line 304) | public synchronized void onServiceDisconnected(ComponentName name) {
    method handleServiceConnectionError (line 316) | private synchronized void handleServiceConnectionError(LicenseValidato...
    method cleanupService (line 327) | private void cleanupService() {
    method onDestroy (line 348) | public synchronized void onDestroy() {
    method generateNonce (line 354) | private int generateNonce() {
    method getVersionCode (line 365) | private static String getVersionCode(Context context, String packageNa...

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseCheckerCallback.java
  type LicenseCheckerCallback (line 35) | public interface LicenseCheckerCallback {
    method allow (line 43) | public void allow(int reason);
    method dontAllow (line 52) | public void dontAllow(int reason);
    method applicationError (line 66) | public void applicationError(int errorCode);

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseValidator.java
  class LicenseValidator (line 35) | class LicenseValidator {
    method LicenseValidator (line 57) | LicenseValidator(Policy policy, DeviceLimiter deviceLimiter, LicenseCh...
    method getCallback (line 67) | public LicenseCheckerCallback getCallback() {
    method getNonce (line 71) | public int getNonce() {
    method getPackageName (line 75) | public String getPackageName() {
    method verify (line 89) | public void verify(PublicKey publicKey, int responseCode, String signe...
    method handleResponse (line 211) | private void handleResponse(int response, ResponseData rawData) {
    method handleApplicationError (line 224) | private void handleApplicationError(int code) {
    method handleInvalidResponse (line 228) | private void handleInvalidResponse() {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/NullDeviceLimiter.java
  class NullDeviceLimiter (line 27) | public class NullDeviceLimiter implements DeviceLimiter {
    method isDeviceAllowed (line 29) | public int isDeviceAllowed(String userId) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/Obfuscator.java
  type Obfuscator (line 28) | public interface Obfuscator {
    method obfuscate (line 37) | String obfuscate(String original, String key);
    method unobfuscate (line 47) | String unobfuscate(String obfuscated, String key) throws ValidationExc...

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/Policy.java
  type Policy (line 23) | public interface Policy {
    method processServerResponse (line 53) | void processServerResponse(int response, ResponseData rawData);
    method allowAccess (line 58) | boolean allowAccess();

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/PreferenceObfuscator.java
  class PreferenceObfuscator (line 25) | public class PreferenceObfuscator {
    method PreferenceObfuscator (line 39) | public PreferenceObfuscator(SharedPreferences sp, Obfuscator o) {
    method putString (line 45) | public void putString(String key, String value) {
    method getString (line 53) | public String getString(String key, String defValue) {
    method commit (line 71) | public void commit() {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ResponseData.java
  class ResponseData (line 26) | public class ResponseData {
    method parse (line 44) | public static ResponseData parse(String responseData) {
    method toString (line 74) | @Override

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ServerManagedPolicy.java
  class ServerManagedPolicy (line 44) | public class ServerManagedPolicy implements Policy {
    method ServerManagedPolicy (line 72) | public ServerManagedPolicy(Context context, Obfuscator obfuscator) {
    method processServerResponse (line 100) | public void processServerResponse(int response, ResponseData rawData) {
    method setLastResponse (line 134) | private void setLastResponse(int l) {
    method setRetryCount (line 146) | private void setRetryCount(long c) {
    method getRetryCount (line 151) | public long getRetryCount() {
    method setValidityTimestamp (line 162) | private void setValidityTimestamp(String validityTimestamp) {
    method getValidityTimestamp (line 177) | public long getValidityTimestamp() {
    method setRetryUntil (line 188) | private void setRetryUntil(String retryUntil) {
    method getRetryUntil (line 203) | public long getRetryUntil() {
    method setMaxRetries (line 214) | private void setMaxRetries(String maxRetries) {
    method getMaxRetries (line 229) | public long getMaxRetries() {
    method allowAccess (line 243) | public boolean allowAccess() {
    method decodeExtras (line 260) | private Map<String, String> decodeExtras(String extras) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/StrictPolicy.java
  class StrictPolicy (line 32) | public class StrictPolicy implements Policy {
    method StrictPolicy (line 36) | public StrictPolicy() {
    method processServerResponse (line 49) | public void processServerResponse(int response, ResponseData rawData) {
    method allowAccess (line 59) | public boolean allowAccess() {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ValidationException.java
  class ValidationException (line 23) | public class ValidationException extends Exception {
    method ValidationException (line 24) | public ValidationException() {
    method ValidationException (line 28) | public ValidationException(String s) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/Base64.java
  class Base64 (line 41) | public class Base64 {
    method Base64 (line 169) | private Base64() {
    method encode3to4 (line 196) | private static byte[] encode3to4(byte[] source, int srcOffset,
    method encode (line 246) | public static String encode(byte[] source) {
    method encodeWebSafe (line 257) | public static String encodeWebSafe(byte[] source, boolean doPadding) {
    method encode (line 272) | public static String encode(byte[] source, int off, int len, byte[] al...
    method encode (line 299) | public static byte[] encode(byte[] source, int off, int len, byte[] al...
    method decode4to3 (line 375) | private static int decode4to3(byte[] source, int srcOffset,
    method decode (line 418) | public static byte[] decode(String s) throws Base64DecoderException {
    method decodeWebSafe (line 430) | public static byte[] decodeWebSafe(String s) throws Base64DecoderExcep...
    method decode (line 444) | public static byte[] decode(byte[] source) throws Base64DecoderExcepti...
    method decodeWebSafe (line 456) | public static byte[] decodeWebSafe(byte[] source)
    method decode (line 472) | public static byte[] decode(byte[] source, int off, int len)
    method decodeWebSafe (line 487) | public static byte[] decodeWebSafe(byte[] source, int off, int len)
    method decode (line 502) | public static byte[] decode(byte[] source, int off, int len, byte[] de...

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/Base64DecoderException.java
  class Base64DecoderException (line 22) | public class Base64DecoderException extends Exception {
    method Base64DecoderException (line 23) | public Base64DecoderException() {
    method Base64DecoderException (line 27) | public Base64DecoderException(String s) {

FILE: src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/URIQueryDecoder.java
  class URIQueryDecoder (line 27) | public class URIQueryDecoder {
    method DecodeQuery (line 37) | static public void DecodeQuery(URI encodedURI, Map<String, String> res...

FILE: src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityAlarmReceiver.java
  class UnityAlarmReceiver (line 10) | public class UnityAlarmReceiver extends BroadcastReceiver {
    method onReceive (line 12) | @Override

FILE: src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityDownloaderActivity.java
  class UnityDownloaderActivity (line 24) | public class UnityDownloaderActivity extends Activity implements IDownlo...
    method onCreate (line 48) | @Override
    method onResume (line 86) | @Override
    method onStop (line 97) | @Override
    method initializeDownloadUI (line 105) | private void initializeDownloadUI() {
    method setState (line 163) | private void setState(int newState) {
    method setButtonPausedState (line 170) | private void setButtonPausedState(boolean paused) {
    method onServiceConnected (line 176) | @Override
    method onDownloadStateChanged (line 181) | @Override
    method onDownloadProgress (line 253) | @Override

FILE: src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityDownloaderService.java
  class UnityDownloaderService (line 5) | public class UnityDownloaderService extends DownloaderService {
    method getPublicKey (line 18) | @Override
    method getSALT (line 28) | @Override
    method getAlarmReceiverClassName (line 38) | @Override
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (361K chars).
[
  {
    "path": ".gitignore",
    "chars": 163,
    "preview": "Library/\r\nPackages/\r\nProjectSettings/\r\nsrc/unityOBBDownloader/.gradle/\r\nsrc/unityOBBDownloader/build/\r\nTemp/\r\nUnityPacka"
  },
  {
    "path": "Assets/Scripts/DownloadObbExample.cs",
    "chars": 1470,
    "preview": "using UnityEngine;\nusing System.Collections;\n\npublic class DownloadObbExample : MonoBehaviour\n{\n    private IGooglePlayO"
  },
  {
    "path": "Assets/Scripts/GooglePlayDownloader.cs",
    "chars": 855,
    "preview": "using UnityEngine;\nusing System;\n\npublic interface IGooglePlayObbDownloader\n{\n    string PublicKey { get; set; }\n\n    st"
  },
  {
    "path": "Assets/Scripts/GooglePlayDownloaderImpl.cs",
    "chars": 4883,
    "preview": "using UnityEngine;\nusing System.IO;\nusing System;\n\ninternal class GooglePlayObbDownloader : IGooglePlayObbDownloader\n{\n "
  },
  {
    "path": "CHANGELOG",
    "chars": 349,
    "preview": "Version 3.0 (2017-09-22)\n\n* Scripts rewritten, with IGooglePlayObbDownloader interface extracted\n* Sample updated to mat"
  },
  {
    "path": "LICENSE.txt",
    "chars": 10762,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 2486,
    "preview": "# UnityOBBDownloader\nThis package is an adaption of the Google Play market_downloader library, for use with Unity Androi"
  },
  {
    "path": "src/unityOBBDownloader/build.gradle",
    "chars": 440,
    "preview": "apply plugin: 'com.android.library'\n\nbuildscript {\n\trepositories {\n\t\tjcenter()\n\t\tgoogle()\n\t}\n\n\tdependencies {\n\t\tclasspat"
  },
  {
    "path": "src/unityOBBDownloader/gradle/wrapper/gradle-wrapper.properties",
    "chars": 202,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dist"
  },
  {
    "path": "src/unityOBBDownloader/gradlew",
    "chars": 5296,
    "preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up"
  },
  {
    "path": "src/unityOBBDownloader/gradlew.bat",
    "chars": 2176,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "src/unityOBBDownloader/proguard.cfg",
    "chars": 1437,
    "preview": "-optimizationpasses 5\n-dontusemixedcaseclassnames\n-dontskipnonpubliclibraryclasses\n-dontpreverify\n-verbose\n-optimization"
  },
  {
    "path": "src/unityOBBDownloader/src/main/AndroidManifest.xml",
    "chars": 1013,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    pack"
  },
  {
    "path": "src/unityOBBDownloader/src/main/aidl/com/android/vending/licensing/ILicenseResultListener.aidl",
    "chars": 782,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/aidl/com/android/vending/licensing/ILicensingService.aidl",
    "chars": 851,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Constants.java",
    "chars": 8390,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloadProgressInfo.java",
    "chars": 2694,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderClientMarshaller.java",
    "chars": 11680,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/DownloaderServiceMarshaller.java",
    "chars": 5757,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/Helpers.java",
    "chars": 14681,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderClient.java",
    "chars": 5462,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IDownloaderService.java",
    "chars": 2920,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/IStub.java",
    "chars": 1467,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/SystemFacade.java",
    "chars": 4194,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/CustomIntentService.java",
    "chars": 3776,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadInfo.java",
    "chars": 2924,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadNotification.java",
    "chars": 9752,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadThread.java",
    "chars": 34278,
    "preview": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloaderService.java",
    "chars": 49989,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/DownloadsDB.java",
    "chars": 18936,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/expansion/downloader/impl/HttpDateTime.java",
    "chars": 7056,
    "preview": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/AESObfuscator.java",
    "chars": 4829,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/APKExpansionPolicy.java",
    "chars": 14776,
    "preview": "\npackage com.google.android.vending.licensing;\n\n/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed "
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/DeviceLimiter.java",
    "chars": 2047,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseChecker.java",
    "chars": 16191,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseCheckerCallback.java",
    "chars": 2651,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/LicenseValidator.java",
    "chars": 8399,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/NullDeviceLimiter.java",
    "chars": 1136,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/Obfuscator.java",
    "chars": 1989,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/Policy.java",
    "chars": 2046,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/PreferenceObfuscator.java",
    "chars": 2411,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ResponseData.java",
    "chars": 2570,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ServerManagedPolicy.java",
    "chars": 10281,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/StrictPolicy.java",
    "chars": 2247,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/ValidationException.java",
    "chars": 1020,
    "preview": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/Base64.java",
    "chars": 22665,
    "preview": "// Portions copyright 2002, Google, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you ma"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/Base64DecoderException.java",
    "chars": 971,
    "preview": "// Copyright 2002, Google, Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/google/android/vending/licensing/util/URIQueryDecoder.java",
    "chars": 2243,
    "preview": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityAlarmReceiver.java",
    "chars": 665,
    "preview": "package com.unity3d.plugin.downloader;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityDownloaderActivity.java",
    "chars": 10798,
    "preview": "package com.unity3d.plugin.downloader;\n\nimport android.app.Activity;\nimport android.app.PendingIntent;\nimport android.gr"
  },
  {
    "path": "src/unityOBBDownloader/src/main/java/com/unity3d/plugin/downloader/UnityDownloaderService.java",
    "chars": 1391,
    "preview": "package com.unity3d.plugin.downloader;\n\nimport com.google.android.vending.expansion.downloader.impl.DownloaderService;\n\n"
  },
  {
    "path": "src/unityOBBDownloader/src/main/res/layout/main.xml",
    "chars": 7174,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "src/unityOBBDownloader/src/main/res/layout/status_bar_ongoing_event_progress_bar.xml",
    "chars": 3130,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/*\n** Copyright 2012, The Android Open Source Project\n**\n** Licensed under t"
  },
  {
    "path": "src/unityOBBDownloader/src/main/res/values/main-strings.xml",
    "chars": 1135,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n<!--\n    <string name=\"app_name\">APKX Downloader</string> -->\n    <st"
  },
  {
    "path": "src/unityOBBDownloader/src/main/res/values/strings.xml",
    "chars": 2657,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- When a download completes, a notification is displayed, and"
  },
  {
    "path": "src/unityOBBDownloader/src/main/res/values/styles.xml",
    "chars": 1020,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"NotificationText\">\n        <item name=\"android:text"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the Over17/UnityOBBDownloader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (335.5 KB), approximately 76.3k tokens, and a symbol index with 401 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!