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:
FalsinSoft 2019-01-09 22:39:04 +01:00
parent 12e61b761b
commit b926f059f4
30 changed files with 1039 additions and 1306 deletions

View File

@ -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 "QtAndroidApkExpansionFiles.h"
#include "QAndroidApkExpansionFiles.h"
#ifdef __cplusplus
extern "C" {
#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();
QString TextInfo;
QAndroidApkExpansionFiles *pInstance = QAndroidApkExpansionFiles::instance();
QString TextString;
Q_UNUSED(thiz)
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(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(thiz)
@ -51,52 +73,62 @@ JNIEXPORT void JNICALL Java_com_falsinsoft_QtAndroidTools_ApkExpansionDownloader
}
#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",
QtAndroid::androidActivity().object<jobject>())
{
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QtAndroidApkExpansionFiles::ApplicationStateChanged);
qRegisterMetaType<ExpansionFileInfo>();
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(scriptEngine);
return new QtAndroidApkExpansionFiles();
return new QAndroidApkExpansionFiles();
}
QtAndroidApkExpansionFiles* QtAndroidApkExpansionFiles::instance()
QAndroidApkExpansionFiles* QAndroidApkExpansionFiles::instance()
{
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())
{
m_JavaApkExpansionDownloader.callMethod<void>("enableClientStubConnection",
"(Z)V",
StubConnect
m_JavaApkExpansionDownloader.callMethod<void>("appStateChanged",
"(I)V",
NewState
);
}
}
QtAndroidApkExpansionFiles::APKEF_STATE QtAndroidApkExpansionFiles::startDownloadFiles()
QAndroidApkExpansionFiles::APKEF_STATE QAndroidApkExpansionFiles::startDownloadFiles()
{
if(m_JavaApkExpansionDownloader.isValid() == false) return APKEF_INVALID_JAVA_CLASS;
if(m_Base64PublicKey.count() == 0) return APKEF_INVALID_BASE64_PUBLIC_KEY;
if(m_SALT.count() != 20) return APKEF_INVALID_SALT;
if(QtAndroid::androidSdkVersion() >= 23)
{
if(QtAndroid::checkPermission("Manifest.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.READ_EXTERNAL_STORAGE") != QtAndroid::PermissionResult::Granted) return APKEF_STORAGE_READ_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++)
@ -147,7 +179,7 @@ QtAndroidApkExpansionFiles::APKEF_STATE QtAndroidApkExpansionFiles::startDownloa
return APKEF_UNKNOWN_ERROR;
}
QString QtAndroidApkExpansionFiles::mainFileName()
QString QAndroidApkExpansionFiles::mainFileName()
{
QString FileName;
@ -164,7 +196,7 @@ QString QtAndroidApkExpansionFiles::mainFileName()
return FileName;
}
QString QtAndroidApkExpansionFiles::patchFileName()
QString QAndroidApkExpansionFiles::patchFileName()
{
QString FileName;
@ -181,111 +213,128 @@ QString QtAndroidApkExpansionFiles::patchFileName()
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;
}
void QtAndroidApkExpansionFiles::setBase64PublicKey(const QString &Base64PublicKey)
void QAndroidApkExpansionFiles::setBase64PublicKey(const QString &Base64PublicKey)
{
m_Base64PublicKey = Base64PublicKey;
}
const QVector<int>& QtAndroidApkExpansionFiles::getSALT() const
const QVector<int>& QAndroidApkExpansionFiles::getSALT() const
{
return m_SALT;
}
void QtAndroidApkExpansionFiles::setSALT(const QVector<int> &SALT)
void QAndroidApkExpansionFiles::setSALT(const QVector<int> &SALT)
{
m_SALT = SALT;
}
const ExpansionFileInfo& QtAndroidApkExpansionFiles::getMainExpansionFileInfo() const
const ExpansionFileInfo& QAndroidApkExpansionFiles::getMainExpansionFileInfo() const
{
return m_ExpansionsFileInfo[0];
}
void QtAndroidApkExpansionFiles::setMainExpansionFileInfo(const ExpansionFileInfo &MainExpansionFileInfo)
void QAndroidApkExpansionFiles::setMainExpansionFileInfo(const ExpansionFileInfo &MainExpansionFileInfo)
{
m_ExpansionsFileInfo[0] = MainExpansionFileInfo;
}
const ExpansionFileInfo& QtAndroidApkExpansionFiles::getPatchExpansionFileInfo() const
const ExpansionFileInfo& QAndroidApkExpansionFiles::getPatchExpansionFileInfo() const
{
return m_ExpansionsFileInfo[1];
}
void QtAndroidApkExpansionFiles::setPatchExpansionFileInfo(const ExpansionFileInfo &PatchExpansionFileInfo)
void QAndroidApkExpansionFiles::setPatchExpansionFileInfo(const ExpansionFileInfo &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:
TextInfo = tr("Waiting for download to start");
case STRING_IDLE:
TextString = tr("Waiting for download to start");
break;
case STATE_FETCHING_URL:
TextInfo = tr("Looking for resources to download");
case STRING_FETCHING_URL:
TextString = tr("Looking for resources to download");
break;
case STATE_CONNECTING:
TextInfo = tr("Connecting to the download server");
case STRING_CONNECTING:
TextString = tr("Connecting to the download server");
break;
case STATE_DOWNLOADING:
TextInfo = tr("Downloading resources");
case STRING_DOWNLOADING:
TextString = tr("Downloading resources");
break;
case STATE_COMPLETED:
TextInfo = tr("Download finished");
case STRING_COMPLETED:
TextString = tr("Download finished");
break;
case STATE_PAUSED_NETWORK_UNAVAILABLE:
TextInfo = tr("Download paused because no network is available");
case STRING_PAUSED_NETWORK_UNAVAILABLE:
TextString = tr("Download paused because no network is available");
break;
case STATE_PAUSED_BY_REQUEST:
TextInfo = tr("Download paused");
case STRING_PAUSED_BY_REQUEST:
TextString = tr("Download paused");
break;
case STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
TextInfo = tr("Download paused because wifi is disabled");
case STRING_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
TextString = tr("Download paused because wifi is disabled");
break;
case STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case STATE_PAUSED_NEED_WIFI:
TextInfo = tr("Download paused because wifi is unavailable");
case STRING_PAUSED_NEED_CELLULAR_PERMISSION:
case STRING_PAUSED_NEED_WIFI:
TextString = tr("Download paused because wifi is unavailable");
break;
case STATE_PAUSED_WIFI_DISABLED:
TextInfo = tr("Download paused because wifi is disabled");
case STRING_PAUSED_WIFI_DISABLED:
TextString = tr("Download paused because wifi is disabled");
break;
case STATE_PAUSED_ROAMING:
TextInfo = tr("Download paused because you are roaming");
case STRING_PAUSED_ROAMING:
TextString = tr("Download paused because you are roaming");
break;
case STATE_PAUSED_NETWORK_SETUP_FAILURE:
TextInfo = tr("Download paused. Test a website in browser");
case STRING_PAUSED_NETWORK_SETUP_FAILURE:
TextString = tr("Download paused. Test a website in browser");
break;
case STATE_PAUSED_SDCARD_UNAVAILABLE:
TextInfo = tr("Download paused because the external storage is unavailable");
case STRING_PAUSED_SDCARD_UNAVAILABLE:
TextString = tr("Download paused because the external storage is unavailable");
break;
case STATE_FAILED_UNLICENSED:
TextInfo = tr("Download failed because you may not have purchased this app");
case STRING_FAILED_UNLICENSED:
TextString = tr("Download failed because you may not have purchased this app");
break;
case STATE_FAILED_FETCHING_URL:
TextInfo = tr("Download failed because the resources could not be found");
case STRING_FAILED_FETCHING_URL:
TextString = tr("Download failed because the resources could not be found");
break;
case STATE_FAILED_SDCARD_FULL:
TextInfo = tr("Download failed because the external storage is full");
case STRING_FAILED_SDCARD_FULL:
TextString = tr("Download failed because the external storage is full");
break;
case STATE_FAILED_CANCELED:
TextInfo = tr("Download cancelled");
case STRING_FAILED_CANCELED:
TextString = tr("Download cancelled");
break;
case STATE_UNKNOWN:
TextInfo = tr("Starting...");
case STRING_FAILED:
TextString = tr("Download failed");
break;
case STATE_DOWNLOADING_TIME_LEFT:
TextInfo = tr("Time left");
case STRING_UNKNOWN:
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;
}
return TextInfo;
return TextString;
}

View File

@ -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
#include <QtAndroidExtras>
@ -15,43 +38,45 @@ public:
};
Q_DECLARE_METATYPE(ExpansionFileInfo)
class QtAndroidApkExpansionFiles : public QObject
class QAndroidApkExpansionFiles : public QObject
{
Q_PROPERTY(ExpansionFileInfo main READ getMainExpansionFileInfo WRITE setMainExpansionFileInfo)
Q_PROPERTY(ExpansionFileInfo patch READ getPatchExpansionFileInfo WRITE setPatchExpansionFileInfo)
Q_PROPERTY(QString base64PublicKey READ getBase64PublicKey WRITE setBase64PublicKey)
Q_PROPERTY(QVector<int> salt READ getSALT WRITE setSALT)
Q_DISABLE_COPY(QtAndroidApkExpansionFiles)
Q_DISABLE_COPY(QAndroidApkExpansionFiles)
Q_ENUMS(DOWNLOAD_STATE)
Q_ENUMS(APKEF_STATE)
Q_ENUMS(REQUEST_ID)
Q_ENUMS(STRING_ID)
Q_OBJECT
QtAndroidApkExpansionFiles();
QAndroidApkExpansionFiles();
public:
~QAndroidApkExpansionFiles();
enum DOWNLOAD_STATE
{
STATE_IDLE = 1,
STATE_FETCHING_URL = 2,
STATE_CONNECTING = 3,
STATE_DOWNLOADING = 4,
STATE_COMPLETED = 5,
STATE_PAUSED_NETWORK_UNAVAILABLE = 6,
STATE_PAUSED_BY_REQUEST = 7,
STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION = 8,
STATE_PAUSED_NEED_CELLULAR_PERMISSION = 9,
STATE_PAUSED_WIFI_DISABLED = 10,
STATE_PAUSED_NEED_WIFI = 11,
STATE_PAUSED_ROAMING = 12,
STATE_PAUSED_NETWORK_SETUP_FAILURE = 13,
STATE_PAUSED_SDCARD_UNAVAILABLE = 14,
STATE_FAILED_UNLICENSED = 15,
STATE_FAILED_FETCHING_URL = 16,
STATE_FAILED_SDCARD_FULL = 17,
STATE_FAILED_CANCELED = 18,
STATE_FAILED = 19,
STATE_UNKNOWN = 20,
STATE_DOWNLOADING_TIME_LEFT = 21
STATE_FETCHING_URL,
STATE_CONNECTING,
STATE_DOWNLOADING,
STATE_COMPLETED,
STATE_PAUSED_NETWORK_UNAVAILABLE,
STATE_PAUSED_BY_REQUEST,
STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION,
STATE_PAUSED_NEED_CELLULAR_PERMISSION,
STATE_PAUSED_WIFI_DISABLED,
STATE_PAUSED_NEED_WIFI,
STATE_PAUSED_ROAMING,
STATE_PAUSED_NETWORK_SETUP_FAILURE,
STATE_PAUSED_SDCARD_UNAVAILABLE,
STATE_FAILED_UNLICENSED,
STATE_FAILED_FETCHING_URL,
STATE_FAILED_SDCARD_FULL,
STATE_FAILED_CANCELED,
STATE_FAILED
};
enum APKEF_STATE
{
@ -65,14 +90,47 @@ public:
APKEF_INVALID_SALT,
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 QtAndroidApkExpansionFiles* instance();
static QAndroidApkExpansionFiles* instance();
Q_INVOKABLE QString mainFileName();
Q_INVOKABLE QString patchFileName();
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;
void setBase64PublicKey(const QString &Base64PublicKey);
@ -92,8 +150,17 @@ private slots:
private:
const QAndroidJniObject m_JavaApkExpansionDownloader;
static QtAndroidApkExpansionFiles *m_pInstance;
static QAndroidApkExpansionFiles *m_pInstance;
ExpansionFileInfo m_ExpansionsFileInfo[2];
QString m_Base64PublicKey;
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);
};

View File

@ -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"
QtAndroidAppPermissions::QtAndroidAppPermissions()
QAndroidAppPermissions::QAndroidAppPermissions()
{
}
QObject* QtAndroidAppPermissions::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
QObject* QAndroidAppPermissions::qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine);
Q_UNUSED(scriptEngine);
return new QtAndroidAppPermissions();
return new QAndroidAppPermissions();
}
void QtAndroidAppPermissions::requestPermissions(const QStringList &permissionsNameList)
{
for(int i = 0; i < permissionsNameList.count(); i++)
{
requestPermission(permissionsNameList[i]);
}
}
void QtAndroidAppPermissions::requestPermission(const QString &permissionName)
void QAndroidAppPermissions::requestPermissions(const QStringList &permissionsNameList)
{
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));
return;
if(QtAndroid::checkPermission(permissionsNameList[i]) != QtAndroid::PermissionResult::Granted)
{
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)
{
@ -45,7 +89,7 @@ bool QtAndroidAppPermissions::shouldShowRequestPermissionInfo(const QString &per
return false;
}
void QtAndroidAppPermissions::RequestPermissionResults(const QtAndroid::PermissionResultMap &ResultMap)
void QAndroidAppPermissions::RequestPermissionResults(const QtAndroid::PermissionResultMap &ResultMap)
{
if(ResultMap.count() > 0)
{

View File

@ -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
#include <QtAndroidExtras>
#include <QQmlEngine>
class QtAndroidAppPermissions : public QObject
class QAndroidAppPermissions : public QObject
{
Q_OBJECT
QtAndroidAppPermissions();
QAndroidAppPermissions();
public:
static QObject* qmlInstance(QQmlEngine *engine, QJSEngine *scriptEngine);

View File

@ -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;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
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.expansion.downloader.impl.DownloaderService;
package com.falsinsoft.qtandroidtools;
import com.google.android.vending.expansion.downloader.*;
import com.google.android.vending.expansion.downloader.impl.*;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -20,87 +37,124 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.os.Build;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import java.util.Arrays;
public class ApkExpansionDownloader implements IDownloaderClient
public class ApkExpansionDownloader
{
private final Activity m_ActivityInstance;
private IDownloaderService m_RemoteService;
private IStub m_DownloaderClientStub;
private final String NOTIFICATION_CHANNEL_ID;
private final DownloaderClient mDownloaderClient;
private final DownloaderProxy mDownloaderProxy;
private final Activity mActivityInstance;
public ApkExpansionDownloader(Activity ActivityInstance)
{
final IDownloaderClient Client = this;
ActivityInstance.runOnUiThread(new Runnable()
{
public void run()
{
m_DownloaderClientStub = DownloaderClientMarshaller.CreateStub(Client, ApkExpansionDownloaderService.class);
}
});
m_ActivityInstance = ActivityInstance;
NOTIFICATION_CHANNEL_ID = ActivityInstance.getClass().getName();
mDownloaderClient = new DownloaderClient();
mDownloaderProxy = new DownloaderProxy(ActivityInstance);
mActivityInstance = ActivityInstance;
}
public boolean isAPKFileDelivered(boolean IsMain, int FileVersion, int FileSize)
{
final String FileName = Helpers.getExpansionAPKFileName(m_ActivityInstance, IsMain, FileVersion);
return Helpers.doesFileExist(m_ActivityInstance, FileName, FileSize, false);
final String FileName = Helpers.getExpansionAPKFileName(mActivityInstance, IsMain, FileVersion);
return Helpers.doesFileExist(mActivityInstance, FileName, FileSize, false);
}
public String getExpansionAPKFileName(boolean IsMain, int FileVersion)
{
final String FileName = Helpers.getExpansionAPKFileName(m_ActivityInstance, IsMain, FileVersion);
return Helpers.generateSaveFileName(m_ActivityInstance, FileName);
final String FileName = Helpers.getExpansionAPKFileName(mActivityInstance, IsMain, FileVersion);
return Helpers.generateSaveFileName(mActivityInstance, FileName);
}
public void enableClientStubConnection(boolean ConnectionEnabled)
public void sendRequest(int requestID)
{
if(ConnectionEnabled == true)
m_DownloaderClientStub.connect(m_ActivityInstance);
else
m_DownloaderClientStub.disconnect(m_ActivityInstance);
mDownloaderProxy.connect();
switch(requestID)
{
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)
{
int DownloadResult = -1;
ApkExpansionDownloaderService.BASE64_PUBLIC_KEY = BASE64_PUBLIC_KEY;
ApkExpansionDownloaderService.SALT = SALT;
try
{
Intent LaunchIntent, IntentToLaunchThisActivityFromNotification;
Intent IntentToLaunchThisActivityFromNotification;
PendingIntent PendingActivity;
LaunchIntent = m_ActivityInstance.getIntent();
IntentToLaunchThisActivityFromNotification = new Intent(m_ActivityInstance, m_ActivityInstance.getClass());
IntentToLaunchThisActivityFromNotification = new Intent(mActivityInstance, mActivityInstance.getClass());
IntentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
IntentToLaunchThisActivityFromNotification.setAction(LaunchIntent.getAction());
if(LaunchIntent.getCategories() != null)
{
for(String Category : LaunchIntent.getCategories())
{
IntentToLaunchThisActivityFromNotification.addCategory(Category);
}
}
PendingActivity = PendingIntent.getActivity(m_ActivityInstance,
PendingActivity = PendingIntent.getActivity(mActivityInstance,
0,
IntentToLaunchThisActivityFromNotification,
PendingIntent.FLAG_UPDATE_CURRENT
);
DownloadResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(m_ActivityInstance, PendingActivity, ApkExpansionDownloaderService.class);
if(DownloadResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED)
{
enableClientStubConnection(true);
}
DownloadResult = DownloaderService.startDownloadServiceIfRequired(mActivityInstance,
NOTIFICATION_CHANNEL_ID,
PendingActivity,
SALT,
BASE64_PUBLIC_KEY
);
}
catch(NameNotFoundException e)
{
@ -110,26 +164,56 @@ public class ApkExpansionDownloader implements IDownloaderClient
return DownloadResult;
}
@Override
public void onServiceConnected(Messenger m)
private class DownloaderClient extends BroadcastDownloaderClient
{
m_RemoteService = DownloaderServiceMarshaller.CreateProxy(m);
m_RemoteService.onClientUpdated(m_DownloaderClientStub.getMessenger());
}
@Override
public void onDownloadStateChanged(int newState)
{
downloadStateChanged(newState);
}
@Override
public void onDownloadStateChanged(int newState)
{
downloadStateChanged(newState);
@Override
public void onDownloadProgress(DownloadProgressInfo progress)
{
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
public void onDownloadProgress(DownloadProgressInfo progress)
{
downloadProgress(progress.mOverallTotal, progress.mOverallProgress, progress.mTimeRemaining, progress.mCurrentSpeed);
}
private static final int APP_STATE_CREATE = 0;
private static final int APP_STATE_START = 1;
private static final int APP_STATE_STOP = 2;
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 downloadProgress(long overallTotal, long overallProgress, long timeRemaining, float currentSpeed);
public static native String getString(int stringID);
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -22,8 +22,12 @@ import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.os.SystemClock;
import android.support.annotation.StringRes;
import android.util.Log;
//import com.android.vending.expansion.downloader.R;
import com.falsinsoft.qtandroidtools.ApkExpansionDownloader;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -266,7 +270,7 @@ public class Helpers {
*
* @param c the app/activity/service context
* @param fileName the name (sans path) of the file to query
* @return true if it does exist, false otherwise
* @return value representing whether the file exists and is readable
*/
static public int getFileStatus(Context c, String fileName) {
// 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}.
* @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;
}
}
}

View File

@ -16,8 +16,6 @@
package com.google.android.vending.expansion.downloader;
import android.os.Messenger;
/**
* This interface should be implemented by the client activity for the
* downloader. It is used to pass status from the service to the client.
@ -72,28 +70,6 @@ public interface IDownloaderClient {
static final int STATE_FAILED_CANCELED = 18;
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

View File

@ -17,7 +17,6 @@
package com.google.android.vending.expansion.downloader;
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
import android.os.Messenger;
/**
* This interface is implemented by the DownloaderService and by the
@ -70,14 +69,4 @@ public interface IDownloaderService {
* Requests that the download status be sent to the client.
*/
void requestDownloadStatus();
/**
* Call this when you get {@link
* IDownloaderClient.onServiceConnected(Messenger m)} from the
* DownloaderClient to register the client with the service. It will
* automatically send the current status to the client.
*
* @param clientMessenger
*/
void onClientUpdated(Messenger clientMessenger);
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -18,20 +18,16 @@ package com.google.android.vending.expansion.downloader.impl;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.*;
import android.util.Log;
/**
* This service differs from IntentService in a few minor ways/ It will not
* auto-stop itself after the intent is handled unless the target returns "true"
* in should stop. Since the goal of this service is to handle a single kind of
* 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.
*/
public abstract class CustomIntentService extends Service {
abstract class CustomIntentService extends Service {
private String mName;
private boolean mRedelivery;
private volatile ServiceHandler mServiceHandler;

View File

@ -16,15 +16,14 @@
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.Helpers;
import android.util.Log;
/**
* Representation of information about an individual download from the database.
*/
public class DownloadInfo {
class DownloadInfo {
public String mUri;
public final int mIndex;
public final String mFileName;

View File

@ -16,18 +16,19 @@
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.PendingIntent;
import android.content.Context;
import android.os.Build;
import android.os.Messenger;
import android.support.annotation.StringRes;
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
* 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
* handle these transient states.
*/
public class DownloadNotification implements IDownloaderClient {
class DownloadNotification {
private int mState;
private final Context mContext;
private final NotificationManager mNotificationManager;
private CharSequence mCurrentTitle;
private final IDownloaderClient mClientProxy;
private IDownloaderClient mClientProxy;
private CharSequence mCurrentTitle;
private NotificationCompat.Builder mActiveDownloadBuilder;
private NotificationCompat.Builder mBuilder;
private NotificationCompat.Builder mCurrentBuilder;
private CharSequence mLabel;
private String mCurrentText;
private DownloadProgressInfo mProgressInfo;
private PendingIntent mContentIntent;
static final String LOGTAG = "DownloadNotification";
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
*
@ -200,6 +70,7 @@ public class DownloadNotification implements IDownloaderClient {
mLabel = applicationLabel;
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mClientProxy = new ClientProxy(ctx);
mActiveDownloadBuilder = new NotificationCompat.Builder(ctx);
mBuilder = new NotificationCompat.Builder(ctx);
@ -214,8 +85,125 @@ public class DownloadNotification implements IDownloaderClient {
mCurrentBuilder = mBuilder;
}
@Override
public void onServiceConnected(Messenger m) {
public PendingIntent getClientIntent() {
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());
}
}

View File

@ -16,21 +16,15 @@
package com.google.android.vending.expansion.downloader.impl;
import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import android.content.Context;
import android.os.PowerManager;
import android.os.Process;
import android.util.Log;
import 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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SyncFailedException;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Locale;
@ -38,7 +32,7 @@ import java.util.Locale;
/**
* Runs an actual download
*/
public class DownloadThread {
class DownloadThread {
private Context mContext;
private DownloadInfo mInfo;
@ -47,7 +41,7 @@ public class DownloadThread {
private final DownloadNotification mNotification;
private String mUserAgent;
public DownloadThread(DownloadInfo info, DownloaderService service,
DownloadThread(DownloadInfo info, DownloaderService service,
DownloadNotification notification) {
mContext = service;
mInfo = info;
@ -135,7 +129,7 @@ public class DownloadThread {
}
/**
* Executes the download in a separate thread
* Executes the download
*/
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

View File

@ -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;
}
}
}

View File

@ -16,19 +16,6 @@
package com.google.android.vending.expansion.downloader.impl;
import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
import com.google.android.vending.licensing.AESObfuscator;
import com.google.android.vending.licensing.APKExpansionPolicy;
import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;
import com.google.android.vending.licensing.Policy;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
@ -38,17 +25,17 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Messenger;
import android.os.SystemClock;
import android.os.*;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.google.android.vending.expansion.downloader.*;
import com.google.android.vending.licensing.*;
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
* 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() {
super("LVLDownloadService");
@ -446,9 +433,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
* Our binding to the network state broadcasts
*/
private BroadcastReceiver mConnReceiver;
final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this);
final private Messenger mServiceMessenger = mServiceStub.getMessenger();
private Messenger mClientMessenger;
final private Messenger mServiceMessenger = new Messenger(new ServiceHandler(this));
private DownloadNotification mNotification;
private PendingIntent mPendingIntent;
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 DOWNLOAD_REQUIRED = 2;
public static final String EXTRA_PACKAGE_NAME = "EPN";
public static final String EXTRA_PENDING_INTENT = "EPI";
public static final String EXTRA_MESSAGE_HANDLER = "EMH";
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
@ -621,25 +607,6 @@ public abstract class DownloaderService extends CustomIntentService implements I
sIsRunning = isRunning;
}
public static int startDownloadServiceIfRequired(Context context,
Intent intent, Class<?> serviceClass) throws NameNotFoundException {
final PendingIntent pendingIntent = (PendingIntent) intent
.getParcelableExtra(EXTRA_PENDING_INTENT);
return startDownloadServiceIfRequired(context, pendingIntent,
serviceClass);
}
public static int startDownloadServiceIfRequired(Context context,
PendingIntent pendingIntent, Class<?> serviceClass)
throws NameNotFoundException
{
String packageName = context.getPackageName();
String className = serviceClass.getName();
return startDownloadServiceIfRequired(context, pendingIntent,
packageName, className);
}
/**
* Starts the download if necessary. This function starts a flow that does `
* many things. 1) Checks to see if the APK version has been checked and the
@ -654,14 +621,18 @@ public abstract class DownloaderService extends CustomIntentService implements I
* network connection, even if Market delivers all of the files.
*
* @param context
* @param channelId The Channel ID to use for download progress
* notifications on Android O+
* @param pendingIntent
* @return true if the app should wait for more guidance from the
* downloader, false if the app can continue
* @throws NameNotFoundException
*/
public static int startDownloadServiceIfRequired(Context context,
PendingIntent pendingIntent, String classPackage, String className)
String channelId,
PendingIntent pendingIntent, byte[] salt, String publicKey)
throws NameNotFoundException {
// first: do we need to do an LVL update?
// we begin by getting our APK version from the package manager
final PackageInfo pi = context.getPackageManager().getPackageInfo(
@ -696,10 +667,12 @@ public abstract class DownloaderService extends CustomIntentService implements I
switch (status) {
case DOWNLOAD_REQUIRED:
case LVL_CHECK_REQUIRED:
Intent fileIntent = new Intent();
fileIntent.setClassName(classPackage, className);
fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
context.startService(fileIntent);
Intent downloadIntent = new Intent(context, DownloaderService.class);
downloadIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
downloadIntent.putExtra(EXTRA_CHANNEL_ID, channelId);
downloadIntent.putExtra(EXTRA_SALT, salt);
downloadIntent.putExtra(EXTRA_PUBLIC_KEY, publicKey);
context.startService(downloadIntent);
break;
}
return status;
@ -732,19 +705,19 @@ public abstract class DownloaderService extends CustomIntentService implements I
this.startService(fileIntent);
}
public abstract String getPublicKey();
public abstract byte[] getSALT();
public abstract String getAlarmReceiverClassName();
private class LVLRunnable implements Runnable {
LVLRunnable(Context context, PendingIntent intent) {
mContext = context;
mPendingIntent = intent;
}
final Context mContext;
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
public void run() {
@ -754,7 +727,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
Secure.ANDROID_ID);
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
// 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
// Construct the LicenseChecker with a Policy.
final LicenseChecker checker = new LicenseChecker(mContext, aep,
getPublicKey() // Your public licensing key.
mPublicKey // Your public licensing key.
);
checker.checkAccess(new LicenseCheckerCallback() {
@ -829,9 +802,8 @@ public abstract class DownloaderService extends CustomIntentService implements I
pi = mContext.getPackageManager().getPackageInfo(
mContext.getPackageName(), 0);
db.updateMetadata(pi.versionCode, status);
Class<?> serviceClass = DownloaderService.this.getClass();
switch (startDownloadServiceIfRequired(mContext, mPendingIntent,
serviceClass)) {
Class<? extends DownloaderService> serviceClass = DownloaderService.this.getClass();
switch (startDownloadServiceIfRequired(mContext, mChannelId, mPendingIntent, mSalt, mPublicKey)) {
case NO_DOWNLOAD_REQUIRED:
mNotification
.onDownloadStateChanged(IDownloaderClient.STATE_COMPLETED);
@ -900,10 +872,10 @@ public abstract class DownloaderService extends CustomIntentService implements I
*
* @param context
*/
public void updateLVL(final Context context) {
public void updateLVL(final Context context, String channelId, byte[] salt, String publicKey) {
Context c = context.getApplicationContext();
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);
}
private void scheduleAlarm(long wakeUp) {
private void scheduleAlarm(long wakeUp, boolean repeated, Bundle callerExtras) {
AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
if (alarms == null) {
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");
}
String className = getAlarmReceiverClassName();
Intent intent = new Intent(Constants.ACTION_RETRY);
intent.putExtra(EXTRA_PENDING_INTENT, mPendingIntent);
intent.setClassName(this.getPackageName(),
className);
// put original extras to the wake up intent
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setAction(Constants.ACTION_RETRY);
intent.putExtras(callerExtras);
mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
alarms.set(
AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + wakeUp, mAlarmIntent
);
PendingIntent.FLAG_CANCEL_CURRENT);
if (repeated) {
alarms.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + wakeUp, wakeUp, mAlarmIntent);
return;
}
alarms.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + wakeUp, mAlarmIntent);
}
private void cancelAlarms() {
@ -998,7 +975,31 @@ public abstract class DownloaderService extends CustomIntentService implements I
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
@ -1011,11 +1012,14 @@ public abstract class DownloaderService extends CustomIntentService implements I
// the database automatically reads the metadata for version code
// and download status when the instance is created
DownloadsDB db = DownloadsDB.getDB(this);
final PendingIntent pendingIntent = (PendingIntent) intent
.getParcelableExtra(EXTRA_PENDING_INTENT);
final PendingIntent pendingIntent = 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);
mPendingIntent = pendingIntent;
} 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
// the service
if (isLVLCheckRequired(db, mPackageInfo)) {
updateLVL(this);
updateLVL(this, channelId, salt, publicKey);
return;
}
@ -1074,7 +1078,8 @@ public abstract class DownloaderService extends CustomIntentService implements I
if (info.mStatus != STATUS_SUCCESS) {
DownloadThread dt = new DownloadThread(info, this, mNotification);
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();
cancelAlarms();
}
@ -1084,7 +1089,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
switch (info.mStatus) {
case STATUS_FORBIDDEN:
// the URL is out of date
updateLVL(this);
updateLVL(this, channelId, salt, publicKey);
return;
case STATUS_SUCCESS:
mBytesSoFar += info.mCurrentBytes - startingCount;
@ -1139,7 +1144,7 @@ public abstract class DownloaderService extends CustomIntentService implements I
break;
}
if (setWakeWatchdog) {
scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER);
scheduleAlarm(Constants.WATCHDOG_WAKE_TIMER, false, intent.getExtras());
} else {
cancelAlarms();
}
@ -1161,7 +1166,6 @@ public abstract class DownloaderService extends CustomIntentService implements I
unregisterReceiver(mConnReceiver);
mConnReceiver = null;
}
mServiceStub.disconnect(this);
super.onDestroy();
}
@ -1331,11 +1335,4 @@ public abstract class DownloaderService extends CustomIntentService implements I
public void requestDownloadStatus() {
mNotification.resendState();
}
@Override
public void onClientUpdated(Messenger clientMessenger) {
this.mClientMessenger = clientMessenger;
mNotification.setMessenger(mClientMessenger);
}
}

View File

@ -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;
}
}
}

View File

@ -33,8 +33,8 @@ import java.util.Vector;
/**
* Default policy. All policy decisions are based off of response data received
* from the licensing service. Specifically, the licensing server sends the
* following information: response validity period, error retry period,
* error retry count and a URL for restoring app access in unlicensed cases.
* following information: response validity period, error retry period, and
* error retry count.
* <p>
* 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
@ -53,7 +53,6 @@ public class APKExpansionPolicy implements Policy {
private static final String PREF_RETRY_UNTIL = "retryUntil";
private static final String PREF_MAX_RETRIES = "maxRetries";
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_RETRY_UNTIL = "0";
private static final String DEFAULT_MAX_RETRIES = "0";
@ -67,7 +66,6 @@ public class APKExpansionPolicy implements Policy {
private long mRetryCount;
private long mLastResponseTime = 0;
private int mLastResponse;
private String mLicensingUrl;
private PreferenceObfuscator mPreferences;
private Vector<String> mExpansionURLs = 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));
mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
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
* <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>LU: a deep link URL that can enable access for unlicensed apps (e.g.
* buy app on the Play Store)
* </ul>
*
* @param response the result from validating the server response
@ -139,12 +134,10 @@ public class APKExpansionPolicy implements Policy {
setRetryCount(mRetryCount + 1);
}
// Update server policy data
Map<String, String> extras = decodeExtras(rawData);
if (response == Policy.LICENSED) {
// Update server policy data
Map<String, String> extras = decodeExtras(rawData.extra);
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));
Set<String> keys = extras.keySet();
for (String key : keys) {
@ -166,12 +159,10 @@ public class APKExpansionPolicy implements Policy {
}
}
} else if (response == Policy.NOT_LICENSED) {
// Clear out stale retry params
// Clear out stale policy data
setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
setRetryUntil(DEFAULT_RETRY_UNTIL);
setMaxRetries(DEFAULT_MAX_RETRIES);
// Update the licensing URL
setLicensingUrl(extras.get("LU"));
}
setLastResponse(response);
@ -284,20 +275,6 @@ public class APKExpansionPolicy implements Policy {
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
* to preferences, this will return zero if there has been no LVL fetch
@ -395,15 +372,10 @@ public class APKExpansionPolicy implements Policy {
return false;
}
private Map<String, String> decodeExtras(
com.google.android.vending.licensing.ResponseData rawData) {
private Map<String, String> decodeExtras(String extras) {
Map<String, String> results = new HashMap<String, String>();
if (rawData == null) {
return results;
}
try {
URI rawExtras = new URI("?" + rawData.extra);
URI rawExtras = new URI("?" + extras);
URIQueryDecoder.DecodeQuery(rawExtras, results);
} catch (URISyntaxException e) {
Log.w(TAG, "Invalid syntax error while decoding extras data from server.");

View File

@ -21,7 +21,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
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() {
LicenseValidator validator;
while ((validator = mPendingChecks.poll()) != null) {

View File

@ -39,9 +39,9 @@ public interface Obfuscator {
/**
* Undo the transformation applied to data by the obfuscate() method.
*
* @param obfuscated The data that is to be un-obfuscated.
* @param key The key for the data that is to be un-obfuscated.
* @return The original data transformed by the obfuscate() method.
* @param original The data that is to be obfuscated.
* @param key The key for the data that is to be obfuscated.
* @return A transformed version of the original data.
* @throws ValidationException Optionally thrown if a data integrity check fails.
*/
String unobfuscate(String obfuscated, String key) throws ValidationException;

View File

@ -56,10 +56,4 @@ public interface Policy {
* Check if the user should be allowed access to the application.
*/
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();
}

View File

@ -30,8 +30,8 @@ import com.google.android.vending.licensing.util.URIQueryDecoder;
/**
* Default policy. All policy decisions are based off of response data received
* from the licensing service. Specifically, the licensing server sends the
* following information: response validity period, error retry period,
* error retry count and a URL for restoring app access in unlicensed cases.
* following information: response validity period, error retry period, and
* error retry count.
* <p>
* 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
@ -50,7 +50,6 @@ public class ServerManagedPolicy implements Policy {
private static final String PREF_RETRY_UNTIL = "retryUntil";
private static final String PREF_MAX_RETRIES = "maxRetries";
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_RETRY_UNTIL = "0";
private static final String DEFAULT_MAX_RETRIES = "0";
@ -64,7 +63,6 @@ public class ServerManagedPolicy implements Policy {
private long mRetryCount;
private long mLastResponseTime = 0;
private int mLastResponse;
private String mLicensingUrl;
private PreferenceObfuscator mPreferences;
/**
@ -82,7 +80,6 @@ public class ServerManagedPolicy implements Policy {
mRetryUntil = Long.parseLong(mPreferences.getString(PREF_RETRY_UNTIL, DEFAULT_RETRY_UNTIL));
mMaxRetries = Long.parseLong(mPreferences.getString(PREF_MAX_RETRIES, DEFAULT_MAX_RETRIES));
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
* following parameters are processed:
* <ul>
* <li>VT: the timestamp that the client should consider the response valid
* until
* <li>VT: the timestamp that the client should consider the response
* valid 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>LU: a deep link URL that can enable access for unlicensed apps (e.g.
* buy app on the Play Store)
* </ul>
*
* @param response the result from validating the server response
@ -111,22 +106,18 @@ public class ServerManagedPolicy implements Policy {
setRetryCount(mRetryCount + 1);
}
// Update server policy data
Map<String, String> extras = decodeExtras(rawData);
if (response == Policy.LICENSED) {
// Update server policy data
Map<String, String> extras = decodeExtras(rawData.extra);
mLastResponse = response;
// Reset the licensing URL since it is only applicable for NOT_LICENSED responses.
setLicensingUrl(null);
setValidityTimestamp(extras.get("VT"));
setRetryUntil(extras.get("GT"));
setMaxRetries(extras.get("GR"));
} else if (response == Policy.NOT_LICENSED) {
// Clear out stale retry params
// Clear out stale policy data
setValidityTimestamp(DEFAULT_VALIDITY_TIMESTAMP);
setRetryUntil(DEFAULT_RETRY_UNTIL);
setMaxRetries(DEFAULT_MAX_RETRIES);
// Update the licensing URL
setLicensingUrl(extras.get("LU"));
}
setLastResponse(response);
@ -239,21 +230,6 @@ public class ServerManagedPolicy implements Policy {
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}
*
@ -281,15 +257,10 @@ public class ServerManagedPolicy implements Policy {
return false;
}
private Map<String, String> decodeExtras(
com.google.android.vending.licensing.ResponseData rawData) {
private Map<String, String> decodeExtras(String extras) {
Map<String, String> results = new HashMap<String, String>();
if (rawData == null) {
return results;
}
try {
URI rawExtras = new URI("?" + rawData.extra);
URI rawExtras = new URI("?" + extras);
URIQueryDecoder.DecodeQuery(rawExtras, results);
} catch (URISyntaxException e) {
Log.w(TAG, "Invalid syntax error while decoding extras data from server.");

View File

@ -16,13 +16,6 @@
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,
* 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,
* such as ServerManagedPolicy.
* <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.
*/
public class StrictPolicy implements Policy {
private static final String TAG = "StrictPolicy";
private int mLastResponse;
private String mLicensingUrl;
public StrictPolicy() {
// Set default policy. This will force the application to check the policy on launch.
mLastResponse = Policy.RETRY;
mLicensingUrl = null;
}
/**
* Process a new response from the license server. Since we aren't
* performing any caching, this equates to reading the LicenseResponse.
* Any cache-related ResponseData is ignored, but the licensing URL
* extra is still extracted in cases where the app is unlicensed.
* Any ResponseData provided is ignored.
*
* @param response the result from validating the server response
* @param rawData the raw server response data
*/
public void processServerResponse(int response, ResponseData rawData) {
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);
}
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;
}
}

View File

@ -64,8 +64,8 @@
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<service android:name="com.falsinsoft.QtAndroidTools.ApkExpansionDownloaderService"/>
<receiver android:name="com.falsinsoft.QtAndroidTools.ApkExpansionDownloaderAlarmReceiver"/>
<service android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService" android:enabled="true"/>
<receiver android:name="com.google.android.vending.expansion.downloader.impl.DownloaderService$AlarmReceiver" android:enabled="true"/>
</application>