/ExpansionFileDownloader

Library to download expansion files

Primary LanguageJava

Expansion Files Downloader

Library with Google Packages to download Expansion Files.

Add libaries as submodule in settings.gradle

include ':app',
include ':zip_file'
include ':market_licensing'
include ':downloader_library'

project(':zip_file').projectDir = new File('expasionfilesdownloader/zip_file')
project(':market_licensing').projectDir = new File('expasionfilesdownloader/market_licensing')
project(':downloader_library').projectDir = new File('expasionfilesdownloader/downloader_library')

Make use of the ExpansionFileDownloadActivity in your MainActity.

 override fun onResume() {
        println("onResume MainActivity")

        initExpansionFilesDelivered()

        if (expansionFileDelivered or !askPermissionAgainPermitted) {
            startEMCAnimation()
        } else if (hasNoInternetConnection) {
            initNoInternetPopup()
        } else if (downloadIsPaused) {
            askCellularData()
        } else {
            handleDownloadExpansionFiles()
        }

        super.onResume()
    }
    
/**
 * Refresh the activity.
 * Show EMC Popup or start downloading expansion files.
 */
private fun refresh() {
    when {
        expansionFileDelivered or !askPermissionAgainPermitted -> startEMCAnimation()

        downloadIsPaused -> {
            requestDownloadStatus()
            askCellularData()
        }

        isDownloading -> {
            requestDownloadStatus()
            initProgressDialog()
        }

        else -> handleDownloadExpansionFiles()
    }
}

// Create AlarmReceiver

package nl.zorgkluis.expansionfiles

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller

/**
 * Created by GeertBerkers.
 */
class AppAlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    context,
                    intent,
                    AppDownloaderService::class.java
            )
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }
}

// Create Downloader Service

package nl.zorgkluis.expansionfiles

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

/**
 * Created by GeertBerkers.
 */
class AppDownloaderService : DownloaderService() {

    companion object {

        const val BASE64_PUBLIC_KEY = "..."

        // TODO: Check if from app, or randomly generated
        private val SALT = byteArrayOf(1,2,3,4,5,6,7,8,9,10,20)
    }

    override fun getPublicKey(): String = BASE64_PUBLIC_KEY

    override fun getSALT(): ByteArray = SALT

    override fun getAlarmReceiverClassName(): String = AppAlarmReceiver::class.java.name
}

// Create MP4ContentProvider

package nl.zorgkluis.expansionfiles

import android.net.Uri

import com.android.vending.expansion.zipfile.APEZProvider

import java.io.File

/**
 * Created by GeertBerkers.
 */
class MP4ContentProvider : APEZProvider() {

    override fun getAuthority(): String {
        return AUTHORITY
    }

    companion object {

        private const val AUTHORITY = "nl.zorgkluis.expansilfiles.content.MP4ContentProvider"

        fun buildURI(path: String): Uri {
            val contentPath = "content://" +
                    AUTHORITY +
                    File.separator +
                    path

            return Uri.parse(contentPath)
        }
    }
}

// Create xAPK Files package nl.zorgkluis.expansionfiles

/**
 * Created by GeertBerkers.
 */
internal class XAPKFile constructor(
        val mIsMain: Boolean,
        val mFileVersion: Int,
        val mFileSize: Long
)

// Create ExpansionFilesDownlaoder Activity

@file:Suppress("DEPRECATION")

package nl.zorgkluis.expansionfiles

import android.Manifest
import android.app.PendingIntent
import android.app.ProgressDialog
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Messenger
import android.provider.Settings
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.widget.Toast
import com.google.android.vending.expansion.downloader.*
import nl.zorgkluis.expansionfiles.R
import org.jetbrains.anko.alert
import org.jetbrains.anko.appcompat.v7.Appcompat
import org.jetbrains.anko.progressDialog

open class ExpansionFileDownloadActivity : AppCompatActivity(), IDownloaderClient {

    companion object {
        const val STORAGE_REQUEST = 1
    }

    private var cellularDialog: AlertDialog? = null
    private var noInternetDialog: AlertDialog? = null
    private var dialogWithProgress: ProgressDialog? = null


    private var mState: Int = 0
    private var storagePermissionGranted = false
    protected var askPermissionAgainPermitted = true
    protected var expansionFileDelivered: Boolean = false

    private var mDownloaderClientStub : IStub? = null
    private var mRemoteService : IDownloaderService? = null

    private val initialAPKVersionCode : Int = 29
    private val mainFileSize : Long = 156085389L

    protected val hasNoInternetConnection
        get() = mState == IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE

    protected val isDownloading
        get() = dialogWithProgress != null && dialogWithProgress?.progress != dialogWithProgress?.max

    protected val downloadIsPaused
        get() = mState == IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION ||
                mState == IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION ||
                mState == IDownloaderClient.STATE_PAUSED_BY_REQUEST

    private val xAPKS = arrayOf(
            XAPKFile(true, initialAPKVersionCode, mainFileSize)
    )

    private val maxFileSize : Int by lazy {
        (xAPKS[0].mFileSize / 1024 / 1024).toInt()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        createDownloaderStub()
    }

    override fun onStart() {
        connectDownloaderClientStub()
        super.onStart()
    }

    override fun onPause() {
        hideProgressDialog()
        super.onPause()
    }

    override fun onStop() {
        disconnectDownloaderClientStub()
        super.onStop()
    }
    /**
     * Create the sub for our service
     */
    private fun createDownloaderStub() {
        mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, AppDownloaderService::class.java)
    }

    /**
     * Connect the stub to our service on start.
     */
    private fun connectDownloaderClientStub() {
        mDownloaderClientStub?.connect(this)
    }

    /**
     * Disconnect the stub from our service on stop
     */
    private fun disconnectDownloaderClientStub() {
        mDownloaderClientStub?.disconnect(this)
    }

    /**
     * Check whether the ExpansionFiles are delivered or not
     */
    protected fun initExpansionFilesDelivered() {
        expansionFileDelivered = false

        for (xf in xAPKS) {
            val fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion)
            if (Helpers.doesFileExist(this, fileName, xf.mFileSize, false)) {
                expansionFileDelivered = true
            }
        }
    }

    /**
     * Download expansion files. Ask permission if needed
     */
    protected fun handleDownloadExpansionFiles() {
        if (storagePermissionGranted) {
            downloadExpansionFiles()
        } else if (askPermissionAgainPermitted) {
            handleStoragePermission()
        }
    }

    //region Storage Permission Functions
    private fun handleStoragePermission() {
        if (!checkWriteExternalStorage()) {
            if (shouldShowRequestPermission()) {
                showNoStoragePermission()
            } else {
                requestWriteExternalStoragePermission()
            }
            return
        }

        storagePermissionGranted = true
        downloadExpansionFiles()
    }

    private fun showNoStoragePermission() {
        initAppLocale()

        alert(Appcompat, R.string.askStorageRightsTitle, R.string.askStorageRightsMessage){
            iconResource = R.drawable.ic_storage

            positiveButton(R.string.askStorageRightsGrant){
                requestWriteExternalStoragePermission()
            }

            negativeButton(R.string.askStorageRightsCancel){ dialog ->
                startEMCAnimation()
                dialog.dismiss()
            }
        }.show()
    }

    /**
     * Check permissions for WRITE_EXTERNAL_STORAGE
     *
     * @return true if permission granted, false if not
     */
    private fun checkWriteExternalStorage(): Boolean {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
    }

    /**
     * Check if you should show request permission rationale for Write External Storage
     *
     * @return true if you should have to show why this permission is needed
     */
    private fun shouldShowRequestPermission(): Boolean {
        return ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
    }

    /**
     * Request permission for Write External Storage
     */
    private fun requestWriteExternalStoragePermission() {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), STORAGE_REQUEST)
    }

    /**
     * Check if Never ask again for Write External Storage Permission is pressed!
     */
    private fun checkNeverAskPermissionAgain() {
        if (!shouldShowRequestPermission()) {
            askPermissionAgainPermitted = checkWriteExternalStorage()
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        storagePermissionGranted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED

        if (requestCode == STORAGE_REQUEST) {
            if (storagePermissionGranted) {
                downloadExpansionFiles()
            } else {
                checkNeverAskPermissionAgain()
            }
        }
    }

    //endregion

    /**
     * Download the expansion files.
     * Create intent for notification.
     * Start the download and initialize progressDialog
     */
    private fun downloadExpansionFiles() {
        expansionFileDelivered = false

        try {
            val launchIntent = this.intent
            val mainIntentNotification = Intent(this, this.javaClass).apply {
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
            }.apply {
                action = launchIntent.action
            }

            if (launchIntent.categories != null) {
                for (category in launchIntent.categories) {
                    mainIntentNotification.addCategory(category)
                }
            }

            // Build PendingIntent to open this activity from Notification and request to start the download
            val pendingIntent = PendingIntent.getActivity(this, 0, mainIntentNotification, PendingIntent.FLAG_UPDATE_CURRENT)
            val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, AppDownloaderService::class.java)

            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                initProgressDialog()
            }
        } catch (e: PackageManager.NameNotFoundException) {
            Log.e("Zorgkluis", "Cannot find package!", e)
        }

    }

    protected fun requestDownloadStatus() {
        mRemoteService?.requestDownloadStatus()
    }

    protected fun initProgressDialog() {
        initAppLocale()

        if (dialogWithProgress == null) {
            dialogWithProgress = progressDialog(message = R.string.availableAfterDownloadMessage) {
                max = maxFileSize
                setProgressNumberFormat("%1d MB / %2d MB")
                setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
//                setMessage(getString(R.string.available_after_download))
                setOnDismissListener {
                    startEMCAnimation()
                }
            }
        }

        dialogWithProgress?.run {
            setTitle(R.string.downloading)
            setMessage(getString(R.string.availableAfterDownloadMessage))
            show()
        }
    }



    /**
     * 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.
     */
    override fun onServiceConnected(m: Messenger?) {
        mRemoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
            mDownloaderClientStub?.messenger?.also { messenger ->
                onClientUpdated(messenger)
            }
        }
    }

    override fun onDownloadStateChanged(newState: Int) {
        Log.e("Zorgkluis", "onDownloadStateChanged")
        setState(newState)
        val indeterminate: Boolean
        when (newState) {
            IDownloaderClient.STATE_IDLE ->
                // STATE_IDLE means the service is listening, so it's
                // safe to start making calls via mRemoteService.
                indeterminate = true

            IDownloaderClient.STATE_CONNECTING, IDownloaderClient.STATE_FETCHING_URL -> {
                hideCellularDialog()
                hideNoInternetDialog()
                indeterminate = true
            }

            IDownloaderClient.STATE_DOWNLOADING -> {
                initProgressDialog()
                indeterminate = false
            }

            IDownloaderClient.STATE_FAILED_CANCELED, IDownloaderClient.STATE_FAILED, IDownloaderClient.STATE_FAILED_FETCHING_URL -> {
                indeterminate = false
                dismissProgressDialog()
                //TODO: Rename toast
                Toast.makeText(this, "Probleem opgetreden tijdens downloaden", Toast.LENGTH_LONG).show()
            }

            IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION, IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION -> {
                indeterminate = false
                askCellularData()
            }

            IDownloaderClient.STATE_FAILED_UNLICENSED -> {
                indeterminate = false
                Toast.makeText(this, "Probleem opgetreden tijdens downloaden", Toast.LENGTH_LONG).show()
                dialogWithProgress?.dismiss()
            }

            IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE -> {
                initNoInternetPopup()
                indeterminate = false
            }

            IDownloaderClient.STATE_PAUSED_BY_REQUEST -> indeterminate = false

            IDownloaderClient.STATE_PAUSED_ROAMING, IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE -> indeterminate = false

            IDownloaderClient.STATE_COMPLETED -> {
                dismissProgressDialog()

                expansionFileDelivered = true
                return
            }
            else -> indeterminate = true
        }

        dialogWithProgress?.isIndeterminate = indeterminate
    }

    override fun onDownloadProgress(progressInfo: DownloadProgressInfo) {
        dialogWithProgress?.run {
            max = (progressInfo.mOverallTotal / 1024 / 1024).toInt()
            progress = (progressInfo.mOverallProgress / 1024 / 1024).toInt()
        }
    }

    /**
     * Dismiss the ProgressDialog
     */
    private fun hideProgressDialog() {
        dialogWithProgress?.hide()
    }

    /**
     * Dismiss the ProgressDialog
     */
    private fun dismissProgressDialog() {
        dialogWithProgress?.dismiss()
    }

    protected fun askCellularData() {
        initAppLocale()


        if (cellularDialog == null){
            cellularDialog = alert(Appcompat, R.string.askCellularMessage, R.string.warningTitle){
                positiveButton(R.string.wifiSettings) { dialog ->
                    startActivity(Intent(Settings.ACTION_WIFI_SETTINGS))
                    dialog.cancel()
                }

                neutralPressed(R.string.hide) { dialog ->
                    dialog.dismiss()
                    startEMCAnimation()
                }

                negativeButton(R.string.yes) { dialog ->
                    mRemoteService?.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR)
                    mRemoteService?.requestContinueDownload()
                    dialog.cancel()

                    dialogWithProgress?.run {
                        if (!isShowing) {
                            show()
                        }
                    }
                }
            }.build().apply {
                setCancelable(false)
            }

        }

        hideProgressDialog()
        hideNoInternetDialog()

        cellularDialog?.run {
            setTitle(R.string.warningTitle)
            setMessage(getString(R.string.askCellularMessage))
            getButton(DialogInterface.BUTTON_NEGATIVE)?.setText(R.string.yes)
            getButton(DialogInterface.BUTTON_NEUTRAL)?.setText(R.string.hide)
            getButton(DialogInterface.BUTTON_POSITIVE)?.setText(R.string.wifiSettings)
            show()
        }
    }

    private fun hideCellularDialog() {
        cellularDialog?.hide()
    }

    protected fun initNoInternetPopup() {
        initAppLocale()

        if (noInternetDialog == null) {
            noInternetDialog = alert(Appcompat,
                    R.string.noInternetMessage,
                    R.string.state_paused_network_unavailable) {

                positiveButton(R.string.wifiSettings) { dialog ->
                    startActivity(Intent(Settings.ACTION_WIFI_SETTINGS))
                    dialog.cancel()
                }

                neutralPressed(R.string.hide) { dialog ->
                    dialog.dismiss()
                    startEMCAnimation()
                }

                negativeButton(R.string.dataSettings) { dialog ->
                    val intent = Intent().apply {
                        component = ComponentName(
                                "com.android.settings",
                                "com.android.settings.Settings\$DataUsageSummaryActivity"
                        )
                    }

                    startActivity(intent)
                    dialog.cancel()
                }

            }.build().apply {
                setCancelable(false)
            }
        }

        hideProgressDialog()
        hideCellularDialog()

        noInternetDialog?.apply {
            setTitle(R.string.state_paused_network_unavailable)
            setMessage(getString(R.string.noInternetMessage))
            getButton(DialogInterface.BUTTON_NEUTRAL)?.setText(R.string.hide)
            getButton(DialogInterface.BUTTON_NEGATIVE)?.setText(R.string.dataSettings)
            getButton(DialogInterface.BUTTON_POSITIVE)?.setText(R.string.wifiSettings)
        }

        noInternetDialog?.show()

    }

    private fun hideNoInternetDialog() {
        noInternetDialog?.run {
            if (isShowing){
                dismiss()
            }
        }

    }

    /**
     * Set the current download state
     *
     * @param newState the new state
     */
    private fun setState(newState: Int) {
        if (mState != newState) {
            mState = newState
            logState(mState)
        }
    }

    /**
     * Log the current download state
     */
    private fun logState(mState: Int) {
        val state: String
        when (mState) {
            1 -> state = "STATE_IDLE"
            2 -> state = "STATE_FETCHING_URL"
            3 -> state = "STATE_CONNECTING"
            4 -> state = "STATE_DOWNLOADING"
            5 -> state = "STATE_COMPLETED"
            6 -> state = "STATE_PAUSED_NETWORK_UNAVAILABLE"
            7 -> state = "STATE_PAUSED_BY_REQUEST"
            8 -> state = "STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION"
            9 -> state = "STATE_PAUSED_NEED_CELLULAR_PERMISSION"
            10 -> state = "STATE_PAUSED_WIFI_DISABLED"
            11 -> state = "STATE_PAUSED_NEED_WIFI"
            12 -> state = "STATE_PAUSED_ROAMING"
            13 -> state = "STATE_PAUSED_NETWORK_SETUP_FAILURE"
            14 -> state = "STATE_PAUSED_SDCARD_UNAVAILABLE"
            15 -> state = "STATE_FAILED_UNLICENSED"
            16 -> state = "STATE_FAILED_FETCHING_URL"
            17 -> state = "STATE_FAILED_SDCARD_FULL"
            18 -> state = "STATE_FAILED_CANCELED"
            19 -> state = "STATE_FAILED"
            else -> state = "DEFAULT"
        }
        Log.e("Zorgkluis", state)
    }


    open fun startEMCAnimation() {
        // Methods to override from MainActivity

    }

    open fun initAppLocale() {
        // Methods to override from MainActivity
    }
}

// Create String resources Yes Hide Warning Downloading Wifi settings Data settings Network unavailable Check your internet settings Video's are available after download Do you want to download the content with your mobile network? This can cause additional costs