mirror of
https://github.com/QuasarApp/QtAndroidTools.git
synced 2025-05-18 14:59:33 +00:00
Changed java libraries for apk expensions file download with non official better-apk-expansion version 5.0.3 and better-licensing version 1.5.0
This commit is contained in:
parent
12e61b761b
commit
b926f059f4
@ -1,29 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include "QtAndroidApkExpansionFiles.h"
|
#include "QAndroidApkExpansionFiles.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL Java_com_google_android_vending_expansion_downloader_Helpers_getDownloaderStringResourceFromState(JNIEnv *env, jobject thiz, jint state)
|
JNIEXPORT jstring JNICALL Java_com_falsinsoft_qtandroidtools_ApkExpansionDownloader_getString(JNIEnv *env, jobject thiz, jint stringID)
|
||||||
{
|
{
|
||||||
QtAndroidApkExpansionFiles *pInstance = QtAndroidApkExpansionFiles::instance();
|
QAndroidApkExpansionFiles *pInstance = QAndroidApkExpansionFiles::instance();
|
||||||
QString TextInfo;
|
QString TextString;
|
||||||
|
|
||||||
Q_UNUSED(thiz)
|
Q_UNUSED(thiz)
|
||||||
|
|
||||||
if(pInstance != nullptr)
|
if(pInstance != nullptr)
|
||||||
{
|
{
|
||||||
TextInfo = pInstance->stateTextInfo(state);
|
TextString = pInstance->getString(stringID);
|
||||||
}
|
}
|
||||||
|
|
||||||
return env->NewString(TextInfo.utf16(), TextInfo.length());
|
return env->NewString(TextString.utf16(), TextString.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_com_falsinsoft_QtAndroidTools_ApkExpansionDownloader_downloadStateChanged(JNIEnv *env, jobject thiz, jint newState)
|
JNIEXPORT void JNICALL Java_com_falsinsoft_qtandroidtools_ApkExpansionDownloader_downloadStateChanged(JNIEnv *env, jobject thiz, jint newState)
|
||||||
{
|
{
|
||||||
QtAndroidApkExpansionFiles *pInstance = QtAndroidApkExpansionFiles::instance();
|
QAndroidApkExpansionFiles *pInstance = QAndroidApkExpansionFiles::instance();
|
||||||
|
|
||||||
Q_UNUSED(env)
|
Q_UNUSED(env)
|
||||||
Q_UNUSED(thiz)
|
Q_UNUSED(thiz)
|
||||||
@ -34,9 +56,9 @@ JNIEXPORT void JNICALL Java_com_falsinsoft_QtAndroidTools_ApkExpansionDownloader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_com_falsinsoft_QtAndroidTools_ApkExpansionDownloader_downloadProgress(JNIEnv *env, jobject thiz, jlong overallTotal, jlong overallProgress, jlong timeRemaining, jfloat currentSpeed)
|
JNIEXPORT void JNICALL Java_com_falsinsoft_qtandroidtools_ApkExpansionDownloader_downloadProgress(JNIEnv *env, jobject thiz, jlong overallTotal, jlong overallProgress, jlong timeRemaining, jfloat currentSpeed)
|
||||||
{
|
{
|
||||||
QtAndroidApkExpansionFiles *pInstance = QtAndroidApkExpansionFiles::instance();
|
QAndroidApkExpansionFiles *pInstance = QAndroidApkExpansionFiles::instance();
|
||||||
|
|
||||||
Q_UNUSED(env)
|
Q_UNUSED(env)
|
||||||
Q_UNUSED(thiz)
|
Q_UNUSED(thiz)
|
||||||
@ -51,52 +73,62 @@ JNIEXPORT void JNICALL Java_com_falsinsoft_QtAndroidTools_ApkExpansionDownloader
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QtAndroidApkExpansionFiles *QtAndroidApkExpansionFiles::m_pInstance = nullptr;
|
QAndroidApkExpansionFiles *QAndroidApkExpansionFiles::m_pInstance = nullptr;
|
||||||
|
|
||||||
QtAndroidApkExpansionFiles::QtAndroidApkExpansionFiles() : m_JavaApkExpansionDownloader("com/falsinsoft/QtAndroidTools/ApkExpansionDownloader",
|
QAndroidApkExpansionFiles::QAndroidApkExpansionFiles() : m_JavaApkExpansionDownloader("com/falsinsoft/qtandroidtools/ApkExpansionDownloader",
|
||||||
"(Landroid/app/Activity;)V",
|
"(Landroid/app/Activity;)V",
|
||||||
QtAndroid::androidActivity().object<jobject>())
|
QtAndroid::androidActivity().object<jobject>())
|
||||||
{
|
{
|
||||||
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QtAndroidApkExpansionFiles::ApplicationStateChanged);
|
|
||||||
qRegisterMetaType<ExpansionFileInfo>();
|
|
||||||
m_pInstance = this;
|
m_pInstance = this;
|
||||||
|
|
||||||
|
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QAndroidApkExpansionFiles::ApplicationStateChanged);
|
||||||
|
qRegisterMetaType<ExpansionFileInfo>();
|
||||||
|
SetNewAppState(APP_STATE_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject* QtAndroidApkExpansionFiles::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
|
QAndroidApkExpansionFiles::~QAndroidApkExpansionFiles()
|
||||||
|
{
|
||||||
|
SetNewAppState(APP_STATE_DESTROY);
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject* QAndroidApkExpansionFiles::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||||
{
|
{
|
||||||
Q_UNUSED(engine);
|
Q_UNUSED(engine);
|
||||||
Q_UNUSED(scriptEngine);
|
Q_UNUSED(scriptEngine);
|
||||||
|
|
||||||
return new QtAndroidApkExpansionFiles();
|
return new QAndroidApkExpansionFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
QtAndroidApkExpansionFiles* QtAndroidApkExpansionFiles::instance()
|
QAndroidApkExpansionFiles* QAndroidApkExpansionFiles::instance()
|
||||||
{
|
{
|
||||||
return m_pInstance;
|
return m_pInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtAndroidApkExpansionFiles::ApplicationStateChanged(Qt::ApplicationState State)
|
void QAndroidApkExpansionFiles::ApplicationStateChanged(Qt::ApplicationState State)
|
||||||
{
|
{
|
||||||
const bool StubConnect = (State == Qt::ApplicationActive) ? true : false;
|
SetNewAppState((State == Qt::ApplicationActive) ? APP_STATE_START : APP_STATE_STOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QAndroidApkExpansionFiles::SetNewAppState(APP_STATE NewState)
|
||||||
|
{
|
||||||
if(m_JavaApkExpansionDownloader.isValid())
|
if(m_JavaApkExpansionDownloader.isValid())
|
||||||
{
|
{
|
||||||
m_JavaApkExpansionDownloader.callMethod<void>("enableClientStubConnection",
|
m_JavaApkExpansionDownloader.callMethod<void>("appStateChanged",
|
||||||
"(Z)V",
|
"(I)V",
|
||||||
StubConnect
|
NewState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QtAndroidApkExpansionFiles::APKEF_STATE QtAndroidApkExpansionFiles::startDownloadFiles()
|
QAndroidApkExpansionFiles::APKEF_STATE QAndroidApkExpansionFiles::startDownloadFiles()
|
||||||
{
|
{
|
||||||
if(m_JavaApkExpansionDownloader.isValid() == false) return APKEF_INVALID_JAVA_CLASS;
|
if(m_JavaApkExpansionDownloader.isValid() == false) return APKEF_INVALID_JAVA_CLASS;
|
||||||
if(m_Base64PublicKey.count() == 0) return APKEF_INVALID_BASE64_PUBLIC_KEY;
|
if(m_Base64PublicKey.count() == 0) return APKEF_INVALID_BASE64_PUBLIC_KEY;
|
||||||
if(m_SALT.count() != 20) return APKEF_INVALID_SALT;
|
if(m_SALT.count() != 20) return APKEF_INVALID_SALT;
|
||||||
if(QtAndroid::androidSdkVersion() >= 23)
|
if(QtAndroid::androidSdkVersion() >= 23)
|
||||||
{
|
{
|
||||||
if(QtAndroid::checkPermission("Manifest.permission.READ_EXTERNAL_STORAGE") != QtAndroid::PermissionResult::Granted) return APKEF_STORAGE_READ_PERMISSION_REQUIRED;
|
if(QtAndroid::checkPermission("android.permission.READ_EXTERNAL_STORAGE") != QtAndroid::PermissionResult::Granted) return APKEF_STORAGE_READ_PERMISSION_REQUIRED;
|
||||||
if(QtAndroid::checkPermission("Manifest.permission.WRITE_EXTERNAL_STORAGE") != QtAndroid::PermissionResult::Granted) return APKEF_STORAGE_WRITE_PERMISSION_REQUIRED;
|
if(QtAndroid::checkPermission("android.permission.WRITE_EXTERNAL_STORAGE") != QtAndroid::PermissionResult::Granted) return APKEF_STORAGE_WRITE_PERMISSION_REQUIRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i = 0; i < 2; i++)
|
for(int i = 0; i < 2; i++)
|
||||||
@ -147,7 +179,7 @@ QtAndroidApkExpansionFiles::APKEF_STATE QtAndroidApkExpansionFiles::startDownloa
|
|||||||
return APKEF_UNKNOWN_ERROR;
|
return APKEF_UNKNOWN_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QtAndroidApkExpansionFiles::mainFileName()
|
QString QAndroidApkExpansionFiles::mainFileName()
|
||||||
{
|
{
|
||||||
QString FileName;
|
QString FileName;
|
||||||
|
|
||||||
@ -164,7 +196,7 @@ QString QtAndroidApkExpansionFiles::mainFileName()
|
|||||||
return FileName;
|
return FileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QtAndroidApkExpansionFiles::patchFileName()
|
QString QAndroidApkExpansionFiles::patchFileName()
|
||||||
{
|
{
|
||||||
QString FileName;
|
QString FileName;
|
||||||
|
|
||||||
@ -181,111 +213,128 @@ QString QtAndroidApkExpansionFiles::patchFileName()
|
|||||||
return FileName;
|
return FileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString& QtAndroidApkExpansionFiles::getBase64PublicKey() const
|
void QAndroidApkExpansionFiles::sendRequest(REQUEST_ID requestID)
|
||||||
|
{
|
||||||
|
if(m_JavaApkExpansionDownloader.isValid())
|
||||||
|
{
|
||||||
|
m_JavaApkExpansionDownloader.callMethod<void>("sendRequest",
|
||||||
|
"(I)V",
|
||||||
|
requestID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& QAndroidApkExpansionFiles::getBase64PublicKey() const
|
||||||
{
|
{
|
||||||
return m_Base64PublicKey;
|
return m_Base64PublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtAndroidApkExpansionFiles::setBase64PublicKey(const QString &Base64PublicKey)
|
void QAndroidApkExpansionFiles::setBase64PublicKey(const QString &Base64PublicKey)
|
||||||
{
|
{
|
||||||
m_Base64PublicKey = Base64PublicKey;
|
m_Base64PublicKey = Base64PublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVector<int>& QtAndroidApkExpansionFiles::getSALT() const
|
const QVector<int>& QAndroidApkExpansionFiles::getSALT() const
|
||||||
{
|
{
|
||||||
return m_SALT;
|
return m_SALT;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtAndroidApkExpansionFiles::setSALT(const QVector<int> &SALT)
|
void QAndroidApkExpansionFiles::setSALT(const QVector<int> &SALT)
|
||||||
{
|
{
|
||||||
m_SALT = SALT;
|
m_SALT = SALT;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpansionFileInfo& QtAndroidApkExpansionFiles::getMainExpansionFileInfo() const
|
const ExpansionFileInfo& QAndroidApkExpansionFiles::getMainExpansionFileInfo() const
|
||||||
{
|
{
|
||||||
return m_ExpansionsFileInfo[0];
|
return m_ExpansionsFileInfo[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtAndroidApkExpansionFiles::setMainExpansionFileInfo(const ExpansionFileInfo &MainExpansionFileInfo)
|
void QAndroidApkExpansionFiles::setMainExpansionFileInfo(const ExpansionFileInfo &MainExpansionFileInfo)
|
||||||
{
|
{
|
||||||
m_ExpansionsFileInfo[0] = MainExpansionFileInfo;
|
m_ExpansionsFileInfo[0] = MainExpansionFileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpansionFileInfo& QtAndroidApkExpansionFiles::getPatchExpansionFileInfo() const
|
const ExpansionFileInfo& QAndroidApkExpansionFiles::getPatchExpansionFileInfo() const
|
||||||
{
|
{
|
||||||
return m_ExpansionsFileInfo[1];
|
return m_ExpansionsFileInfo[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtAndroidApkExpansionFiles::setPatchExpansionFileInfo(const ExpansionFileInfo &PatchExpansionFileInfo)
|
void QAndroidApkExpansionFiles::setPatchExpansionFileInfo(const ExpansionFileInfo &PatchExpansionFileInfo)
|
||||||
{
|
{
|
||||||
m_ExpansionsFileInfo[1] = PatchExpansionFileInfo;
|
m_ExpansionsFileInfo[1] = PatchExpansionFileInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QtAndroidApkExpansionFiles::stateTextInfo(int state)
|
QString QAndroidApkExpansionFiles::getString(int stringID)
|
||||||
{
|
{
|
||||||
QString TextInfo;
|
QString TextString;
|
||||||
|
|
||||||
switch(state)
|
switch(stringID)
|
||||||
{
|
{
|
||||||
case STATE_IDLE:
|
case STRING_IDLE:
|
||||||
TextInfo = tr("Waiting for download to start");
|
TextString = tr("Waiting for download to start");
|
||||||
break;
|
break;
|
||||||
case STATE_FETCHING_URL:
|
case STRING_FETCHING_URL:
|
||||||
TextInfo = tr("Looking for resources to download");
|
TextString = tr("Looking for resources to download");
|
||||||
break;
|
break;
|
||||||
case STATE_CONNECTING:
|
case STRING_CONNECTING:
|
||||||
TextInfo = tr("Connecting to the download server");
|
TextString = tr("Connecting to the download server");
|
||||||
break;
|
break;
|
||||||
case STATE_DOWNLOADING:
|
case STRING_DOWNLOADING:
|
||||||
TextInfo = tr("Downloading resources");
|
TextString = tr("Downloading resources");
|
||||||
break;
|
break;
|
||||||
case STATE_COMPLETED:
|
case STRING_COMPLETED:
|
||||||
TextInfo = tr("Download finished");
|
TextString = tr("Download finished");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_NETWORK_UNAVAILABLE:
|
case STRING_PAUSED_NETWORK_UNAVAILABLE:
|
||||||
TextInfo = tr("Download paused because no network is available");
|
TextString = tr("Download paused because no network is available");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_BY_REQUEST:
|
case STRING_PAUSED_BY_REQUEST:
|
||||||
TextInfo = tr("Download paused");
|
TextString = tr("Download paused");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
|
case STRING_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
|
||||||
TextInfo = tr("Download paused because wifi is disabled");
|
TextString = tr("Download paused because wifi is disabled");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_NEED_CELLULAR_PERMISSION:
|
case STRING_PAUSED_NEED_CELLULAR_PERMISSION:
|
||||||
case STATE_PAUSED_NEED_WIFI:
|
case STRING_PAUSED_NEED_WIFI:
|
||||||
TextInfo = tr("Download paused because wifi is unavailable");
|
TextString = tr("Download paused because wifi is unavailable");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_WIFI_DISABLED:
|
case STRING_PAUSED_WIFI_DISABLED:
|
||||||
TextInfo = tr("Download paused because wifi is disabled");
|
TextString = tr("Download paused because wifi is disabled");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_ROAMING:
|
case STRING_PAUSED_ROAMING:
|
||||||
TextInfo = tr("Download paused because you are roaming");
|
TextString = tr("Download paused because you are roaming");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_NETWORK_SETUP_FAILURE:
|
case STRING_PAUSED_NETWORK_SETUP_FAILURE:
|
||||||
TextInfo = tr("Download paused. Test a website in browser");
|
TextString = tr("Download paused. Test a website in browser");
|
||||||
break;
|
break;
|
||||||
case STATE_PAUSED_SDCARD_UNAVAILABLE:
|
case STRING_PAUSED_SDCARD_UNAVAILABLE:
|
||||||
TextInfo = tr("Download paused because the external storage is unavailable");
|
TextString = tr("Download paused because the external storage is unavailable");
|
||||||
break;
|
break;
|
||||||
case STATE_FAILED_UNLICENSED:
|
case STRING_FAILED_UNLICENSED:
|
||||||
TextInfo = tr("Download failed because you may not have purchased this app");
|
TextString = tr("Download failed because you may not have purchased this app");
|
||||||
break;
|
break;
|
||||||
case STATE_FAILED_FETCHING_URL:
|
case STRING_FAILED_FETCHING_URL:
|
||||||
TextInfo = tr("Download failed because the resources could not be found");
|
TextString = tr("Download failed because the resources could not be found");
|
||||||
break;
|
break;
|
||||||
case STATE_FAILED_SDCARD_FULL:
|
case STRING_FAILED_SDCARD_FULL:
|
||||||
TextInfo = tr("Download failed because the external storage is full");
|
TextString = tr("Download failed because the external storage is full");
|
||||||
break;
|
break;
|
||||||
case STATE_FAILED_CANCELED:
|
case STRING_FAILED_CANCELED:
|
||||||
TextInfo = tr("Download cancelled");
|
TextString = tr("Download cancelled");
|
||||||
break;
|
break;
|
||||||
case STATE_UNKNOWN:
|
case STRING_FAILED:
|
||||||
TextInfo = tr("Starting...");
|
TextString = tr("Download failed");
|
||||||
break;
|
break;
|
||||||
case STATE_DOWNLOADING_TIME_LEFT:
|
case STRING_UNKNOWN:
|
||||||
TextInfo = tr("Time left");
|
TextString = tr("Unknown error");
|
||||||
|
break;
|
||||||
|
case STRING_TIME_LEFT:
|
||||||
|
TextString = tr("Time left");
|
||||||
|
break;
|
||||||
|
case STRING_NOTIFICATION_CHANNEL_NAME:
|
||||||
|
TextString = tr("App data download");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextInfo;
|
return TextString;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QtAndroidExtras>
|
#include <QtAndroidExtras>
|
||||||
@ -15,43 +38,45 @@ public:
|
|||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(ExpansionFileInfo)
|
Q_DECLARE_METATYPE(ExpansionFileInfo)
|
||||||
|
|
||||||
class QtAndroidApkExpansionFiles : public QObject
|
class QAndroidApkExpansionFiles : public QObject
|
||||||
{
|
{
|
||||||
Q_PROPERTY(ExpansionFileInfo main READ getMainExpansionFileInfo WRITE setMainExpansionFileInfo)
|
Q_PROPERTY(ExpansionFileInfo main READ getMainExpansionFileInfo WRITE setMainExpansionFileInfo)
|
||||||
Q_PROPERTY(ExpansionFileInfo patch READ getPatchExpansionFileInfo WRITE setPatchExpansionFileInfo)
|
Q_PROPERTY(ExpansionFileInfo patch READ getPatchExpansionFileInfo WRITE setPatchExpansionFileInfo)
|
||||||
Q_PROPERTY(QString base64PublicKey READ getBase64PublicKey WRITE setBase64PublicKey)
|
Q_PROPERTY(QString base64PublicKey READ getBase64PublicKey WRITE setBase64PublicKey)
|
||||||
Q_PROPERTY(QVector<int> salt READ getSALT WRITE setSALT)
|
Q_PROPERTY(QVector<int> salt READ getSALT WRITE setSALT)
|
||||||
Q_DISABLE_COPY(QtAndroidApkExpansionFiles)
|
Q_DISABLE_COPY(QAndroidApkExpansionFiles)
|
||||||
Q_ENUMS(DOWNLOAD_STATE)
|
Q_ENUMS(DOWNLOAD_STATE)
|
||||||
Q_ENUMS(APKEF_STATE)
|
Q_ENUMS(APKEF_STATE)
|
||||||
|
Q_ENUMS(REQUEST_ID)
|
||||||
|
Q_ENUMS(STRING_ID)
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
QtAndroidApkExpansionFiles();
|
QAndroidApkExpansionFiles();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
~QAndroidApkExpansionFiles();
|
||||||
|
|
||||||
enum DOWNLOAD_STATE
|
enum DOWNLOAD_STATE
|
||||||
{
|
{
|
||||||
STATE_IDLE = 1,
|
STATE_IDLE = 1,
|
||||||
STATE_FETCHING_URL = 2,
|
STATE_FETCHING_URL,
|
||||||
STATE_CONNECTING = 3,
|
STATE_CONNECTING,
|
||||||
STATE_DOWNLOADING = 4,
|
STATE_DOWNLOADING,
|
||||||
STATE_COMPLETED = 5,
|
STATE_COMPLETED,
|
||||||
STATE_PAUSED_NETWORK_UNAVAILABLE = 6,
|
STATE_PAUSED_NETWORK_UNAVAILABLE,
|
||||||
STATE_PAUSED_BY_REQUEST = 7,
|
STATE_PAUSED_BY_REQUEST,
|
||||||
STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8,
|
STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION,
|
||||||
STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9,
|
STATE_PAUSED_NEED_CELLULAR_PERMISSION,
|
||||||
STATE_PAUSED_WIFI_DISABLED = 10,
|
STATE_PAUSED_WIFI_DISABLED,
|
||||||
STATE_PAUSED_NEED_WIFI = 11,
|
STATE_PAUSED_NEED_WIFI,
|
||||||
STATE_PAUSED_ROAMING = 12,
|
STATE_PAUSED_ROAMING,
|
||||||
STATE_PAUSED_NETWORK_SETUP_FAILURE = 13,
|
STATE_PAUSED_NETWORK_SETUP_FAILURE,
|
||||||
STATE_PAUSED_SDCARD_UNAVAILABLE = 14,
|
STATE_PAUSED_SDCARD_UNAVAILABLE,
|
||||||
STATE_FAILED_UNLICENSED = 15,
|
STATE_FAILED_UNLICENSED,
|
||||||
STATE_FAILED_FETCHING_URL = 16,
|
STATE_FAILED_FETCHING_URL,
|
||||||
STATE_FAILED_SDCARD_FULL = 17,
|
STATE_FAILED_SDCARD_FULL,
|
||||||
STATE_FAILED_CANCELED = 18,
|
STATE_FAILED_CANCELED,
|
||||||
STATE_FAILED = 19,
|
STATE_FAILED
|
||||||
STATE_UNKNOWN = 20,
|
|
||||||
STATE_DOWNLOADING_TIME_LEFT = 21
|
|
||||||
};
|
};
|
||||||
enum APKEF_STATE
|
enum APKEF_STATE
|
||||||
{
|
{
|
||||||
@ -65,14 +90,47 @@ public:
|
|||||||
APKEF_INVALID_SALT,
|
APKEF_INVALID_SALT,
|
||||||
APKEF_UNKNOWN_ERROR
|
APKEF_UNKNOWN_ERROR
|
||||||
};
|
};
|
||||||
|
enum REQUEST_ID
|
||||||
|
{
|
||||||
|
REQUEST_ABORT_DOWNLOAD = 0,
|
||||||
|
REQUEST_PAUSE_DOWNLOAD,
|
||||||
|
REQUEST_CONTINUE_DOWNLOAD,
|
||||||
|
REQUEST_DOWNLOAD_STATUS
|
||||||
|
};
|
||||||
|
enum STRING_ID
|
||||||
|
{
|
||||||
|
STRING_IDLE = 0,
|
||||||
|
STRING_FETCHING_URL,
|
||||||
|
STRING_CONNECTING,
|
||||||
|
STRING_DOWNLOADING,
|
||||||
|
STRING_COMPLETED,
|
||||||
|
STRING_PAUSED_NETWORK_UNAVAILABLE,
|
||||||
|
STRING_PAUSED_BY_REQUEST,
|
||||||
|
STRING_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION,
|
||||||
|
STRING_PAUSED_NEED_CELLULAR_PERMISSION,
|
||||||
|
STRING_PAUSED_WIFI_DISABLED,
|
||||||
|
STRING_PAUSED_NEED_WIFI,
|
||||||
|
STRING_PAUSED_ROAMING,
|
||||||
|
STRING_PAUSED_NETWORK_SETUP_FAILURE,
|
||||||
|
STRING_PAUSED_SDCARD_UNAVAILABLE,
|
||||||
|
STRING_FAILED_UNLICENSED,
|
||||||
|
STRING_FAILED_FETCHING_URL,
|
||||||
|
STRING_FAILED_SDCARD_FULL,
|
||||||
|
STRING_FAILED_CANCELED,
|
||||||
|
STRING_FAILED,
|
||||||
|
STRING_UNKNOWN,
|
||||||
|
STRING_TIME_LEFT,
|
||||||
|
STRING_NOTIFICATION_CHANNEL_NAME
|
||||||
|
};
|
||||||
|
|
||||||
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
|
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
|
||||||
static QtAndroidApkExpansionFiles* instance();
|
static QAndroidApkExpansionFiles* instance();
|
||||||
|
|
||||||
Q_INVOKABLE QString mainFileName();
|
Q_INVOKABLE QString mainFileName();
|
||||||
Q_INVOKABLE QString patchFileName();
|
Q_INVOKABLE QString patchFileName();
|
||||||
Q_INVOKABLE APKEF_STATE startDownloadFiles();
|
Q_INVOKABLE APKEF_STATE startDownloadFiles();
|
||||||
Q_INVOKABLE QString stateTextInfo(int state);
|
Q_INVOKABLE QString getString(int stringID);
|
||||||
|
Q_INVOKABLE void sendRequest(REQUEST_ID requestID);
|
||||||
|
|
||||||
const QString& getBase64PublicKey() const;
|
const QString& getBase64PublicKey() const;
|
||||||
void setBase64PublicKey(const QString &Base64PublicKey);
|
void setBase64PublicKey(const QString &Base64PublicKey);
|
||||||
@ -92,8 +150,17 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const QAndroidJniObject m_JavaApkExpansionDownloader;
|
const QAndroidJniObject m_JavaApkExpansionDownloader;
|
||||||
static QtAndroidApkExpansionFiles *m_pInstance;
|
static QAndroidApkExpansionFiles *m_pInstance;
|
||||||
ExpansionFileInfo m_ExpansionsFileInfo[2];
|
ExpansionFileInfo m_ExpansionsFileInfo[2];
|
||||||
QString m_Base64PublicKey;
|
QString m_Base64PublicKey;
|
||||||
QVector<int> m_SALT;
|
QVector<int> m_SALT;
|
||||||
|
|
||||||
|
enum APP_STATE
|
||||||
|
{
|
||||||
|
APP_STATE_CREATE = 0,
|
||||||
|
APP_STATE_START = 1,
|
||||||
|
APP_STATE_STOP = 2,
|
||||||
|
APP_STATE_DESTROY = 3
|
||||||
|
};
|
||||||
|
void SetNewAppState(APP_STATE NewState);
|
||||||
};
|
};
|
||||||
|
@ -1,41 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "QAndroidAppPermissions.h"
|
||||||
|
|
||||||
#include "QtAndroidAppPermissions.h"
|
QAndroidAppPermissions::QAndroidAppPermissions()
|
||||||
|
|
||||||
QtAndroidAppPermissions::QtAndroidAppPermissions()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject* QtAndroidAppPermissions::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
|
QObject* QAndroidAppPermissions::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||||
{
|
{
|
||||||
Q_UNUSED(engine);
|
Q_UNUSED(engine);
|
||||||
Q_UNUSED(scriptEngine);
|
Q_UNUSED(scriptEngine);
|
||||||
|
|
||||||
return new QtAndroidAppPermissions();
|
return new QAndroidAppPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtAndroidAppPermissions::requestPermissions(const QStringList &permissionsNameList)
|
void QAndroidAppPermissions::requestPermissions(const QStringList &permissionsNameList)
|
||||||
{
|
|
||||||
for(int i = 0; i < permissionsNameList.count(); i++)
|
|
||||||
{
|
|
||||||
requestPermission(permissionsNameList[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QtAndroidAppPermissions::requestPermission(const QString &permissionName)
|
|
||||||
{
|
{
|
||||||
if(QtAndroid::androidSdkVersion() >= 23)
|
if(QtAndroid::androidSdkVersion() >= 23)
|
||||||
{
|
{
|
||||||
if(QtAndroid::checkPermission(permissionName) != QtAndroid::PermissionResult::Granted)
|
QStringList PermissionsNotGrantedList;
|
||||||
|
|
||||||
|
for(int i = 0; i < permissionsNameList.count(); i++)
|
||||||
{
|
{
|
||||||
QtAndroid::requestPermissions(QStringList() << permissionName, std::bind(&QtAndroidAppPermissions::RequestPermissionResults, this, std::placeholders::_1));
|
if(QtAndroid::checkPermission(permissionsNameList[i]) != QtAndroid::PermissionResult::Granted)
|
||||||
return;
|
{
|
||||||
|
PermissionsNotGrantedList << permissionsNameList[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emit requestPermissionsResults(permissionsNameList[i], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(PermissionsNotGrantedList.count() > 0)
|
||||||
|
{
|
||||||
|
QtAndroid::requestPermissions(PermissionsNotGrantedList, std::bind(&QAndroidAppPermissions::RequestPermissionResults, this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for(int i = 0; i < permissionsNameList.count(); i++)
|
||||||
|
{
|
||||||
|
emit requestPermissionsResults(permissionsNameList[i], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit requestPermissionsResults(permissionName, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QtAndroidAppPermissions::shouldShowRequestPermissionInfo(const QString &permissionName)
|
void QAndroidAppPermissions::requestPermission(const QString &permissionName)
|
||||||
|
{
|
||||||
|
if(QtAndroid::androidSdkVersion() >= 23 && QtAndroid::checkPermission(permissionName) != QtAndroid::PermissionResult::Granted)
|
||||||
|
{
|
||||||
|
QtAndroid::requestPermissions(QStringList() << permissionName, std::bind(&QAndroidAppPermissions::RequestPermissionResults, this, std::placeholders::_1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emit requestPermissionsResults(permissionName, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QAndroidAppPermissions::shouldShowRequestPermissionInfo(const QString &permissionName)
|
||||||
{
|
{
|
||||||
if(QtAndroid::androidSdkVersion() >= 23)
|
if(QtAndroid::androidSdkVersion() >= 23)
|
||||||
{
|
{
|
||||||
@ -45,7 +89,7 @@ bool QtAndroidAppPermissions::shouldShowRequestPermissionInfo(const QString &per
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtAndroidAppPermissions::RequestPermissionResults(const QtAndroid::PermissionResultMap &ResultMap)
|
void QAndroidAppPermissions::RequestPermissionResults(const QtAndroid::PermissionResultMap &ResultMap)
|
||||||
{
|
{
|
||||||
if(ResultMap.count() > 0)
|
if(ResultMap.count() > 0)
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QtAndroidExtras>
|
#include <QtAndroidExtras>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
class QtAndroidAppPermissions : public QObject
|
class QAndroidAppPermissions : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
QtAndroidAppPermissions();
|
QAndroidAppPermissions();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
|
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);
|
||||||
|
@ -1,14 +1,31 @@
|
|||||||
package com.falsinsoft.QtAndroidTools;
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2018 Fabio Falsini <falsinsoft@gmail.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
import com.google.android.vending.expansion.downloader.Constants;
|
package com.falsinsoft.qtandroidtools;
|
||||||
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
|
|
||||||
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
|
import com.google.android.vending.expansion.downloader.*;
|
||||||
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
|
import com.google.android.vending.expansion.downloader.impl.*;
|
||||||
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.expansion.downloader.impl.DownloaderService;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -20,87 +37,124 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class ApkExpansionDownloader implements IDownloaderClient
|
public class ApkExpansionDownloader
|
||||||
{
|
{
|
||||||
private final Activity m_ActivityInstance;
|
private final String NOTIFICATION_CHANNEL_ID;
|
||||||
private IDownloaderService m_RemoteService;
|
private final DownloaderClient mDownloaderClient;
|
||||||
private IStub m_DownloaderClientStub;
|
private final DownloaderProxy mDownloaderProxy;
|
||||||
|
private final Activity mActivityInstance;
|
||||||
|
|
||||||
public ApkExpansionDownloader(Activity ActivityInstance)
|
public ApkExpansionDownloader(Activity ActivityInstance)
|
||||||
{
|
{
|
||||||
final IDownloaderClient Client = this;
|
NOTIFICATION_CHANNEL_ID = ActivityInstance.getClass().getName();
|
||||||
ActivityInstance.runOnUiThread(new Runnable()
|
mDownloaderClient = new DownloaderClient();
|
||||||
{
|
mDownloaderProxy = new DownloaderProxy(ActivityInstance);
|
||||||
public void run()
|
mActivityInstance = ActivityInstance;
|
||||||
{
|
|
||||||
m_DownloaderClientStub = DownloaderClientMarshaller.CreateStub(Client, ApkExpansionDownloaderService.class);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
m_ActivityInstance = ActivityInstance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAPKFileDelivered(boolean IsMain, int FileVersion, int FileSize)
|
public boolean isAPKFileDelivered(boolean IsMain, int FileVersion, int FileSize)
|
||||||
{
|
{
|
||||||
final String FileName = Helpers.getExpansionAPKFileName(m_ActivityInstance, IsMain, FileVersion);
|
final String FileName = Helpers.getExpansionAPKFileName(mActivityInstance, IsMain, FileVersion);
|
||||||
return Helpers.doesFileExist(m_ActivityInstance, FileName, FileSize, false);
|
return Helpers.doesFileExist(mActivityInstance, FileName, FileSize, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getExpansionAPKFileName(boolean IsMain, int FileVersion)
|
public String getExpansionAPKFileName(boolean IsMain, int FileVersion)
|
||||||
{
|
{
|
||||||
final String FileName = Helpers.getExpansionAPKFileName(m_ActivityInstance, IsMain, FileVersion);
|
final String FileName = Helpers.getExpansionAPKFileName(mActivityInstance, IsMain, FileVersion);
|
||||||
return Helpers.generateSaveFileName(m_ActivityInstance, FileName);
|
return Helpers.generateSaveFileName(mActivityInstance, FileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableClientStubConnection(boolean ConnectionEnabled)
|
public void sendRequest(int requestID)
|
||||||
{
|
{
|
||||||
if(ConnectionEnabled == true)
|
mDownloaderProxy.connect();
|
||||||
m_DownloaderClientStub.connect(m_ActivityInstance);
|
|
||||||
else
|
switch(requestID)
|
||||||
m_DownloaderClientStub.disconnect(m_ActivityInstance);
|
{
|
||||||
|
case REQUEST_ABORT_DOWNLOAD:
|
||||||
|
mDownloaderProxy.requestAbortDownload();
|
||||||
|
break;
|
||||||
|
case REQUEST_PAUSE_DOWNLOAD:
|
||||||
|
mDownloaderProxy.requestPauseDownload();
|
||||||
|
break;
|
||||||
|
case REQUEST_CONTINUE_DOWNLOAD:
|
||||||
|
mDownloaderProxy.requestContinueDownload();
|
||||||
|
break;
|
||||||
|
case REQUEST_DOWNLOAD_STATUS:
|
||||||
|
mDownloaderProxy.requestDownloadStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDownloaderProxy.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appStateChanged(int newState)
|
||||||
|
{
|
||||||
|
switch(newState)
|
||||||
|
{
|
||||||
|
case APP_STATE_CREATE:
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||||
|
{
|
||||||
|
NotificationManager Manager = mActivityInstance.getSystemService(NotificationManager.class);
|
||||||
|
NotificationChannel Channel;
|
||||||
|
|
||||||
|
Channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
|
||||||
|
getString(STRING_NOTIFICATION_CHANNEL_NAME),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
);
|
||||||
|
|
||||||
|
Manager.createNotificationChannel(Channel);
|
||||||
|
}
|
||||||
|
mDownloaderClient.register(mActivityInstance);
|
||||||
|
break;
|
||||||
|
case APP_STATE_START:
|
||||||
|
mDownloaderClient.register(mActivityInstance);
|
||||||
|
break;
|
||||||
|
case APP_STATE_STOP:
|
||||||
|
mDownloaderClient.unregister(mActivityInstance);
|
||||||
|
break;
|
||||||
|
case APP_STATE_DESTROY:
|
||||||
|
/*
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||||
|
{
|
||||||
|
NotificationManager Manager = mActivityInstance.getSystemService(NotificationManager.class);
|
||||||
|
Manager.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
mDownloaderClient.unregister(mActivityInstance);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int startDownloader(String BASE64_PUBLIC_KEY, byte[] SALT)
|
public int startDownloader(String BASE64_PUBLIC_KEY, byte[] SALT)
|
||||||
{
|
{
|
||||||
int DownloadResult = -1;
|
int DownloadResult = -1;
|
||||||
|
|
||||||
ApkExpansionDownloaderService.BASE64_PUBLIC_KEY = BASE64_PUBLIC_KEY;
|
|
||||||
ApkExpansionDownloaderService.SALT = SALT;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Intent LaunchIntent, IntentToLaunchThisActivityFromNotification;
|
Intent IntentToLaunchThisActivityFromNotification;
|
||||||
PendingIntent PendingActivity;
|
PendingIntent PendingActivity;
|
||||||
|
|
||||||
LaunchIntent = m_ActivityInstance.getIntent();
|
IntentToLaunchThisActivityFromNotification = new Intent(mActivityInstance, mActivityInstance.getClass());
|
||||||
|
|
||||||
IntentToLaunchThisActivityFromNotification = new Intent(m_ActivityInstance, m_ActivityInstance.getClass());
|
|
||||||
IntentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
IntentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
IntentToLaunchThisActivityFromNotification.setAction(LaunchIntent.getAction());
|
|
||||||
|
|
||||||
|
PendingActivity = PendingIntent.getActivity(mActivityInstance,
|
||||||
if(LaunchIntent.getCategories() != null)
|
|
||||||
{
|
|
||||||
for(String Category : LaunchIntent.getCategories())
|
|
||||||
{
|
|
||||||
IntentToLaunchThisActivityFromNotification.addCategory(Category);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingActivity = PendingIntent.getActivity(m_ActivityInstance,
|
|
||||||
0,
|
0,
|
||||||
IntentToLaunchThisActivityFromNotification,
|
IntentToLaunchThisActivityFromNotification,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
);
|
);
|
||||||
|
|
||||||
DownloadResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(m_ActivityInstance, PendingActivity, ApkExpansionDownloaderService.class);
|
DownloadResult = DownloaderService.startDownloadServiceIfRequired(mActivityInstance,
|
||||||
|
NOTIFICATION_CHANNEL_ID,
|
||||||
if(DownloadResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED)
|
PendingActivity,
|
||||||
{
|
SALT,
|
||||||
enableClientStubConnection(true);
|
BASE64_PUBLIC_KEY
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
catch(NameNotFoundException e)
|
catch(NameNotFoundException e)
|
||||||
{
|
{
|
||||||
@ -110,26 +164,56 @@ public class ApkExpansionDownloader implements IDownloaderClient
|
|||||||
return DownloadResult;
|
return DownloadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private class DownloaderClient extends BroadcastDownloaderClient
|
||||||
public void onServiceConnected(Messenger m)
|
|
||||||
{
|
{
|
||||||
m_RemoteService = DownloaderServiceMarshaller.CreateProxy(m);
|
@Override
|
||||||
m_RemoteService.onClientUpdated(m_DownloaderClientStub.getMessenger());
|
public void onDownloadStateChanged(int newState)
|
||||||
}
|
{
|
||||||
|
downloadStateChanged(newState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDownloadStateChanged(int newState)
|
public void onDownloadProgress(DownloadProgressInfo progress)
|
||||||
{
|
{
|
||||||
downloadStateChanged(newState);
|
downloadProgress(progress.mOverallTotal, progress.mOverallProgress, progress.mTimeRemaining, progress.mCurrentSpeed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final int STRING_IDLE = 0;
|
||||||
|
public static final int STRING_FETCHING_URL = 1;
|
||||||
|
public static final int STRING_CONNECTING = 2;
|
||||||
|
public static final int STRING_DOWNLOADING = 3;
|
||||||
|
public static final int STRING_COMPLETED = 4;
|
||||||
|
public static final int STRING_PAUSED_NETWORK_UNAVAILABLE = 5;
|
||||||
|
public static final int STRING_PAUSED_BY_REQUEST = 6;
|
||||||
|
public static final int STRING_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 7;
|
||||||
|
public static final int STRING_PAUSED_NEED_CELLULAR_PERMISSION = 8;
|
||||||
|
public static final int STRING_PAUSED_WIFI_DISABLED = 9;
|
||||||
|
public static final int STRING_PAUSED_NEED_WIFI = 10;
|
||||||
|
public static final int STRING_PAUSED_ROAMING = 11;
|
||||||
|
public static final int STRING_PAUSED_NETWORK_SETUP_FAILURE = 12;
|
||||||
|
public static final int STRING_PAUSED_SDCARD_UNAVAILABLE = 13;
|
||||||
|
public static final int STRING_FAILED_UNLICENSED = 14;
|
||||||
|
public static final int STRING_FAILED_FETCHING_URL = 15;
|
||||||
|
public static final int STRING_FAILED_SDCARD_FULL = 16;
|
||||||
|
public static final int STRING_FAILED_CANCELED = 17;
|
||||||
|
public static final int STRING_FAILED = 18;
|
||||||
|
public static final int STRING_UNKNOWN = 19;
|
||||||
|
public static final int STRING_TIME_LEFT = 20;
|
||||||
|
public static final int STRING_NOTIFICATION_CHANNEL_NAME = 21;
|
||||||
|
|
||||||
@Override
|
private static final int APP_STATE_CREATE = 0;
|
||||||
public void onDownloadProgress(DownloadProgressInfo progress)
|
private static final int APP_STATE_START = 1;
|
||||||
{
|
private static final int APP_STATE_STOP = 2;
|
||||||
downloadProgress(progress.mOverallTotal, progress.mOverallProgress, progress.mTimeRemaining, progress.mCurrentSpeed);
|
private static final int APP_STATE_DESTROY = 3;
|
||||||
}
|
|
||||||
|
private static final int REQUEST_ABORT_DOWNLOAD = 0;
|
||||||
|
private static final int REQUEST_PAUSE_DOWNLOAD = 1;
|
||||||
|
private static final int REQUEST_CONTINUE_DOWNLOAD = 2;
|
||||||
|
private static final int REQUEST_DOWNLOAD_STATUS = 3;
|
||||||
|
|
||||||
private static native void downloadStateChanged(int newState);
|
private static native void downloadStateChanged(int newState);
|
||||||
private static native void downloadProgress(long overallTotal, long overallProgress, long timeRemaining, float currentSpeed);
|
private static native void downloadProgress(long overallTotal, long overallProgress, long timeRemaining, float currentSpeed);
|
||||||
|
public static native String getString(int stringID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package com.falsinsoft.QtAndroidTools;
|
|
||||||
|
|
||||||
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
|
|
||||||
public class ApkExpansionDownloaderAlarmReceiver extends BroadcastReceiver
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, ApkExpansionDownloaderService.class);
|
|
||||||
}
|
|
||||||
catch (NameNotFoundException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package com.falsinsoft.QtAndroidTools;
|
|
||||||
|
|
||||||
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
|
|
||||||
|
|
||||||
public class ApkExpansionDownloaderService extends DownloaderService
|
|
||||||
{
|
|
||||||
public static String BASE64_PUBLIC_KEY = null;
|
|
||||||
public static byte[] SALT = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPublicKey()
|
|
||||||
{
|
|
||||||
return BASE64_PUBLIC_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSALT()
|
|
||||||
{
|
|
||||||
return SALT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAlarmReceiverClassName()
|
|
||||||
{
|
|
||||||
return ApkExpansionDownloaderAlarmReceiver.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,277 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -22,8 +22,12 @@ import android.os.Build;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.StatFs;
|
import android.os.StatFs;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
//import com.android.vending.expansion.downloader.R;
|
||||||
|
import com.falsinsoft.qtandroidtools.ApkExpansionDownloader;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -266,7 +270,7 @@ public class Helpers {
|
|||||||
*
|
*
|
||||||
* @param c the app/activity/service context
|
* @param c the app/activity/service context
|
||||||
* @param fileName the name (sans path) of the file to query
|
* @param fileName the name (sans path) of the file to query
|
||||||
* @return true if it does exist, false otherwise
|
* @return value representing whether the file exists and is readable
|
||||||
*/
|
*/
|
||||||
static public int getFileStatus(Context c, String fileName) {
|
static public int getFileStatus(Context c, String fileName) {
|
||||||
// the file may have been delivered by Play --- let's make sure
|
// the file may have been delivered by Play --- let's make sure
|
||||||
@ -312,5 +316,48 @@ public class Helpers {
|
|||||||
* @param state One of the STATE_* constants from {@link IDownloaderClient}.
|
* @param state One of the STATE_* constants from {@link IDownloaderClient}.
|
||||||
* @return string resource ID for the corresponding string.
|
* @return string resource ID for the corresponding string.
|
||||||
*/
|
*/
|
||||||
public static native String getDownloaderStringResourceFromState(int state);
|
@StringRes
|
||||||
|
static public int getDownloaderStringResourceIDFromState(int state) {
|
||||||
|
switch (state) {
|
||||||
|
case IDownloaderClient.STATE_IDLE:
|
||||||
|
return ApkExpansionDownloader.STRING_IDLE;
|
||||||
|
case IDownloaderClient.STATE_FETCHING_URL:
|
||||||
|
return ApkExpansionDownloader.STRING_FETCHING_URL;
|
||||||
|
case IDownloaderClient.STATE_CONNECTING:
|
||||||
|
return ApkExpansionDownloader.STRING_CONNECTING;
|
||||||
|
case IDownloaderClient.STATE_DOWNLOADING:
|
||||||
|
return ApkExpansionDownloader.STRING_DOWNLOADING;
|
||||||
|
case IDownloaderClient.STATE_COMPLETED:
|
||||||
|
return ApkExpansionDownloader.STRING_COMPLETED;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_NETWORK_UNAVAILABLE:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_NETWORK_UNAVAILABLE;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_BY_REQUEST;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_NEED_CELLULAR_PERMISSION;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_WIFI_DISABLED;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_NEED_WIFI:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_NEED_WIFI;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_ROAMING:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_ROAMING;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_NETWORK_SETUP_FAILURE:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_NETWORK_SETUP_FAILURE;
|
||||||
|
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
|
||||||
|
return ApkExpansionDownloader.STRING_PAUSED_SDCARD_UNAVAILABLE;
|
||||||
|
case IDownloaderClient.STATE_FAILED_UNLICENSED:
|
||||||
|
return ApkExpansionDownloader.STRING_FAILED_UNLICENSED;
|
||||||
|
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
|
||||||
|
return ApkExpansionDownloader.STRING_FAILED_FETCHING_URL;
|
||||||
|
case IDownloaderClient.STATE_FAILED_SDCARD_FULL:
|
||||||
|
return ApkExpansionDownloader.STRING_FAILED_SDCARD_FULL;
|
||||||
|
case IDownloaderClient.STATE_FAILED_CANCELED:
|
||||||
|
return ApkExpansionDownloader.STRING_FAILED_CANCELED;
|
||||||
|
default:
|
||||||
|
return ApkExpansionDownloader.STRING_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package com.google.android.vending.expansion.downloader;
|
package com.google.android.vending.expansion.downloader;
|
||||||
|
|
||||||
import android.os.Messenger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface should be implemented by the client activity for the
|
* This interface should be implemented by the client activity for the
|
||||||
* downloader. It is used to pass status from the service to the client.
|
* downloader. It is used to pass status from the service to the client.
|
||||||
@ -72,28 +70,6 @@ public interface IDownloaderClient {
|
|||||||
static final int STATE_FAILED_CANCELED = 18;
|
static final int STATE_FAILED_CANCELED = 18;
|
||||||
|
|
||||||
static final int STATE_FAILED = 19;
|
static final int STATE_FAILED = 19;
|
||||||
|
|
||||||
static final int STATE_UNKNOWN = 20;
|
|
||||||
static final int STATE_DOWNLOADING_TIME_LEFT = 21;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Called when the download state changes. Depending on the state, there may
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package com.google.android.vending.expansion.downloader;
|
package com.google.android.vending.expansion.downloader;
|
||||||
|
|
||||||
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
|
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
|
||||||
import android.os.Messenger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface is implemented by the DownloaderService and by the
|
* This interface is implemented by the DownloaderService and by the
|
||||||
@ -70,14 +69,4 @@ public interface IDownloaderService {
|
|||||||
* Requests that the download status be sent to the client.
|
* Requests that the download status be sent to the client.
|
||||||
*/
|
*/
|
||||||
void requestDownloadStatus();
|
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);
|
|
||||||
}
|
}
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.google.android.vending.expansion.downloader.impl;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
|
||||||
|
import com.google.android.vending.expansion.downloader.IDownloaderClient;
|
||||||
|
import com.google.android.vending.expansion.downloader.IDownloaderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used by client to receive download events. The current
|
||||||
|
* implementation uses {@link LocalBroadcastManager}, so client should
|
||||||
|
* subscribe for the updates within the application context.
|
||||||
|
*
|
||||||
|
* <br /> <br />
|
||||||
|
*
|
||||||
|
* <b>Note:</b> You should preferably register the receiver through the
|
||||||
|
* methods {@link #register(Context)} and {@link #unregister(Context)}.
|
||||||
|
* The broadcast is not sticky, so client should use
|
||||||
|
* {@link IDownloaderService#requestDownloadStatus()} to receive the
|
||||||
|
* current download status immediately (see {@link DownloaderProxy}).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @since 5.0.0
|
||||||
|
*/
|
||||||
|
public abstract class BroadcastDownloaderClient extends BroadcastReceiver implements IDownloaderClient {
|
||||||
|
|
||||||
|
public static final String ACTION_STATE_CHANGED = "com.google.android.vending.expansion.downloader.ACTION_STATE_CHANGED";
|
||||||
|
public static final String ACTION_PROGRESS = "com.google.android.vending.expansion.downloader.ACTION_PROGRESS";
|
||||||
|
|
||||||
|
public static final String EXTRA_NEW_STATE = "newState";
|
||||||
|
public static final String EXTRA_PROGRESS = "progress";
|
||||||
|
|
||||||
|
public void register(Context context) {
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_STATE_CHANGED);
|
||||||
|
filter.addAction(ACTION_PROGRESS);
|
||||||
|
LocalBroadcastManager.getInstance(context).registerReceiver(this, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister(Context context) {
|
||||||
|
LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onReceive(Context context, Intent intent) {
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case ACTION_STATE_CHANGED:
|
||||||
|
int state = intent.getIntExtra(EXTRA_NEW_STATE, -1);
|
||||||
|
onDownloadStateChanged(state);
|
||||||
|
break;
|
||||||
|
case ACTION_PROGRESS:
|
||||||
|
DownloadProgressInfo info = intent.getParcelableExtra(EXTRA_PROGRESS);
|
||||||
|
onDownloadProgress(info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.google.android.vending.expansion.downloader.impl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
|
||||||
|
import com.google.android.vending.expansion.downloader.IDownloaderClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal class used for communication with client. In current
|
||||||
|
* implementation the communication is done via {@link LocalBroadcastManager}.
|
||||||
|
* Commands are received by the {@link BroadcastDownloaderClient}.
|
||||||
|
*/
|
||||||
|
class ClientProxy implements IDownloaderClient {
|
||||||
|
|
||||||
|
private LocalBroadcastManager mBroadcastManager;
|
||||||
|
|
||||||
|
ClientProxy(Context ctx) {
|
||||||
|
mBroadcastManager = LocalBroadcastManager.getInstance(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownloadStateChanged(int newState) {
|
||||||
|
Bundle params = new Bundle(1);
|
||||||
|
params.putInt(BroadcastDownloaderClient.EXTRA_NEW_STATE, newState);
|
||||||
|
send(BroadcastDownloaderClient.ACTION_STATE_CHANGED, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDownloadProgress(DownloadProgressInfo progress) {
|
||||||
|
Bundle params = new Bundle(1);
|
||||||
|
params.putParcelable(BroadcastDownloaderClient.EXTRA_PROGRESS, progress);
|
||||||
|
send(BroadcastDownloaderClient.ACTION_PROGRESS, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(String action, Bundle params) {
|
||||||
|
Intent intent = new Intent(action);
|
||||||
|
intent.putExtras(params);
|
||||||
|
mBroadcastManager.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
}
|
@ -18,20 +18,16 @@ package com.google.android.vending.expansion.downloader.impl;
|
|||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Handler;
|
import android.os.*;
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.Message;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service differs from IntentService in a few minor ways/ It will not
|
* 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"
|
* 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
|
* in shouldStop(). 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.
|
* intent, it does not queue up batches of intents of the same type.
|
||||||
*/
|
*/
|
||||||
public abstract class CustomIntentService extends Service {
|
abstract class CustomIntentService extends Service {
|
||||||
private String mName;
|
private String mName;
|
||||||
private boolean mRedelivery;
|
private boolean mRedelivery;
|
||||||
private volatile ServiceHandler mServiceHandler;
|
private volatile ServiceHandler mServiceHandler;
|
||||||
|
@ -16,15 +16,14 @@
|
|||||||
|
|
||||||
package com.google.android.vending.expansion.downloader.impl;
|
package com.google.android.vending.expansion.downloader.impl;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import com.google.android.vending.expansion.downloader.Constants;
|
import com.google.android.vending.expansion.downloader.Constants;
|
||||||
import com.google.android.vending.expansion.downloader.Helpers;
|
import com.google.android.vending.expansion.downloader.Helpers;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of information about an individual download from the database.
|
* Representation of information about an individual download from the database.
|
||||||
*/
|
*/
|
||||||
public class DownloadInfo {
|
class DownloadInfo {
|
||||||
public String mUri;
|
public String mUri;
|
||||||
public final int mIndex;
|
public final int mIndex;
|
||||||
public final String mFileName;
|
public final String mFileName;
|
||||||
|
@ -16,18 +16,19 @@
|
|||||||
|
|
||||||
package com.google.android.vending.expansion.downloader.impl;
|
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.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.support.annotation.StringRes;
|
||||||
import android.os.Messenger;
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
//import com.android.vending.expansion.downloader.R;
|
||||||
|
import com.falsinsoft.qtandroidtools.ApkExpansionDownloader;
|
||||||
|
|
||||||
|
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
|
||||||
|
import com.google.android.vending.expansion.downloader.Helpers;
|
||||||
|
import com.google.android.vending.expansion.downloader.IDownloaderClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class handles displaying the notification associated with the download
|
* This class handles displaying the notification associated with the download
|
||||||
* queue going on in the download manager. It handles multiple status types;
|
* queue going on in the download manager. It handles multiple status types;
|
||||||
@ -39,155 +40,24 @@ import android.support.v4.app.NotificationCompat;
|
|||||||
* The application interface for the downloader also needs to understand and
|
* The application interface for the downloader also needs to understand and
|
||||||
* handle these transient states.
|
* handle these transient states.
|
||||||
*/
|
*/
|
||||||
public class DownloadNotification implements IDownloaderClient {
|
class DownloadNotification {
|
||||||
|
|
||||||
private int mState;
|
private int mState;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final NotificationManager mNotificationManager;
|
private final NotificationManager mNotificationManager;
|
||||||
private CharSequence mCurrentTitle;
|
private final IDownloaderClient mClientProxy;
|
||||||
|
|
||||||
private IDownloaderClient mClientProxy;
|
private CharSequence mCurrentTitle;
|
||||||
private NotificationCompat.Builder mActiveDownloadBuilder;
|
private NotificationCompat.Builder mActiveDownloadBuilder;
|
||||||
private NotificationCompat.Builder mBuilder;
|
private NotificationCompat.Builder mBuilder;
|
||||||
private NotificationCompat.Builder mCurrentBuilder;
|
private NotificationCompat.Builder mCurrentBuilder;
|
||||||
private CharSequence mLabel;
|
private CharSequence mLabel;
|
||||||
private String mCurrentText;
|
private String mCurrentText;
|
||||||
private DownloadProgressInfo mProgressInfo;
|
|
||||||
private PendingIntent mContentIntent;
|
private PendingIntent mContentIntent;
|
||||||
|
|
||||||
static final String LOGTAG = "DownloadNotification";
|
static final String LOGTAG = "DownloadNotification";
|
||||||
static final int NOTIFICATION_ID = LOGTAG.hashCode();
|
static final int NOTIFICATION_ID = LOGTAG.hashCode();
|
||||||
|
|
||||||
public PendingIntent getClientIntent() {
|
|
||||||
return mContentIntent;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 iconResource;
|
|
||||||
boolean ongoingEvent;
|
|
||||||
|
|
||||||
// get the new title string and paused text
|
|
||||||
switch (newState) {
|
|
||||||
case 0:
|
|
||||||
iconResource = android.R.drawable.stat_sys_warning;
|
|
||||||
mCurrentText = Helpers.getDownloaderStringResourceFromState(IDownloaderClient.STATE_UNKNOWN);
|
|
||||||
ongoingEvent = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IDownloaderClient.STATE_DOWNLOADING:
|
|
||||||
iconResource = android.R.drawable.stat_sys_download;
|
|
||||||
mCurrentText = Helpers.getDownloaderStringResourceFromState(newState);
|
|
||||||
ongoingEvent = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IDownloaderClient.STATE_FETCHING_URL:
|
|
||||||
case IDownloaderClient.STATE_CONNECTING:
|
|
||||||
iconResource = android.R.drawable.stat_sys_download_done;
|
|
||||||
mCurrentText = Helpers.getDownloaderStringResourceFromState(newState);
|
|
||||||
ongoingEvent = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IDownloaderClient.STATE_COMPLETED:
|
|
||||||
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
|
|
||||||
iconResource = android.R.drawable.stat_sys_download_done;
|
|
||||||
mCurrentText = Helpers.getDownloaderStringResourceFromState(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;
|
|
||||||
mCurrentText = Helpers.getDownloaderStringResourceFromState(newState);
|
|
||||||
ongoingEvent = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
iconResource = android.R.drawable.stat_sys_warning;
|
|
||||||
mCurrentText = Helpers.getDownloaderStringResourceFromState(newState);
|
|
||||||
ongoingEvent = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentTitle = mLabel;
|
|
||||||
mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText);
|
|
||||||
mCurrentBuilder.setSmallIcon(iconResource);
|
|
||||||
mCurrentBuilder.setContentTitle(mCurrentTitle);
|
|
||||||
mCurrentBuilder.setContentText(mCurrentText);
|
|
||||||
if (ongoingEvent) {
|
|
||||||
mCurrentBuilder.setOngoing(true);
|
|
||||||
} else {
|
|
||||||
mCurrentBuilder.setOngoing(false);
|
|
||||||
mCurrentBuilder.setAutoCancel(true);
|
|
||||||
}
|
|
||||||
mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
mBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
|
|
||||||
mBuilder.setContentTitle(mCurrentTitle);
|
|
||||||
mBuilder.setContentText(mCurrentText);
|
|
||||||
mCurrentBuilder = mBuilder;
|
|
||||||
} else {
|
|
||||||
mActiveDownloadBuilder.setProgress((int) progress.mOverallTotal, (int) progress.mOverallProgress, false);
|
|
||||||
mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
|
|
||||||
mActiveDownloadBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
|
|
||||||
mActiveDownloadBuilder.setTicker(mLabel + ": " + mCurrentText);
|
|
||||||
mActiveDownloadBuilder.setContentTitle(mLabel);
|
|
||||||
mActiveDownloadBuilder.setContentInfo(Helpers.getDownloaderStringResourceFromState(IDownloaderClient.STATE_DOWNLOADING_TIME_LEFT) + ": " + Helpers.getTimeRemaining(progress.mTimeRemaining));
|
|
||||||
mCurrentBuilder = mActiveDownloadBuilder;
|
|
||||||
}
|
|
||||||
mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -200,6 +70,7 @@ public class DownloadNotification implements IDownloaderClient {
|
|||||||
mLabel = applicationLabel;
|
mLabel = applicationLabel;
|
||||||
mNotificationManager = (NotificationManager)
|
mNotificationManager = (NotificationManager)
|
||||||
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
mClientProxy = new ClientProxy(ctx);
|
||||||
mActiveDownloadBuilder = new NotificationCompat.Builder(ctx);
|
mActiveDownloadBuilder = new NotificationCompat.Builder(ctx);
|
||||||
mBuilder = new NotificationCompat.Builder(ctx);
|
mBuilder = new NotificationCompat.Builder(ctx);
|
||||||
|
|
||||||
@ -214,8 +85,125 @@ public class DownloadNotification implements IDownloaderClient {
|
|||||||
mCurrentBuilder = mBuilder;
|
mCurrentBuilder = mBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public PendingIntent getClientIntent() {
|
||||||
public void onServiceConnected(Messenger m) {
|
return mContentIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientIntent(PendingIntent clientIntent) {
|
||||||
|
this.mBuilder.setContentIntent(clientIntent);
|
||||||
|
this.mActiveDownloadBuilder.setContentIntent(clientIntent);
|
||||||
|
this.mContentIntent = clientIntent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannelId(String channelId) {
|
||||||
|
this.mBuilder.setChannelId(channelId);
|
||||||
|
this.mActiveDownloadBuilder.setChannelId(channelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resendState() {
|
||||||
|
mClientProxy.onDownloadStateChanged(mState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDownloadStateChanged(int newState) {
|
||||||
|
mClientProxy.onDownloadStateChanged(newState);
|
||||||
|
if (newState != mState) {
|
||||||
|
mState = newState;
|
||||||
|
if (newState == IDownloaderClient.STATE_IDLE || null == mContentIntent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@StringRes int stringDownloadID;
|
||||||
|
int iconResource;
|
||||||
|
boolean ongoingEvent;
|
||||||
|
|
||||||
|
mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
|
||||||
|
|
||||||
|
// get the new title string and paused text
|
||||||
|
switch (newState) {
|
||||||
|
case 0:
|
||||||
|
iconResource = android.R.drawable.stat_sys_warning;
|
||||||
|
stringDownloadID = ApkExpansionDownloader.STRING_UNKNOWN;
|
||||||
|
ongoingEvent = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IDownloaderClient.STATE_DOWNLOADING:
|
||||||
|
iconResource = android.R.drawable.stat_sys_download;
|
||||||
|
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
|
||||||
|
ongoingEvent = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IDownloaderClient.STATE_FETCHING_URL:
|
||||||
|
case IDownloaderClient.STATE_CONNECTING:
|
||||||
|
iconResource = android.R.drawable.stat_sys_download_done;
|
||||||
|
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
|
||||||
|
ongoingEvent = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IDownloaderClient.STATE_COMPLETED:
|
||||||
|
// show notification without progress
|
||||||
|
mCurrentBuilder = mBuilder;
|
||||||
|
mBuilder.setPriority(NotificationCompat.PRIORITY_MAX);
|
||||||
|
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
|
||||||
|
iconResource = android.R.drawable.stat_sys_download_done;
|
||||||
|
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(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(newState);
|
||||||
|
ongoingEvent = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
iconResource = android.R.drawable.stat_sys_warning;
|
||||||
|
stringDownloadID = Helpers.getDownloaderStringResourceIDFromState(newState);
|
||||||
|
ongoingEvent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentText = ApkExpansionDownloader.getString(stringDownloadID);
|
||||||
|
mCurrentTitle = mLabel;
|
||||||
|
mCurrentBuilder.setTicker(mLabel + ": " + mCurrentText);
|
||||||
|
mCurrentBuilder.setSmallIcon(iconResource);
|
||||||
|
mCurrentBuilder.setContentTitle(mCurrentTitle);
|
||||||
|
mCurrentBuilder.setContentText(mCurrentText);
|
||||||
|
if (ongoingEvent) {
|
||||||
|
mCurrentBuilder.setOngoing(true);
|
||||||
|
mCurrentBuilder.setOnlyAlertOnce(true);
|
||||||
|
} else {
|
||||||
|
mCurrentBuilder.setOnlyAlertOnce(false);
|
||||||
|
mCurrentBuilder.setOngoing(false);
|
||||||
|
mCurrentBuilder.setAutoCancel(true);
|
||||||
|
}
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDownloadProgress(DownloadProgressInfo progress) {
|
||||||
|
mClientProxy.onDownloadProgress(progress);
|
||||||
|
if (progress.mOverallTotal <= 0) {
|
||||||
|
// we just show the text
|
||||||
|
mBuilder.setTicker(mCurrentTitle);
|
||||||
|
mBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
|
||||||
|
mBuilder.setContentTitle(mCurrentTitle);
|
||||||
|
mBuilder.setContentText(mCurrentText);
|
||||||
|
mCurrentBuilder = mBuilder;
|
||||||
|
} else {
|
||||||
|
mActiveDownloadBuilder.setProgress((int) progress.mOverallTotal, (int) progress.mOverallProgress, false);
|
||||||
|
mActiveDownloadBuilder.setContentText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
|
||||||
|
mActiveDownloadBuilder.setSmallIcon(android.R.drawable.stat_sys_download);
|
||||||
|
mActiveDownloadBuilder.setTicker(mLabel + ": " + mCurrentText);
|
||||||
|
mActiveDownloadBuilder.setContentTitle(mLabel);
|
||||||
|
mActiveDownloadBuilder.setContentInfo(ApkExpansionDownloader.getString(ApkExpansionDownloader.STRING_TIME_LEFT) + ": " + Helpers.getTimeRemaining(progress.mTimeRemaining));
|
||||||
|
mActiveDownloadBuilder.setOngoing(true);
|
||||||
|
mActiveDownloadBuilder.setOnlyAlertOnce(true);
|
||||||
|
mCurrentBuilder = mActiveDownloadBuilder;
|
||||||
|
}
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mCurrentBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,21 +16,15 @@
|
|||||||
|
|
||||||
package com.google.android.vending.expansion.downloader.impl;
|
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.content.Context;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
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 java.io.File;
|
import java.io.*;
|
||||||
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.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -38,7 +32,7 @@ import java.util.Locale;
|
|||||||
/**
|
/**
|
||||||
* Runs an actual download
|
* Runs an actual download
|
||||||
*/
|
*/
|
||||||
public class DownloadThread {
|
class DownloadThread {
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private DownloadInfo mInfo;
|
private DownloadInfo mInfo;
|
||||||
@ -47,7 +41,7 @@ public class DownloadThread {
|
|||||||
private final DownloadNotification mNotification;
|
private final DownloadNotification mNotification;
|
||||||
private String mUserAgent;
|
private String mUserAgent;
|
||||||
|
|
||||||
public DownloadThread(DownloadInfo info, DownloaderService service,
|
DownloadThread(DownloadInfo info, DownloaderService service,
|
||||||
DownloadNotification notification) {
|
DownloadNotification notification) {
|
||||||
mContext = service;
|
mContext = service;
|
||||||
mInfo = info;
|
mInfo = info;
|
||||||
@ -135,7 +129,7 @@ public class DownloadThread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the download in a separate thread
|
* Executes the download
|
||||||
*/
|
*/
|
||||||
public void run() {
|
public void run() {
|
||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
package com.google.android.vending.expansion.downloader.impl;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.*;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.google.android.vending.expansion.downloader.Constants;
|
||||||
|
import com.google.android.vending.expansion.downloader.IDownloaderService;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import static com.google.android.vending.expansion.downloader.impl
|
||||||
|
.ServiceHandler.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used by the client to issue commands to the {@link
|
||||||
|
* DownloaderService} (such as to pause and resume downloads).
|
||||||
|
* <p>
|
||||||
|
* Most importantly, you must first call {@link #connect()} method to
|
||||||
|
* establish the connection with the service. All the calls to {@link
|
||||||
|
* IDownloaderService} that happen when connection is being
|
||||||
|
* established will be queued and delivered when connection is ready.
|
||||||
|
*/
|
||||||
|
public final class DownloaderProxy implements IDownloaderService,
|
||||||
|
ServiceConnection {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
private Messenger mMessenger;
|
||||||
|
private boolean mConnected;
|
||||||
|
private boolean connectCalled;
|
||||||
|
private final Queue<Message> mMessages = new LinkedList<>();
|
||||||
|
|
||||||
|
public DownloaderProxy(Context context) {
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
mMessenger = new Messenger(service);
|
||||||
|
mConnected = true;
|
||||||
|
drainMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onServiceDisconnected(ComponentName name) {
|
||||||
|
mMessenger = null;
|
||||||
|
mConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drainMessages() {
|
||||||
|
while (mMessenger != null && !mMessages.isEmpty()) {
|
||||||
|
try {
|
||||||
|
mMessenger.send(mMessages.peek());
|
||||||
|
mMessages.remove();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(Constants.TAG, "send: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(int method, Bundle params) {
|
||||||
|
|
||||||
|
if (!connectCalled) {
|
||||||
|
throw new IllegalStateException("connect() method was not called");
|
||||||
|
}
|
||||||
|
|
||||||
|
Message m = Message.obtain(null, method);
|
||||||
|
m.setData(params);
|
||||||
|
|
||||||
|
mMessages.add(m);
|
||||||
|
|
||||||
|
if (mConnected) {
|
||||||
|
drainMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect() {
|
||||||
|
Intent bindIntent = new Intent(mContext.getApplicationContext(),
|
||||||
|
DownloaderService.class);
|
||||||
|
if (!mContext.getApplicationContext().bindService(bindIntent, this,
|
||||||
|
Context.BIND_DEBUG_UNBIND)) {
|
||||||
|
Log.w(Constants.TAG, "Service not bound. Check Manifest.xml " +
|
||||||
|
"declaration");
|
||||||
|
} else {
|
||||||
|
connectCalled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect() {
|
||||||
|
if (mConnected) {
|
||||||
|
mContext.getApplicationContext().unbindService(this);
|
||||||
|
connectCalled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,19 +16,6 @@
|
|||||||
|
|
||||||
package com.google.android.vending.expansion.downloader.impl;
|
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.AlarmManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
@ -38,17 +25,17 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.wifi.WifiManager;
|
import android.net.wifi.WifiManager;
|
||||||
import android.os.Handler;
|
import android.os.*;
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.Messenger;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.provider.Settings.Secure;
|
import android.provider.Settings.Secure;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import com.google.android.vending.expansion.downloader.*;
|
||||||
|
import com.google.android.vending.licensing.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@ -59,7 +46,7 @@ import java.io.File;
|
|||||||
* Note that Android by default will kill off any process that has an open file
|
* 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.
|
* handle on the shared (SD Card) partition if the partition is unmounted.
|
||||||
*/
|
*/
|
||||||
public abstract class DownloaderService extends CustomIntentService implements IDownloaderService {
|
public class DownloaderService extends CustomIntentService implements IDownloaderService {
|
||||||
|
|
||||||
public DownloaderService() {
|
public DownloaderService() {
|
||||||
super("LVLDownloadService");
|
super("LVLDownloadService");
|
||||||
@ -446,9 +433,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
* Our binding to the network state broadcasts
|
* Our binding to the network state broadcasts
|
||||||
*/
|
*/
|
||||||
private BroadcastReceiver mConnReceiver;
|
private BroadcastReceiver mConnReceiver;
|
||||||
final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this);
|
final private Messenger mServiceMessenger = new Messenger(new ServiceHandler(this));
|
||||||
final private Messenger mServiceMessenger = mServiceStub.getMessenger();
|
|
||||||
private Messenger mClientMessenger;
|
|
||||||
private DownloadNotification mNotification;
|
private DownloadNotification mNotification;
|
||||||
private PendingIntent mPendingIntent;
|
private PendingIntent mPendingIntent;
|
||||||
private PendingIntent mAlarmIntent;
|
private PendingIntent mAlarmIntent;
|
||||||
@ -588,9 +573,10 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
public static final int LVL_CHECK_REQUIRED = 1;
|
public static final int LVL_CHECK_REQUIRED = 1;
|
||||||
public static final int DOWNLOAD_REQUIRED = 2;
|
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_PENDING_INTENT = "EPI";
|
||||||
public static final String EXTRA_MESSAGE_HANDLER = "EMH";
|
public static final String EXTRA_CHANNEL_ID = "ECI";
|
||||||
|
public static final String EXTRA_SALT = "ESALT";
|
||||||
|
public static final String EXTRA_PUBLIC_KEY = "EPK";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the LVL check is required
|
* Returns true if the LVL check is required
|
||||||
@ -621,25 +607,6 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
sIsRunning = 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 `
|
* 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
|
* many things. 1) Checks to see if the APK version has been checked and the
|
||||||
@ -654,14 +621,18 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
* network connection, even if Market delivers all of the files.
|
* network connection, even if Market delivers all of the files.
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
|
* @param channelId The Channel ID to use for download progress
|
||||||
|
* notifications on Android O+
|
||||||
* @param pendingIntent
|
* @param pendingIntent
|
||||||
* @return true if the app should wait for more guidance from the
|
* @return true if the app should wait for more guidance from the
|
||||||
* downloader, false if the app can continue
|
* downloader, false if the app can continue
|
||||||
* @throws NameNotFoundException
|
* @throws NameNotFoundException
|
||||||
*/
|
*/
|
||||||
public static int startDownloadServiceIfRequired(Context context,
|
public static int startDownloadServiceIfRequired(Context context,
|
||||||
PendingIntent pendingIntent, String classPackage, String className)
|
String channelId,
|
||||||
|
PendingIntent pendingIntent, byte[] salt, String publicKey)
|
||||||
throws NameNotFoundException {
|
throws NameNotFoundException {
|
||||||
|
|
||||||
// first: do we need to do an LVL update?
|
// first: do we need to do an LVL update?
|
||||||
// we begin by getting our APK version from the package manager
|
// we begin by getting our APK version from the package manager
|
||||||
final PackageInfo pi = context.getPackageManager().getPackageInfo(
|
final PackageInfo pi = context.getPackageManager().getPackageInfo(
|
||||||
@ -696,10 +667,12 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case DOWNLOAD_REQUIRED:
|
case DOWNLOAD_REQUIRED:
|
||||||
case LVL_CHECK_REQUIRED:
|
case LVL_CHECK_REQUIRED:
|
||||||
Intent fileIntent = new Intent();
|
Intent downloadIntent = new Intent(context, DownloaderService.class);
|
||||||
fileIntent.setClassName(classPackage, className);
|
downloadIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
|
||||||
fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
|
downloadIntent.putExtra(EXTRA_CHANNEL_ID, channelId);
|
||||||
context.startService(fileIntent);
|
downloadIntent.putExtra(EXTRA_SALT, salt);
|
||||||
|
downloadIntent.putExtra(EXTRA_PUBLIC_KEY, publicKey);
|
||||||
|
context.startService(downloadIntent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
@ -732,19 +705,19 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
this.startService(fileIntent);
|
this.startService(fileIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String getPublicKey();
|
|
||||||
|
|
||||||
public abstract byte[] getSALT();
|
|
||||||
|
|
||||||
public abstract String getAlarmReceiverClassName();
|
|
||||||
|
|
||||||
private class LVLRunnable implements Runnable {
|
private class LVLRunnable implements Runnable {
|
||||||
LVLRunnable(Context context, PendingIntent intent) {
|
|
||||||
mContext = context;
|
|
||||||
mPendingIntent = intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Context mContext;
|
final Context mContext;
|
||||||
|
private final String mChannelId;
|
||||||
|
private final byte[] mSalt;
|
||||||
|
private final String mPublicKey;
|
||||||
|
|
||||||
|
LVLRunnable(Context context, String channelId, byte[] salt, String publicKey) {
|
||||||
|
mContext = context;
|
||||||
|
mChannelId = channelId;
|
||||||
|
mSalt = salt;
|
||||||
|
mPublicKey = publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -754,7 +727,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
Secure.ANDROID_ID);
|
Secure.ANDROID_ID);
|
||||||
|
|
||||||
final APKExpansionPolicy aep = new APKExpansionPolicy(mContext,
|
final APKExpansionPolicy aep = new APKExpansionPolicy(mContext,
|
||||||
new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId));
|
new AESObfuscator(mSalt, mContext.getPackageName(), deviceId));
|
||||||
|
|
||||||
// reset our policy back to the start of the world to force a
|
// reset our policy back to the start of the world to force a
|
||||||
// re-check
|
// re-check
|
||||||
@ -763,7 +736,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
// let's try and get the OBB file from LVL first
|
// let's try and get the OBB file from LVL first
|
||||||
// Construct the LicenseChecker with a Policy.
|
// Construct the LicenseChecker with a Policy.
|
||||||
final LicenseChecker checker = new LicenseChecker(mContext, aep,
|
final LicenseChecker checker = new LicenseChecker(mContext, aep,
|
||||||
getPublicKey() // Your public licensing key.
|
mPublicKey // Your public licensing key.
|
||||||
);
|
);
|
||||||
checker.checkAccess(new LicenseCheckerCallback() {
|
checker.checkAccess(new LicenseCheckerCallback() {
|
||||||
|
|
||||||
@ -829,9 +802,8 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
pi = mContext.getPackageManager().getPackageInfo(
|
pi = mContext.getPackageManager().getPackageInfo(
|
||||||
mContext.getPackageName(), 0);
|
mContext.getPackageName(), 0);
|
||||||
db.updateMetadata(pi.versionCode, status);
|
db.updateMetadata(pi.versionCode, status);
|
||||||
Class<?> serviceClass = DownloaderService.this.getClass();
|
Class<? extends DownloaderService> serviceClass = DownloaderService.this.getClass();
|
||||||
switch (startDownloadServiceIfRequired(mContext, mPendingIntent,
|
switch (startDownloadServiceIfRequired(mContext, mChannelId, mPendingIntent, mSalt, mPublicKey)) {
|
||||||
serviceClass)) {
|
|
||||||
case NO_DOWNLOAD_REQUIRED:
|
case NO_DOWNLOAD_REQUIRED:
|
||||||
mNotification
|
mNotification
|
||||||
.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED);
|
.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED);
|
||||||
@ -900,10 +872,10 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
*/
|
*/
|
||||||
public void updateLVL(final Context context) {
|
public void updateLVL(final Context context, String channelId, byte[] salt, String publicKey) {
|
||||||
Context c = context.getApplicationContext();
|
Context c = context.getApplicationContext();
|
||||||
Handler h = new Handler(c.getMainLooper());
|
Handler h = new Handler(c.getMainLooper());
|
||||||
h.post(new LVLRunnable(c, mPendingIntent));
|
h.post(new LVLRunnable(c, channelId, salt, publicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -939,7 +911,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
return !Helpers.doesFileExist(this, filename, fileSize, true);
|
return !Helpers.doesFileExist(this, filename, fileSize, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleAlarm(long wakeUp) {
|
private void scheduleAlarm(long wakeUp, boolean repeated, Bundle callerExtras) {
|
||||||
AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||||
if (alarms == null) {
|
if (alarms == null) {
|
||||||
Log.e(Constants.TAG, "couldn't get alarm manager");
|
Log.e(Constants.TAG, "couldn't get alarm manager");
|
||||||
@ -950,17 +922,22 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
|
Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
String className = getAlarmReceiverClassName();
|
// put original extras to the wake up intent
|
||||||
Intent intent = new Intent(Constants.ACTION_RETRY);
|
Intent intent = new Intent(this, AlarmReceiver.class);
|
||||||
intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
|
intent.setAction(Constants.ACTION_RETRY);
|
||||||
intent.setClassName(this.getPackageName(),
|
intent.putExtras(callerExtras);
|
||||||
className);
|
|
||||||
mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent,
|
mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent,
|
||||||
PendingIntent.FLAG_ONE_SHOT);
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
alarms.set(
|
|
||||||
AlarmManager.RTC_WAKEUP,
|
if (repeated) {
|
||||||
System.currentTimeMillis() + wakeUp, mAlarmIntent
|
alarms.setRepeating(AlarmManager.RTC_WAKEUP,
|
||||||
);
|
System.currentTimeMillis() + wakeUp, wakeUp, mAlarmIntent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alarms.set(AlarmManager.RTC_WAKEUP,
|
||||||
|
System.currentTimeMillis() + wakeUp, mAlarmIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelAlarms() {
|
private void cancelAlarms() {
|
||||||
@ -998,7 +975,31 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
context.startService(fileIntent);
|
context.startService(fileIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to handle wake up calls from service watch dogs.
|
||||||
|
*/
|
||||||
|
public static class AlarmReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
try {
|
||||||
|
final PendingIntent pendingIntent = (PendingIntent) intent
|
||||||
|
.getParcelableExtra(EXTRA_PENDING_INTENT);
|
||||||
|
|
||||||
|
startDownloadServiceIfRequired(
|
||||||
|
context,
|
||||||
|
intent.getStringExtra(EXTRA_CHANNEL_ID),
|
||||||
|
pendingIntent,
|
||||||
|
intent.getByteArrayExtra(EXTRA_SALT),
|
||||||
|
intent.getStringExtra(EXTRA_PUBLIC_KEY)
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.e(getClass().getSimpleName(), "onReceive: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main thread for the Downloader. This thread is responsible
|
* This is the main thread for the Downloader. This thread is responsible
|
||||||
@ -1011,11 +1012,14 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
// the database automatically reads the metadata for version code
|
// the database automatically reads the metadata for version code
|
||||||
// and download status when the instance is created
|
// and download status when the instance is created
|
||||||
DownloadsDB db = DownloadsDB.getDB(this);
|
DownloadsDB db = DownloadsDB.getDB(this);
|
||||||
final PendingIntent pendingIntent = (PendingIntent) intent
|
final PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
|
||||||
.getParcelableExtra(EXTRA_PENDING_INTENT);
|
final String channelId = intent.getStringExtra(EXTRA_CHANNEL_ID);
|
||||||
|
final byte[] salt = intent.getByteArrayExtra(EXTRA_SALT);
|
||||||
|
final String publicKey = intent.getStringExtra(EXTRA_PUBLIC_KEY);
|
||||||
|
|
||||||
if (null != pendingIntent)
|
mNotification.setChannelId(channelId);
|
||||||
{
|
|
||||||
|
if (null != pendingIntent) {
|
||||||
mNotification.setClientIntent(pendingIntent);
|
mNotification.setClientIntent(pendingIntent);
|
||||||
mPendingIntent = pendingIntent;
|
mPendingIntent = pendingIntent;
|
||||||
} else if (null != mPendingIntent) {
|
} else if (null != mPendingIntent) {
|
||||||
@ -1028,7 +1032,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
// when the LVL check completes, a successful response will update
|
// when the LVL check completes, a successful response will update
|
||||||
// the service
|
// the service
|
||||||
if (isLVLCheckRequired(db, mPackageInfo)) {
|
if (isLVLCheckRequired(db, mPackageInfo)) {
|
||||||
updateLVL(this);
|
updateLVL(this, channelId, salt, publicKey);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1074,7 +1078,8 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
if (info.mStatus != STATUS_SUCCESS) {
|
if (info.mStatus != STATUS_SUCCESS) {
|
||||||
DownloadThread dt = new DownloadThread(info, this, mNotification);
|
DownloadThread dt = new DownloadThread(info, this, mNotification);
|
||||||
cancelAlarms();
|
cancelAlarms();
|
||||||
scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG);
|
// schedule repeated alarm to check if process is alive
|
||||||
|
scheduleAlarm(Constants.ACTIVE_THREAD_WATCHDOG, true, intent.getExtras());
|
||||||
dt.run();
|
dt.run();
|
||||||
cancelAlarms();
|
cancelAlarms();
|
||||||
}
|
}
|
||||||
@ -1084,7 +1089,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
switch (info.mStatus) {
|
switch (info.mStatus) {
|
||||||
case STATUS_FORBIDDEN:
|
case STATUS_FORBIDDEN:
|
||||||
// the URL is out of date
|
// the URL is out of date
|
||||||
updateLVL(this);
|
updateLVL(this, channelId, salt, publicKey);
|
||||||
return;
|
return;
|
||||||
case STATUS_SUCCESS:
|
case STATUS_SUCCESS:
|
||||||
mBytesSoFar += info.mCurrentBytes - startingCount;
|
mBytesSoFar += info.mCurrentBytes - startingCount;
|
||||||
@ -1139,7 +1144,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (setWakeWatchdog) {
|
if (setWakeWatchdog) {
|
||||||
scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER);
|
scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER, false, intent.getExtras());
|
||||||
} else {
|
} else {
|
||||||
cancelAlarms();
|
cancelAlarms();
|
||||||
}
|
}
|
||||||
@ -1161,7 +1166,6 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
unregisterReceiver(mConnReceiver);
|
unregisterReceiver(mConnReceiver);
|
||||||
mConnReceiver = null;
|
mConnReceiver = null;
|
||||||
}
|
}
|
||||||
mServiceStub.disconnect(this);
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1331,11 +1335,4 @@ public abstract class DownloaderService extends CustomIntentService implements I
|
|||||||
public void requestDownloadStatus() {
|
public void requestDownloadStatus() {
|
||||||
mNotification.resendState();
|
mNotification.resendState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClientUpdated(Messenger clientMessenger) {
|
|
||||||
this.mClientMessenger = clientMessenger;
|
|
||||||
mNotification.setMessenger(mClientMessenger);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.google.android.vending.expansion.downloader.impl;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import com.google.android.vending.expansion.downloader.IDownloaderService;
|
||||||
|
|
||||||
|
class ServiceHandler extends Handler {
|
||||||
|
|
||||||
|
public static final String PARAMS_FLAGS = "flags";
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
private final IDownloaderService service;
|
||||||
|
|
||||||
|
ServiceHandler(IDownloaderService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_REQUEST_ABORT_DOWNLOAD:
|
||||||
|
service.requestAbortDownload();
|
||||||
|
break;
|
||||||
|
case MSG_REQUEST_CONTINUE_DOWNLOAD:
|
||||||
|
service.requestContinueDownload();
|
||||||
|
break;
|
||||||
|
case MSG_REQUEST_PAUSE_DOWNLOAD:
|
||||||
|
service.requestPauseDownload();
|
||||||
|
break;
|
||||||
|
case MSG_SET_DOWNLOAD_FLAGS:
|
||||||
|
service.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
|
||||||
|
break;
|
||||||
|
case MSG_REQUEST_DOWNLOAD_STATE:
|
||||||
|
service.requestDownloadStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -33,8 +33,8 @@ import java.util.Vector;
|
|||||||
/**
|
/**
|
||||||
* Default policy. All policy decisions are based off of response data received
|
* Default policy. All policy decisions are based off of response data received
|
||||||
* from the licensing service. Specifically, the licensing server sends the
|
* from the licensing service. Specifically, the licensing server sends the
|
||||||
* following information: response validity period, error retry period,
|
* following information: response validity period, error retry period, and
|
||||||
* error retry count and a URL for restoring app access in unlicensed cases.
|
* error retry count.
|
||||||
* <p>
|
* <p>
|
||||||
* These values will vary based on the the way the application is configured in
|
* These values will vary based on the the way the application is configured in
|
||||||
* the Google Play publishing console, such as whether the application is
|
* the Google Play publishing console, such as whether the application is
|
||||||
@ -53,7 +53,6 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
private static final String PREF_RETRY_UNTIL = "retryUntil";
|
private static final String PREF_RETRY_UNTIL = "retryUntil";
|
||||||
private static final String PREF_MAX_RETRIES = "maxRetries";
|
private static final String PREF_MAX_RETRIES = "maxRetries";
|
||||||
private static final String PREF_RETRY_COUNT = "retryCount";
|
private static final String PREF_RETRY_COUNT = "retryCount";
|
||||||
private static final String PREF_LICENSING_URL = "licensingUrl";
|
|
||||||
private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
|
private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
|
||||||
private static final String DEFAULT_RETRY_UNTIL = "0";
|
private static final String DEFAULT_RETRY_UNTIL = "0";
|
||||||
private static final String DEFAULT_MAX_RETRIES = "0";
|
private static final String DEFAULT_MAX_RETRIES = "0";
|
||||||
@ -67,7 +66,6 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
private long mRetryCount;
|
private long mRetryCount;
|
||||||
private long mLastResponseTime = 0;
|
private long mLastResponseTime = 0;
|
||||||
private int mLastResponse;
|
private int mLastResponse;
|
||||||
private String mLicensingUrl;
|
|
||||||
private PreferenceObfuscator mPreferences;
|
private PreferenceObfuscator mPreferences;
|
||||||
private Vector<String> mExpansionURLs = new Vector<String>();
|
private Vector<String> mExpansionURLs = new Vector<String>();
|
||||||
private Vector<String> mExpansionFileNames = new Vector<String>();
|
private Vector<String> mExpansionFileNames = new Vector<String>();
|
||||||
@ -96,7 +94,6 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
|
mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
|
||||||
mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
|
mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
|
||||||
mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
|
mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
|
||||||
mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,8 +119,6 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
* until
|
* until
|
||||||
* <li>GT: the timestamp that the client should ignore retry errors until
|
* <li>GT: the timestamp that the client should ignore retry errors until
|
||||||
* <li>GR: the number of retry errors that the client should ignore
|
* <li>GR: the number of retry errors that the client should ignore
|
||||||
* <li>LU: a deep link URL that can enable access for unlicensed apps (e.g.
|
|
||||||
* buy app on the Play Store)
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param response the result from validating the server response
|
* @param response the result from validating the server response
|
||||||
@ -139,12 +134,10 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
setRetryCount(mRetryCount + 1);
|
setRetryCount(mRetryCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update server policy data
|
|
||||||
Map<String, String> extras = decodeExtras(rawData);
|
|
||||||
if (response == Policy.LICENSED) {
|
if (response == Policy.LICENSED) {
|
||||||
|
// Update server policy data
|
||||||
|
Map<String, String> extras = decodeExtras(rawData.extra);
|
||||||
mLastResponse = response;
|
mLastResponse = response;
|
||||||
// Reset the licensing URL since it is only applicable for NOT_LICENSED responses.
|
|
||||||
setLicensingUrl(null);
|
|
||||||
setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE));
|
setValidityTimestamp(Long.toString(System.currentTimeMillis() + MILLIS_PER_MINUTE));
|
||||||
Set<String> keys = extras.keySet();
|
Set<String> keys = extras.keySet();
|
||||||
for (String key : keys) {
|
for (String key : keys) {
|
||||||
@ -166,12 +159,10 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (response == Policy.NOT_LICENSED) {
|
} else if (response == Policy.NOT_LICENSED) {
|
||||||
// Clear out stale retry params
|
// Clear out stale policy data
|
||||||
setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
|
setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
|
||||||
setRetryUntil(DEFAULT_RETRY_UNTIL);
|
setRetryUntil(DEFAULT_RETRY_UNTIL);
|
||||||
setMaxRetries(DEFAULT_MAX_RETRIES);
|
setMaxRetries(DEFAULT_MAX_RETRIES);
|
||||||
// Update the licensing URL
|
|
||||||
setLicensingUrl(extras.get("LU"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLastResponse(response);
|
setLastResponse(response);
|
||||||
@ -284,20 +275,6 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
return mMaxRetries;
|
return mMaxRetries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the licensing URL that displays a Play Store UI for the user to regain app access.
|
|
||||||
*
|
|
||||||
* @param url the LU string received
|
|
||||||
*/
|
|
||||||
private void setLicensingUrl(String url) {
|
|
||||||
mLicensingUrl = url;
|
|
||||||
mPreferences.putString(PREF_LICENSING_URL, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLicensingUrl() {
|
|
||||||
return mLicensingUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the count of expansion URLs. Since expansionURLs are not committed
|
* Gets the count of expansion URLs. Since expansionURLs are not committed
|
||||||
* to preferences, this will return zero if there has been no LVL fetch
|
* to preferences, this will return zero if there has been no LVL fetch
|
||||||
@ -395,15 +372,10 @@ public class APKExpansionPolicy implements Policy {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> decodeExtras(
|
private Map<String, String> decodeExtras(String extras) {
|
||||||
com.google.android.vending.licensing.ResponseData rawData) {
|
|
||||||
Map<String, String> results = new HashMap<String, String>();
|
Map<String, String> results = new HashMap<String, String>();
|
||||||
if (rawData == null) {
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URI rawExtras = new URI("?" + rawData.extra);
|
URI rawExtras = new URI("?" + extras);
|
||||||
URIQueryDecoder.DecodeQuery(rawExtras, results);
|
URIQueryDecoder.DecodeQuery(rawExtras, results);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
|
Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
|
||||||
|
@ -21,7 +21,6 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
@ -196,20 +195,6 @@ public class LicenseChecker implements ServiceConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers the last deep link licensing URL returned from the server, which redirects users to a
|
|
||||||
* page which enables them to gain access to the app. If no such URL is returned by the server, it
|
|
||||||
* will go to the details page of the app in the Play Store.
|
|
||||||
*/
|
|
||||||
public void followLastLicensingUrl(Context context) {
|
|
||||||
String licensingUrl = mPolicy.getLicensingUrl();
|
|
||||||
if (licensingUrl == null) {
|
|
||||||
licensingUrl = "https://play.google.com/store/apps/details?id=" + context.getPackageName();
|
|
||||||
}
|
|
||||||
Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(licensingUrl));
|
|
||||||
context.startActivity(marketIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runChecks() {
|
private void runChecks() {
|
||||||
LicenseValidator validator;
|
LicenseValidator validator;
|
||||||
while ((validator = mPendingChecks.poll()) != null) {
|
while ((validator = mPendingChecks.poll()) != null) {
|
||||||
|
@ -39,9 +39,9 @@ public interface Obfuscator {
|
|||||||
/**
|
/**
|
||||||
* Undo the transformation applied to data by the obfuscate() method.
|
* Undo the transformation applied to data by the obfuscate() method.
|
||||||
*
|
*
|
||||||
* @param obfuscated The data that is to be un-obfuscated.
|
* @param original The data that is to be obfuscated.
|
||||||
* @param key The key for the data that is to be un-obfuscated.
|
* @param key The key for the data that is to be obfuscated.
|
||||||
* @return The original data transformed by the obfuscate() method.
|
* @return A transformed version of the original data.
|
||||||
* @throws ValidationException Optionally thrown if a data integrity check fails.
|
* @throws ValidationException Optionally thrown if a data integrity check fails.
|
||||||
*/
|
*/
|
||||||
String unobfuscate(String obfuscated, String key) throws ValidationException;
|
String unobfuscate(String obfuscated, String key) throws ValidationException;
|
||||||
|
@ -56,10 +56,4 @@ public interface Policy {
|
|||||||
* Check if the user should be allowed access to the application.
|
* Check if the user should be allowed access to the application.
|
||||||
*/
|
*/
|
||||||
boolean allowAccess();
|
boolean allowAccess();
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the licensing URL returned by the server that can enable access for unlicensed apps (e.g.
|
|
||||||
* buy app on the Play Store).
|
|
||||||
*/
|
|
||||||
String getLicensingUrl();
|
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,8 @@ import com.google.android.vending.licensing.util.URIQueryDecoder;
|
|||||||
/**
|
/**
|
||||||
* Default policy. All policy decisions are based off of response data received
|
* Default policy. All policy decisions are based off of response data received
|
||||||
* from the licensing service. Specifically, the licensing server sends the
|
* from the licensing service. Specifically, the licensing server sends the
|
||||||
* following information: response validity period, error retry period,
|
* following information: response validity period, error retry period, and
|
||||||
* error retry count and a URL for restoring app access in unlicensed cases.
|
* error retry count.
|
||||||
* <p>
|
* <p>
|
||||||
* These values will vary based on the the way the application is configured in
|
* These values will vary based on the the way the application is configured in
|
||||||
* the Google Play publishing console, such as whether the application is
|
* the Google Play publishing console, such as whether the application is
|
||||||
@ -50,7 +50,6 @@ public class ServerManagedPolicy implements Policy {
|
|||||||
private static final String PREF_RETRY_UNTIL = "retryUntil";
|
private static final String PREF_RETRY_UNTIL = "retryUntil";
|
||||||
private static final String PREF_MAX_RETRIES = "maxRetries";
|
private static final String PREF_MAX_RETRIES = "maxRetries";
|
||||||
private static final String PREF_RETRY_COUNT = "retryCount";
|
private static final String PREF_RETRY_COUNT = "retryCount";
|
||||||
private static final String PREF_LICENSING_URL = "licensingUrl";
|
|
||||||
private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
|
private static final String DEFAULT_VALIDITY_TIMESTAMP = "0";
|
||||||
private static final String DEFAULT_RETRY_UNTIL = "0";
|
private static final String DEFAULT_RETRY_UNTIL = "0";
|
||||||
private static final String DEFAULT_MAX_RETRIES = "0";
|
private static final String DEFAULT_MAX_RETRIES = "0";
|
||||||
@ -64,7 +63,6 @@ public class ServerManagedPolicy implements Policy {
|
|||||||
private long mRetryCount;
|
private long mRetryCount;
|
||||||
private long mLastResponseTime = 0;
|
private long mLastResponseTime = 0;
|
||||||
private int mLastResponse;
|
private int mLastResponse;
|
||||||
private String mLicensingUrl;
|
|
||||||
private PreferenceObfuscator mPreferences;
|
private PreferenceObfuscator mPreferences;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,7 +80,6 @@ public class ServerManagedPolicy implements Policy {
|
|||||||
mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
|
mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
|
||||||
mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
|
mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
|
||||||
mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
|
mRetryCount = Long.parseLong(mPreferences.getString(PREF_RETRY_COUNT, DEFAULT_RETRY_COUNT));
|
||||||
mLicensingUrl = mPreferences.getString(PREF_LICENSING_URL, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,12 +88,10 @@ public class ServerManagedPolicy implements Policy {
|
|||||||
* This data will be used for computing future policy decisions. The
|
* This data will be used for computing future policy decisions. The
|
||||||
* following parameters are processed:
|
* following parameters are processed:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>VT: the timestamp that the client should consider the response valid
|
* <li>VT: the timestamp that the client should consider the response
|
||||||
* until
|
* valid until
|
||||||
* <li>GT: the timestamp that the client should ignore retry errors until
|
* <li>GT: the timestamp that the client should ignore retry errors until
|
||||||
* <li>GR: the number of retry errors that the client should ignore
|
* <li>GR: the number of retry errors that the client should ignore
|
||||||
* <li>LU: a deep link URL that can enable access for unlicensed apps (e.g.
|
|
||||||
* buy app on the Play Store)
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param response the result from validating the server response
|
* @param response the result from validating the server response
|
||||||
@ -111,22 +106,18 @@ public class ServerManagedPolicy implements Policy {
|
|||||||
setRetryCount(mRetryCount + 1);
|
setRetryCount(mRetryCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update server policy data
|
|
||||||
Map<String, String> extras = decodeExtras(rawData);
|
|
||||||
if (response == Policy.LICENSED) {
|
if (response == Policy.LICENSED) {
|
||||||
|
// Update server policy data
|
||||||
|
Map<String, String> extras = decodeExtras(rawData.extra);
|
||||||
mLastResponse = response;
|
mLastResponse = response;
|
||||||
// Reset the licensing URL since it is only applicable for NOT_LICENSED responses.
|
|
||||||
setLicensingUrl(null);
|
|
||||||
setValidityTimestamp(extras.get("VT"));
|
setValidityTimestamp(extras.get("VT"));
|
||||||
setRetryUntil(extras.get("GT"));
|
setRetryUntil(extras.get("GT"));
|
||||||
setMaxRetries(extras.get("GR"));
|
setMaxRetries(extras.get("GR"));
|
||||||
} else if (response == Policy.NOT_LICENSED) {
|
} else if (response == Policy.NOT_LICENSED) {
|
||||||
// Clear out stale retry params
|
// Clear out stale policy data
|
||||||
setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
|
setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
|
||||||
setRetryUntil(DEFAULT_RETRY_UNTIL);
|
setRetryUntil(DEFAULT_RETRY_UNTIL);
|
||||||
setMaxRetries(DEFAULT_MAX_RETRIES);
|
setMaxRetries(DEFAULT_MAX_RETRIES);
|
||||||
// Update the licensing URL
|
|
||||||
setLicensingUrl(extras.get("LU"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLastResponse(response);
|
setLastResponse(response);
|
||||||
@ -239,21 +230,6 @@ public class ServerManagedPolicy implements Policy {
|
|||||||
return mMaxRetries;
|
return mMaxRetries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the license URL value (LU) as received from the server and add to preferences. You must
|
|
||||||
* manually call PreferenceObfuscator.commit() to commit these changes to disk.
|
|
||||||
*
|
|
||||||
* @param url the LU string received
|
|
||||||
*/
|
|
||||||
private void setLicensingUrl(String url) {
|
|
||||||
mLicensingUrl = url;
|
|
||||||
mPreferences.putString(PREF_LICENSING_URL, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLicensingUrl() {
|
|
||||||
return mLicensingUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
@ -281,15 +257,10 @@ public class ServerManagedPolicy implements Policy {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> decodeExtras(
|
private Map<String, String> decodeExtras(String extras) {
|
||||||
com.google.android.vending.licensing.ResponseData rawData) {
|
|
||||||
Map<String, String> results = new HashMap<String, String>();
|
Map<String, String> results = new HashMap<String, String>();
|
||||||
if (rawData == null) {
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URI rawExtras = new URI("?" + rawData.extra);
|
URI rawExtras = new URI("?" + extras);
|
||||||
URIQueryDecoder.DecodeQuery(rawExtras, results);
|
URIQueryDecoder.DecodeQuery(rawExtras, results);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
|
Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
|
||||||
|
@ -16,13 +16,6 @@
|
|||||||
|
|
||||||
package com.google.android.vending.licensing;
|
package com.google.android.vending.licensing;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import com.google.android.vending.licensing.util.URIQueryDecoder;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Non-caching policy. All requests will be sent to the licensing service,
|
* Non-caching policy. All requests will be sent to the licensing service,
|
||||||
* and no local caching is performed.
|
* and no local caching is performed.
|
||||||
@ -33,38 +26,28 @@ import java.util.Map;
|
|||||||
* weigh the risks of using this Policy over one which implements caching,
|
* weigh the risks of using this Policy over one which implements caching,
|
||||||
* such as ServerManagedPolicy.
|
* such as ServerManagedPolicy.
|
||||||
* <p>
|
* <p>
|
||||||
* Access to the application is only allowed if a LICENSED response is.
|
* Access to the application is only allowed if a LICESNED response is.
|
||||||
* received. All other responses (including RETRY) will deny access.
|
* received. All other responses (including RETRY) will deny access.
|
||||||
*/
|
*/
|
||||||
public class StrictPolicy implements Policy {
|
public class StrictPolicy implements Policy {
|
||||||
|
|
||||||
private static final String TAG = "StrictPolicy";
|
|
||||||
|
|
||||||
private int mLastResponse;
|
private int mLastResponse;
|
||||||
private String mLicensingUrl;
|
|
||||||
|
|
||||||
public StrictPolicy() {
|
public StrictPolicy() {
|
||||||
// Set default policy. This will force the application to check the policy on launch.
|
// Set default policy. This will force the application to check the policy on launch.
|
||||||
mLastResponse = Policy.RETRY;
|
mLastResponse = Policy.RETRY;
|
||||||
mLicensingUrl = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a new response from the license server. Since we aren't
|
* Process a new response from the license server. Since we aren't
|
||||||
* performing any caching, this equates to reading the LicenseResponse.
|
* performing any caching, this equates to reading the LicenseResponse.
|
||||||
* Any cache-related ResponseData is ignored, but the licensing URL
|
* Any ResponseData provided is ignored.
|
||||||
* extra is still extracted in cases where the app is unlicensed.
|
|
||||||
*
|
*
|
||||||
* @param response the result from validating the server response
|
* @param response the result from validating the server response
|
||||||
* @param rawData the raw server response data
|
* @param rawData the raw server response data
|
||||||
*/
|
*/
|
||||||
public void processServerResponse(int response, ResponseData rawData) {
|
public void processServerResponse(int response, ResponseData rawData) {
|
||||||
mLastResponse = response;
|
mLastResponse = response;
|
||||||
|
|
||||||
if (response == Policy.NOT_LICENSED) {
|
|
||||||
Map<String, String> extras = decodeExtras(rawData);
|
|
||||||
mLicensingUrl = extras.get("LU");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,24 +60,4 @@ public class StrictPolicy implements Policy {
|
|||||||
return (mLastResponse == Policy.LICENSED);
|
return (mLastResponse == Policy.LICENSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLicensingUrl() {
|
|
||||||
return mLicensingUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> decodeExtras(
|
|
||||||
com.google.android.vending.licensing.ResponseData rawData) {
|
|
||||||
Map<String, String> results = new HashMap<String, String>();
|
|
||||||
if (rawData == null) {
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
URI rawExtras = new URI("?" + rawData.extra);
|
|
||||||
URIQueryDecoder.DecodeQuery(rawExtras, results);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
Log.w(TAG, "Invalid syntax error while decoding extras data from server.");
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -64,8 +64,8 @@
|
|||||||
|
|
||||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||||
|
|
||||||
<service android:name="com.falsinsoft.QtAndroidTools.ApkExpansionDownloaderService"/>
|
<service android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService" android:enabled="true"/>
|
||||||
<receiver android:name="com.falsinsoft.QtAndroidTools.ApkExpansionDownloaderAlarmReceiver"/>
|
<receiver android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService$AlarmReceiver" android:enabled="true"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user