summaryrefslogtreecommitdiffstats
path: root/examples/demos/hangman/purchasing/android
diff options
context:
space:
mode:
authorJani Korteniemi <jani.korteniemi@qt.io>2021-07-26 13:42:14 +0300
committerJani Korteniemi <jani.korteniemi@qt.io>2021-11-10 13:46:03 +0200
commit2eff5e431435f2b5be3511823400080d4029a6c1 (patch)
tree7d4647e00bdb463bc4bb29b85b445745bc18c0f3 /examples/demos/hangman/purchasing/android
parent28dd0b133435e044224c3141265e14f2a2586efc (diff)
Add QtPurchasing module as an example under Qt demos
QtPurchasing module is deprecated in Qt6. However, the purchasing code is moved as example/demo under qtdoc to demonstrate the use of in-app purchases in Android/iOS. The demo is under demos\hangman. Few fixes done to the original code: * Ported the code to Qt 6. * Removed reinterpret_casts and QQmlListPropertys. * Fixed few QML warnings. * Added documentation * Fixed documentation * Added CMake port * Fixed Cmake for iOS * Modified Fonts in qml file * project name changed from qthangmanpurchasing to hangman Task-number: QTBUG-84776 Pick-to: 6.2 Change-Id: I86051b29d54cfb4a48b310ebc8d538c806fbf8da Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Nicholas Bennett <nicholas.bennett@qt.io> Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
Diffstat (limited to 'examples/demos/hangman/purchasing/android')
-rw-r--r--examples/demos/hangman/purchasing/android/androidinappproduct.cpp71
-rw-r--r--examples/demos/hangman/purchasing/android/androidinappproduct.h76
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp332
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h145
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapptransaction.cpp124
-rw-r--r--examples/demos/hangman/purchasing/android/androidinapptransaction.h90
-rw-r--r--examples/demos/hangman/purchasing/android/androidjni.cpp153
-rw-r--r--examples/demos/hangman/purchasing/android/build.gradle79
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java572
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java34
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java375
-rw-r--r--examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java131
12 files changed, 2182 insertions, 0 deletions
diff --git a/examples/demos/hangman/purchasing/android/androidinappproduct.cpp b/examples/demos/hangman/purchasing/android/androidinappproduct.cpp
new file mode 100644
index 000000000..8c968eeee
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinappproduct.cpp
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "androidinappproduct.h"
+#include "androidinapppurchasebackend.h"
+
+QT_BEGIN_NAMESPACE
+
+AndroidInAppProduct::AndroidInAppProduct(AndroidInAppPurchaseBackend *backend,
+ const QString &price,
+ const QString &title,
+ const QString &description,
+ ProductType productType,
+ const QString &identifier,
+ QObject *parent)
+ : InAppProduct(price, title, description, productType, identifier, parent)
+ , m_backend(backend)
+{
+}
+
+void AndroidInAppProduct::purchase()
+{
+ m_backend->purchaseProduct(this);
+}
diff --git a/examples/demos/hangman/purchasing/android/androidinappproduct.h b/examples/demos/hangman/purchasing/android/androidinappproduct.h
new file mode 100644
index 000000000..1e7b6eb7f
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinappproduct.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ANDROIDINAPPPRODUCT_H
+#define ANDROIDINAPPPRODUCT_H
+
+#include "../inapp/inappproduct.h"
+
+class AndroidInAppPurchaseBackend;
+class AndroidInAppProduct : public InAppProduct
+{
+ Q_OBJECT
+public:
+ explicit AndroidInAppProduct(AndroidInAppPurchaseBackend *backend,
+ const QString &price,
+ const QString &title,
+ const QString &description,
+ ProductType productType,
+ const QString &identifier,
+ QObject *parent = 0);
+
+ QString getProductId();
+ void purchase();
+
+private:
+ AndroidInAppPurchaseBackend *m_backend;
+};
+
+#endif // ANDROIDINAPPPRODUCT_H
diff --git a/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp
new file mode 100644
index 000000000..63470137c
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.cpp
@@ -0,0 +1,332 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QDebug>
+
+#include <QJniEnvironment>
+#include <QtCore>
+
+#include "androidinapppurchasebackend.h"
+#include "androidinappproduct.h"
+#include "androidinapptransaction.h"
+#include "../inapp/inappstore.h"
+
+
+QT_BEGIN_NAMESPACE
+
+AndroidInAppPurchaseBackend::AndroidInAppPurchaseBackend(QObject *parent)
+ : InAppPurchaseBackend(parent)
+ , m_isReady(false)
+{
+ m_javaObject = QJniObject("org/qtproject/qt/android/purchasing/InAppPurchase",
+ "(Landroid/content/Context;J)V",
+ QNativeInterface::QAndroidApplication::context(),
+ this);
+
+ if (!m_javaObject.isValid()) {
+ qWarning("Cannot initialize IAP backend for Android due to missing dependency: InAppPurchase class");
+ return;
+ }
+}
+
+void AndroidInAppPurchaseBackend::initialize()
+{
+ m_javaObject.callMethod<void>("initializeConnection");
+}
+
+bool AndroidInAppPurchaseBackend::isReady() const
+{
+ QMutexLocker locker(&m_mutex);
+ return m_isReady;
+}
+
+void AndroidInAppPurchaseBackend::restorePurchases()
+{
+ for (const QString &purchasedUnlockeble : purchasedUnlockebles) {
+ InAppProduct *product = store()->registeredProduct(purchasedUnlockeble);
+ Q_ASSERT(product != 0);
+
+ checkFinalizationStatus(product, InAppTransaction::PurchaseRestored);
+ }
+
+}
+
+void AndroidInAppPurchaseBackend::queryProducts(const QList<Product> &products)
+{
+ QMutexLocker locker(&m_mutex);
+ QJniEnvironment environment;
+
+ QStringList newProducts;
+ for (int i = 0; i < products.size(); ++i) {
+ const Product &product = products.at(i);
+ if (m_productTypeForPendingId.contains(product.identifier)) {
+ qWarning("Product query already pending for %s", qPrintable(product.identifier));
+ continue;
+ }
+ else{
+ m_productTypeForPendingId[product.identifier] = product.productType;
+ newProducts.append(product.identifier);
+ }
+ }
+
+ if (newProducts.isEmpty())
+ return;
+
+ jclass cls = environment->FindClass("java/lang/String");
+ jobjectArray productIds = environment->NewObjectArray(newProducts.size(), cls, 0);
+ environment->DeleteLocalRef(cls);
+
+ for (int i = 0; i < newProducts.size(); ++i) {
+ QJniObject identifier = QJniObject::fromString(newProducts.at(i));
+ environment->SetObjectArrayElement(productIds, i, identifier.object());
+ }
+
+ m_javaObject.callMethod<void>("queryDetails",
+ "([Ljava/lang/String;)V",
+ productIds);
+ environment->DeleteLocalRef(productIds);
+}
+
+void AndroidInAppPurchaseBackend::queryProduct(InAppProduct::ProductType productType,
+ const QString &identifier)
+{
+ queryProducts(QList<Product>() << Product(productType, identifier));
+}
+
+void AndroidInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ QMutexLocker locker(&m_mutex);
+ if (propertyName.compare(QStringLiteral("AndroidPublicKey"), Qt::CaseInsensitive) == 0) {
+ m_javaObject.callMethod<void>("setPublicKey",
+ "(Ljava/lang/String;)V",
+ QJniObject::fromString(value).object<jstring>());
+ }
+}
+
+void AndroidInAppPurchaseBackend::consumeTransaction(const QString &purchaseToken)
+{
+ QMutexLocker locker(&m_mutex);
+ m_javaObject.callMethod<void>("consumePurchase",
+ "(Ljava/lang/String;)V",
+ QJniObject::fromString(purchaseToken).object<jstring>());
+}
+
+void AndroidInAppPurchaseBackend::registerFinalizedUnlockable(const QString &purchaseToken)
+{
+ QMutexLocker locker(&m_mutex);
+ m_javaObject.callMethod<void>("acknowledgeUnlockablePurchase",
+ "(Ljava/lang/String;)V",
+ QJniObject::fromString(purchaseToken).object<jstring>());
+}
+
+bool AndroidInAppPurchaseBackend::transactionFinalizedForProduct(InAppProduct *product)
+{
+ Q_ASSERT(m_infoForPurchase.contains(product->identifier()));
+
+ if (product->productType() != InAppProduct::Consumable && purchasedUnlockebles.contains(product->identifier()))
+ {
+ return true;
+ }
+ return false;
+}
+
+void AndroidInAppPurchaseBackend::checkFinalizationStatus(InAppProduct *product,
+ InAppTransaction::TransactionStatus status)
+{
+ // Verifies the finalization status of an item based on the following logic:
+ // 1. If the item is not purchased yet, do nothing (it's either never been purchased, or it's a
+ // consumed consumable.
+ // 2. If the item is purchased, and it's a consumable, it's unfinalized. Emit a new transaction.
+ // Consumable items are consumed when they are finalized.
+ // 3. If the item is purchased, and it's an unlockable, check the local cache for finalized
+ // unlockable purchases. If it's not there, then the transaction is unfinalized. This means
+ // that if the cache gets deleted or corrupted, the worst-case scenario is that the transactions
+ // are republished.
+ QHash<QString, PurchaseInfo>::iterator it = m_infoForPurchase.find(product->identifier());
+
+ if (it == m_infoForPurchase.end()) {
+ return;
+ }
+
+ const PurchaseInfo &info = it.value();
+ if (transactionFinalizedForProduct(product)) {
+ AndroidInAppTransaction *transaction = new AndroidInAppTransaction(info.signature,
+ info.data,
+ info.purchaseToken,
+ info.orderId,
+ status,
+ product,
+ info.timestamp,
+ InAppTransaction::NoFailure,
+ QString(),
+ this);
+ emit transactionReady(transaction);
+ }
+}
+
+void AndroidInAppPurchaseBackend::registerProduct(const QString &productId,
+ const QString &price,
+ const QString &title,
+ const QString &description)
+{
+ QMutexLocker locker(&m_mutex);
+ QHash<QString, InAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(productId);
+ Q_ASSERT(it != m_productTypeForPendingId.end());
+
+ AndroidInAppProduct *product = new AndroidInAppProduct(this, price, title, description, it.value(), it.key(), this);
+
+ emit productQueryDone(product);
+}
+
+void AndroidInAppPurchaseBackend::registerPurchased(const QString &identifier,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp)
+{
+ QMutexLocker locker(&m_mutex);
+ m_infoForPurchase.insert(identifier, PurchaseInfo(signature, data, purchaseToken, orderId, timestamp));
+
+ QHash<QString, InAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(identifier);
+ if (it.value() == InAppProduct::Unlockable && !purchasedUnlockebles.contains(identifier))
+ purchasedUnlockebles.append(identifier);
+
+
+}
+
+void AndroidInAppPurchaseBackend::registerReady()
+{
+ QMutexLocker locker(&m_mutex);
+ m_isReady = true;
+ emit ready();
+}
+
+void AndroidInAppPurchaseBackend::purchaseProduct(AndroidInAppProduct *product)
+{
+ QMutexLocker locker(&m_mutex);
+ if (!m_javaObject.isValid()) {
+ purchaseFailed(product, InAppTransaction::ErrorOccurred, QStringLiteral("Java backend is not initialized"));
+ return;
+ }
+
+ int requestCode = 0;
+ while (m_activePurchaseRequests.contains(requestCode)) {
+ requestCode++;
+ if (requestCode == 0) {
+ qWarning("No available request code for purchase request.");
+ return;
+ }
+ }
+
+ m_activePurchaseRequests[requestCode] = product;
+
+ m_javaObject.callMethod<void>("launchBillingFlow",
+ "(Ljava/lang/String;I)V",
+ QJniObject::fromString(product->identifier()).object<jstring>(),
+ requestCode);
+}
+
+void AndroidInAppPurchaseBackend::purchaseFailed(int requestCode, int failureReason, const QString &errorString)
+{
+ QMutexLocker locker(&m_mutex);
+ InAppProduct *product = m_activePurchaseRequests.take(requestCode);
+ if (product == 0) {
+ qWarning("No product registered for requestCode %d", requestCode);
+ return;
+ }
+
+ purchaseFailed(product, failureReason, errorString);
+}
+
+void AndroidInAppPurchaseBackend::purchaseFailed(InAppProduct *product, int failureReason, const QString &errorString)
+{
+ InAppTransaction *transaction = new AndroidInAppTransaction(QString(),
+ QString(),
+ QString(),
+ QString(),
+ InAppTransaction::PurchaseFailed,
+ product,
+ QDateTime(),
+ InAppTransaction::FailureReason(failureReason),
+ errorString,
+ this);
+ emit transactionReady(transaction);
+}
+
+void AndroidInAppPurchaseBackend::purchaseSucceeded(int requestCode,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp)
+
+{
+ QMutexLocker locker(&m_mutex);
+ InAppProduct *product = m_activePurchaseRequests.take(requestCode);
+ if (product == 0) {
+ qWarning("No product registered for requestCode %d", requestCode);
+ return;
+ }
+
+ m_infoForPurchase.insert(product->identifier(), PurchaseInfo(signature, data, purchaseToken, orderId, timestamp));
+ InAppTransaction *transaction = new AndroidInAppTransaction(signature,
+ data,
+ purchaseToken,
+ orderId,
+ InAppTransaction::PurchaseApproved,
+ product,
+ timestamp,
+ InAppTransaction::NoFailure,
+ QString(),
+ this);
+ emit transactionReady(transaction);
+}
+QT_END_NAMESPACE
diff --git a/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h
new file mode 100644
index 000000000..0c66fb5e3
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapppurchasebackend.h
@@ -0,0 +1,145 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ANDROIDINAPPPURCHASEBACKEND_H
+#define ANDROIDINAPPPURCHASEBACKEND_H
+
+#include <QMutex>
+#include <QSet>
+#include <QDateTime>
+#include <QJniObject>
+#include <QJniEnvironment>
+
+#include "../inapp/inapppurchasebackend.h"
+#include "../inapp/inappproduct.h"
+#include "../inapp/inapptransaction.h"
+
+QT_BEGIN_NAMESPACE
+class AndroidInAppProduct;
+class AndroidInAppPurchaseBackend : public InAppPurchaseBackend
+{
+ Q_OBJECT
+public:
+ explicit AndroidInAppPurchaseBackend(QObject *parent = 0);
+
+ void initialize();
+ bool isReady() const;
+
+ void queryProducts(const QList<Product> &products);
+ void queryProduct(InAppProduct::ProductType productType, const QString &identifier);
+ void restorePurchases();
+
+ void setPlatformProperty(const QString &propertyName, const QString &value);
+
+ void purchaseProduct(AndroidInAppProduct *product);
+
+ void consumeTransaction(const QString &purchaseToken);
+ void registerFinalizedUnlockable(const QString &identifier);
+
+ // Callbacks from Java
+ Q_INVOKABLE void registerProduct(const QString &productId,
+ const QString &price,
+ const QString &title,
+ const QString &description);
+ Q_INVOKABLE void registerPurchased(const QString &identifier,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp);
+ Q_INVOKABLE void purchaseSucceeded(int requestCode,
+ const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ const QDateTime &timestamp);
+ Q_INVOKABLE void purchaseFailed(int requestCode,
+ int failureReason,
+ const QString &errorString);
+ Q_INVOKABLE void registerReady();
+
+private:
+ void checkFinalizationStatus(InAppProduct *product,
+ InAppTransaction::TransactionStatus status = InAppTransaction::PurchaseApproved);
+ bool transactionFinalizedForProduct(InAppProduct *product);
+ void purchaseFailed(InAppProduct *product,
+ int failureReason,
+ const QString &errorString);
+
+ struct PurchaseInfo
+ {
+ PurchaseInfo(const QString &signature_, const QString &data_, const QString &purchaseToken_, const QString &orderId_, const QDateTime &timestamp_)
+ : signature(signature_)
+ , data(data_)
+ , purchaseToken(purchaseToken_)
+ , orderId(orderId_)
+ , timestamp(timestamp_)
+ {
+ }
+
+ QString signature;
+ QString data;
+ QString purchaseToken;
+ QString orderId;
+ QDateTime timestamp;
+ };
+
+ mutable QRecursiveMutex m_mutex;
+ bool m_isReady;
+ QList<QString> purchasedUnlockebles;
+ QJniObject m_javaObject;
+ QScopedPointer<AndroidInAppPurchaseBackend> d;
+ QHash<QString, InAppProduct::ProductType> m_productTypeForPendingId;
+ QHash<QString, PurchaseInfo> m_infoForPurchase;
+ QHash<int, InAppProduct *> m_activePurchaseRequests;
+};
+QT_END_NAMESPACE
+
+#endif // ANDROIDINAPPPURCHASEBACKEND_H
diff --git a/examples/demos/hangman/purchasing/android/androidinapptransaction.cpp b/examples/demos/hangman/purchasing/android/androidinapptransaction.cpp
new file mode 100644
index 000000000..397336f2b
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapptransaction.cpp
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "../inapp/inappproduct.h"
+#include "androidinapptransaction.h"
+#include "androidinapppurchasebackend.h"
+
+QT_BEGIN_NAMESPACE
+
+AndroidInAppTransaction::AndroidInAppTransaction(const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ TransactionStatus status,
+ InAppProduct *product,
+ const QDateTime &timestamp,
+ FailureReason failureReason,
+ const QString &errorString,
+ QObject *parent)
+ : InAppTransaction(status, product, parent)
+ , m_signature(signature)
+ , m_data(data)
+ , m_purchaseToken(purchaseToken)
+ , m_orderId(orderId)
+ , m_timestamp(timestamp)
+ , m_errorString(errorString)
+ , m_failureReason(failureReason)
+{
+ Q_ASSERT(qobject_cast<AndroidInAppPurchaseBackend *>(parent) != 0);
+}
+
+QString AndroidInAppTransaction::orderId() const
+{
+ return m_orderId;
+}
+
+QDateTime AndroidInAppTransaction::timestamp() const
+{
+ return m_timestamp;
+}
+
+QString AndroidInAppTransaction::errorString() const
+{
+ return m_errorString;
+}
+
+InAppTransaction::FailureReason AndroidInAppTransaction::failureReason() const
+{
+ return m_failureReason;
+}
+
+QString AndroidInAppTransaction::platformProperty(const QString &propertyName) const
+{
+ if (propertyName.compare(QStringLiteral("AndroidSignature"), Qt::CaseInsensitive) == 0)
+ return m_signature;
+ else if (propertyName.compare(QStringLiteral("AndroidPurchaseData"), Qt::CaseInsensitive) == 0)
+ return m_data;
+ else
+ return InAppTransaction::platformProperty(propertyName);
+}
+
+void AndroidInAppTransaction::finalize()
+{
+ AndroidInAppPurchaseBackend *backend = qobject_cast<AndroidInAppPurchaseBackend *>(parent());
+ if (status() == PurchaseApproved || status() == PurchaseRestored) {
+ if (product()->productType() == InAppProduct::Consumable){
+ backend->consumeTransaction(m_purchaseToken);}
+ else if (product()->productType() == InAppProduct::Unlockable){
+ backend->registerFinalizedUnlockable(m_purchaseToken);}
+ else {
+ qWarning("Product type not implemented.");
+ }
+ }
+
+ deleteLater();
+}
+QT_END_NAMESPACE
diff --git a/examples/demos/hangman/purchasing/android/androidinapptransaction.h b/examples/demos/hangman/purchasing/android/androidinapptransaction.h
new file mode 100644
index 000000000..5bba856eb
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidinapptransaction.h
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef ANDROIDINAPPTRANSACTION_H
+#define ANDROIDINAPPTRANSACTION_H
+
+
+#include "../inapp/inapptransaction.h"
+
+class AndroidInAppTransaction : public InAppTransaction
+{
+ Q_OBJECT
+public:
+ explicit AndroidInAppTransaction(const QString &signature,
+ const QString &data,
+ const QString &purchaseToken,
+ const QString &orderId,
+ TransactionStatus status,
+ InAppProduct *product,
+ const QDateTime &timestamp,
+ FailureReason failureReason,
+ const QString &errorString,
+ QObject *parent = 0);
+
+ void finalize();
+
+ QString orderId() const;
+ QString errorString() const;
+ FailureReason failureReason() const;
+ QDateTime timestamp() const;
+ QString platformProperty(const QString &propertyName) const;
+
+private:
+ QString m_signature;
+ QString m_data;
+ QString m_purchaseToken;
+ QString m_orderId;
+ QDateTime m_timestamp;
+ QString m_errorString;
+ FailureReason m_failureReason;
+};
+
+#endif // ANDROIDINAPPTRANSACTION_H
diff --git a/examples/demos/hangman/purchasing/android/androidjni.cpp b/examples/demos/hangman/purchasing/android/androidjni.cpp
new file mode 100644
index 000000000..03e6d752b
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/androidjni.cpp
@@ -0,0 +1,153 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qdatetime.h>
+#include <jni.h>
+#include <QJniObject>
+
+#include "androidinapppurchasebackend.h"
+
+QT_USE_NAMESPACE
+
+static void purchasedProductsQueried(JNIEnv *, jclass, jlong nativePointer)
+{
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "registerReady",
+ Qt::AutoConnection);
+}
+
+static void registerProduct(JNIEnv *, jclass, jlong nativePointer, jstring productId, jstring price, jstring title, jstring description)
+{
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "registerProduct",
+ Qt::AutoConnection,
+ Q_ARG(QString, QJniObject(productId).toString()),
+ Q_ARG(QString, QJniObject(price).toString()),
+ Q_ARG(QString, QJniObject(title).toString()),
+ Q_ARG(QString, QJniObject(description).toString()));
+}
+
+static void registerPurchased(JNIEnv *, jclass, jlong nativePointer, jstring identifier,
+ jstring signature, jstring data, jstring purchaseToken, jstring orderId, jlong timestamp)
+{
+ QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(qint64(timestamp));
+ dateTime.setTimeSpec(Qt::LocalTime);
+
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "registerPurchased",
+ Qt::AutoConnection,
+ Q_ARG(QString, QJniObject(identifier).toString()),
+ Q_ARG(QString, QJniObject(signature).toString()),
+ Q_ARG(QString, QJniObject(data).toString()),
+ Q_ARG(QString, QJniObject(purchaseToken).toString()),
+ Q_ARG(QString, QJniObject(orderId).toString()),
+ Q_ARG(QDateTime, dateTime));
+}
+
+static void purchaseSucceeded(JNIEnv *, jclass, jlong nativePointer, jint requestCode,
+ jstring signature, jstring data, jstring purchaseToken, jstring orderId, jlong timestamp)
+{
+ QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(qint64(timestamp));
+ dateTime.setTimeSpec(Qt::LocalTime);
+
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "purchaseSucceeded",
+ Qt::AutoConnection,
+ Q_ARG(int, int(requestCode)),
+ Q_ARG(QString, QJniObject(signature).toString()),
+ Q_ARG(QString, QJniObject(data).toString()),
+ Q_ARG(QString, QJniObject(purchaseToken).toString()),
+ Q_ARG(QString, QJniObject(orderId).toString()),
+ Q_ARG(QDateTime, dateTime));
+}
+
+static void purchaseFailed(JNIEnv *, jclass, jlong nativePointer, jint requestCode, jint failureReason, jstring errorString)
+{
+ AndroidInAppPurchaseBackend *backend = reinterpret_cast<AndroidInAppPurchaseBackend *>(nativePointer);
+ QMetaObject::invokeMethod(backend,
+ "purchaseFailed",
+ Qt::AutoConnection,
+ Q_ARG(int, int(requestCode)),
+ Q_ARG(int, int(failureReason)),
+ Q_ARG(QString, QJniObject(errorString).toString()));
+}
+
+static JNINativeMethod methods[] = {
+ {"purchasedProductsQueried", "(J)V", (void *)purchasedProductsQueried},
+ {"registerProduct", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", (void *)registerProduct},
+ {"registerPurchased", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V", (void *)registerPurchased},
+ {"purchaseSucceeded", "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V", (void *)purchaseSucceeded},
+ {"purchaseFailed", "(JIILjava/lang/String;)V", (void *)purchaseFailed}
+};
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
+{
+ static bool initialized = false;
+ if (initialized){
+ return JNI_VERSION_1_6;}
+ initialized = true;
+
+ JNIEnv *env;
+ if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK){
+ return JNI_ERR;}
+
+ jclass clazz = env->FindClass("org/qtproject/qt/android/purchasing/InAppPurchase");
+ if (!clazz){
+ return JNI_ERR;}
+
+ if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0){
+ return JNI_ERR;}
+
+ return JNI_VERSION_1_6;
+}
diff --git a/examples/demos/hangman/purchasing/android/build.gradle b/examples/demos/hangman/purchasing/android/build.gradle
new file mode 100644
index 000000000..b33486e1c
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/build.gradle
@@ -0,0 +1,79 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:4.1.3'
+ }
+}
+
+repositories {
+ google()
+ mavenCentral()
+}
+
+apply plugin: 'com.android.application'
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
+ implementation "com.android.billingclient:billing:4.0.0"
+}
+
+android {
+ /*******************************************************
+ * The following variables:
+ * - androidBuildToolsVersion,
+ * - androidCompileSdkVersion
+ * - qtAndroidDir - holds the path to qt android files
+ * needed to build any Qt application
+ * on Android.
+ *
+ * are defined in gradle.properties file. This file is
+ * updated by QtCreator and androiddeployqt tools.
+ * Changing them manually might break the compilation!
+ *******************************************************/
+
+ compileSdkVersion androidCompileSdkVersion.toInteger()
+
+ buildToolsVersion androidBuildToolsVersion
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
+ aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
+ res.srcDirs = [qtAndroidDir + '/res', 'res']
+ resources.srcDirs = ['resources']
+ renderscript.srcDirs = ['src']
+ assets.srcDirs = ['assets']
+ jniLibs.srcDirs = ['libs']
+ }
+ }
+
+ tasks.withType(JavaCompile) {
+ options.incremental = true
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ // Do not compress Qt binary resources file
+ aaptOptions {
+ noCompress 'rcc'
+ }
+
+ defaultConfig {
+ resConfig "en"
+ minSdkVersion qtMinSdkVersion
+ targetSdkVersion qtTargetSdkVersion
+ ndk.abiFilters = qtTargetAbiList.split(",")
+ }
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java
new file mode 100644
index 000000000..fb0605fa8
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64.java
@@ -0,0 +1,572 @@
+/* Copyright (c) 2012 Google Inc.
+ * Copyright (c) 2015 The Qt Company Ltd.
+ *
+ * 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 org.qtproject.qt.android.purchasing;
+
+// This code was converted from code at http://iharder.sourceforge.net/base64/
+// Lots of extraneous features were removed.
+/* The original code said:
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit
+ * <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rharder@usa.net
+ * @version 1.3
+ */
+
+/**
+ * Base64 converter class. This code is not a complete MIME encoder;
+ * it simply converts binary data to base64 data and back.
+ *
+ * <p>Note {@link CharBase64} is a GWT-compatible implementation of this
+ * class.
+ */
+public class Base64 {
+ /** Specify encoding (value is {@code true}). */
+ public final static boolean ENCODE = true;
+
+ /** Specify decoding (value is {@code false}). */
+ public final static boolean DECODE = false;
+
+ /** The equals sign (=) as a byte. */
+ private final static byte EQUALS_SIGN = (byte) '=';
+
+ /** The new line character (\n) as a byte. */
+ private final static byte NEW_LINE = (byte) '\n';
+
+ /**
+ * The 64 valid Base64 values.
+ */
+ private final static byte[] ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '+', (byte) '/'};
+
+ /**
+ * The 64 valid web safe Base64 values.
+ */
+ private final static byte[] WEBSAFE_ALPHABET =
+ {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
+ (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
+ (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
+ (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
+ (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
+ (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
+ (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
+ (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
+ (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
+ (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
+ (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
+ (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
+ (byte) '9', (byte) '-', (byte) '_'};
+
+ /**
+ * Translates a Base64 value to either its 6-bit reconstruction value
+ * or a negative number indicating some other meaning.
+ **/
+ private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+ 62, // Plus sign at decimal 43
+ -9, -9, -9, // Decimal 44 - 46
+ 63, // Slash at decimal 47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, -9, -9, // Decimal 91 - 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+ /** The web safe decodabet */
+ private final static byte[] WEBSAFE_DECODABET =
+ {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+ -5, -5, // Whitespace: Tab and Linefeed
+ -9, -9, // Decimal 11 - 12
+ -5, // Whitespace: Carriage Return
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+ -9, -9, -9, -9, -9, // Decimal 27 - 31
+ -5, // Whitespace: Space
+ -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
+ 62, // Dash '-' sign at decimal 45
+ -9, -9, // Decimal 46-47
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+ -9, -9, -9, // Decimal 58 - 60
+ -1, // Equals sign at decimal 61
+ -9, -9, -9, // Decimal 62 - 64
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+ 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+ -9, -9, -9, -9, // Decimal 91-94
+ 63, // Underscore '_' at decimal 95
+ -9, // Decimal 96
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+ -9, -9, -9, -9, -9 // Decimal 123 - 127
+ /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
+ -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
+ };
+
+ // Indicates white space in encoding
+ private final static byte WHITE_SPACE_ENC = -5;
+ // Indicates equals sign in encoding
+ private final static byte EQUALS_SIGN_ENC = -1;
+
+ /** Defeats instantiation. */
+ private Base64() {
+ }
+
+ /* ******** E N C O D I N G M E T H O D S ******** */
+
+ /**
+ * Encodes up to three bytes of the array <var>source</var>
+ * and writes the resulting four Base64 bytes to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 3 for
+ * the <var>source</var> array or <var>destOffset</var> + 4 for
+ * the <var>destination</var> array.
+ * The actual number of significant bytes in your array is
+ * given by <var>numSigBytes</var>.
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param numSigBytes the number of significant bytes in your array
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param alphabet is the encoding alphabet
+ * @return the <var>destination</var> array
+ * @since 1.3
+ */
+ private static byte[] encode3to4(byte[] source, int srcOffset,
+ int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
+ // 1 2 3
+ // 01234567890123456789012345678901 Bit position
+ // --------000000001111111122222222 Array position from threeBytes
+ // --------| || || || | Six bit groups to index alphabet
+ // >>18 >>12 >> 6 >> 0 Right shift necessary
+ // 0x3f 0x3f 0x3f Additional AND
+
+ // Create buffer with zero-padding if there are only one or two
+ // significant bytes passed in the array.
+ // We have to shift left 24 in order to flush out the 1's that appear
+ // when Java treats a value as negative that is cast from a byte to an int.
+ int inBuff =
+ (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
+ | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
+ | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
+
+ switch (numSigBytes) {
+ case 3:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
+ return destination;
+ case 2:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ case 1:
+ destination[destOffset] = alphabet[(inBuff >>> 18)];
+ destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ destination[destOffset + 2] = EQUALS_SIGN;
+ destination[destOffset + 3] = EQUALS_SIGN;
+ return destination;
+ default:
+ return destination;
+ } // end switch
+ } // end encode3to4
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ * Equivalent to calling
+ * {@code encodeBytes(source, 0, source.length)}
+ *
+ * @param source The data to convert
+ * @since 1.4
+ */
+ public static String encode(byte[] source) {
+ return encode(source, 0, source.length, ALPHABET, true);
+ }
+
+ /**
+ * Encodes a byte array into web safe Base64 notation.
+ *
+ * @param source The data to convert
+ * @param doPadding is {@code true} to pad result with '=' chars
+ * if it does not fall on 3 byte boundaries
+ */
+ public static String encodeWebSafe(byte[] source, boolean doPadding) {
+ return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
+ }
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ *
+ * @param source the data to convert
+ * @param off offset in array where conversion should begin
+ * @param len length of data to convert
+ * @param alphabet the encoding alphabet
+ * @param doPadding is {@code true} to pad result with '=' chars
+ * if it does not fall on 3 byte boundaries
+ * @since 1.4
+ */
+ public static String encode(byte[] source, int off, int len, byte[] alphabet,
+ boolean doPadding) {
+ byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
+ int outLen = outBuff.length;
+
+ // If doPadding is false, set length to truncate '='
+ // padding characters
+ while (doPadding == false && outLen > 0) {
+ if (outBuff[outLen - 1] != '=') {
+ break;
+ }
+ outLen -= 1;
+ }
+
+ return new String(outBuff, 0, outLen);
+ }
+
+ /**
+ * Encodes a byte array into Base64 notation.
+ *
+ * @param source the data to convert
+ * @param off offset in array where conversion should begin
+ * @param len length of data to convert
+ * @param alphabet is the encoding alphabet
+ * @param maxLineLength maximum length of one line.
+ * @return the BASE64-encoded byte array
+ */
+ public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
+ int maxLineLength) {
+ int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
+ int len43 = lenDiv3 * 4;
+ byte[] outBuff = new byte[len43 // Main 4:3
+ + (len43 / maxLineLength)]; // New lines
+
+ int d = 0;
+ int e = 0;
+ int len2 = len - 2;
+ int lineLength = 0;
+ for (; d < len2; d += 3, e += 4) {
+
+ // The following block of code is the same as
+ // encode3to4( source, d + off, 3, outBuff, e, alphabet );
+ // but inlined for faster encoding (~20% improvement)
+ int inBuff =
+ ((source[d + off] << 24) >>> 8)
+ | ((source[d + 1 + off] << 24) >>> 16)
+ | ((source[d + 2 + off] << 24) >>> 24);
+ outBuff[e] = alphabet[(inBuff >>> 18)];
+ outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
+ outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
+ outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ lineLength = 0;
+ } // end if: end of line
+ } // end for: each piece of array
+
+ if (d < len) {
+ encode3to4(source, d + off, len - d, outBuff, e, alphabet);
+
+ lineLength += 4;
+ if (lineLength == maxLineLength) {
+ // Add a last newline
+ outBuff[e + 4] = NEW_LINE;
+ e++;
+ }
+ e += 4;
+ }
+
+ assert (e == outBuff.length);
+ return outBuff;
+ }
+
+
+ /* ******** D E C O D I N G M E T H O D S ******** */
+
+
+ /**
+ * Decodes four bytes from array <var>source</var>
+ * and writes the resulting bytes (up to three of them)
+ * to <var>destination</var>.
+ * The source and destination arrays can be manipulated
+ * anywhere along their length by specifying
+ * <var>srcOffset</var> and <var>destOffset</var>.
+ * This method does not check to make sure your arrays
+ * are large enough to accommodate <var>srcOffset</var> + 4 for
+ * the <var>source</var> array or <var>destOffset</var> + 3 for
+ * the <var>destination</var> array.
+ * This method returns the actual number of bytes that
+ * were converted from the Base64 encoding.
+ *
+ *
+ * @param source the array to convert
+ * @param srcOffset the index where conversion begins
+ * @param destination the array to hold the conversion
+ * @param destOffset the index where output will be put
+ * @param decodabet the decodabet for decoding Base64 content
+ * @return the number of decoded bytes converted
+ * @since 1.3
+ */
+ private static int decode4to3(byte[] source, int srcOffset,
+ byte[] destination, int destOffset, byte[] decodabet) {
+ // Example: Dk==
+ if (source[srcOffset + 2] == EQUALS_SIGN) {
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ return 1;
+ } else if (source[srcOffset + 3] == EQUALS_SIGN) {
+ // Example: DkL=
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
+
+ destination[destOffset] = (byte) (outBuff >>> 16);
+ destination[destOffset + 1] = (byte) (outBuff >>> 8);
+ return 2;
+ } else {
+ // Example: DkLE
+ int outBuff =
+ ((decodabet[source[srcOffset]] << 24) >>> 6)
+ | ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
+ | ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
+ | ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
+
+ destination[destOffset] = (byte) (outBuff >> 16);
+ destination[destOffset + 1] = (byte) (outBuff >> 8);
+ destination[destOffset + 2] = (byte) (outBuff);
+ return 3;
+ }
+ } // end decodeToBytes
+
+
+ /**
+ * Decodes data from Base64 notation.
+ *
+ * @param s the string to decode (decoded in default encoding)
+ * @return the decoded data
+ * @since 1.4
+ */
+ public static byte[] decode(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decode(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Decodes data from web safe Base64 notation.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param s the string to decode (decoded in default encoding)
+ * @return the decoded data
+ */
+ public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
+ byte[] bytes = s.getBytes();
+ return decodeWebSafe(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Decodes Base64 content in byte array format and returns
+ * the decoded byte array.
+ *
+ * @param source The Base64 encoded data
+ * @return decoded data
+ * @since 1.3
+ * @throws Base64DecoderException
+ */
+ public static byte[] decode(byte[] source) throws Base64DecoderException {
+ return decode(source, 0, source.length);
+ }
+
+ /**
+ * Decodes web safe Base64 content in byte array format and returns
+ * the decoded data.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param source the string to decode (decoded in default encoding)
+ * @return the decoded data
+ */
+ public static byte[] decodeWebSafe(byte[] source)
+ throws Base64DecoderException {
+ return decodeWebSafe(source, 0, source.length);
+ }
+
+ /**
+ * Decodes Base64 content in byte array format and returns
+ * the decoded byte array.
+ *
+ * @param source the Base64 encoded data
+ * @param off the offset of where to begin decoding
+ * @param len the length of characters to decode
+ * @return decoded data
+ * @since 1.3
+ * @throws Base64DecoderException
+ */
+ public static byte[] decode(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, DECODABET);
+ }
+
+ /**
+ * Decodes web safe Base64 content in byte array format and returns
+ * the decoded byte array.
+ * Web safe encoding uses '-' instead of '+', '_' instead of '/'
+ *
+ * @param source the Base64 encoded data
+ * @param off the offset of where to begin decoding
+ * @param len the length of characters to decode
+ * @return decoded data
+ */
+ public static byte[] decodeWebSafe(byte[] source, int off, int len)
+ throws Base64DecoderException {
+ return decode(source, off, len, WEBSAFE_DECODABET);
+ }
+
+ /**
+ * Decodes Base64 content using the supplied decodabet and returns
+ * the decoded byte array.
+ *
+ * @param source the Base64 encoded data
+ * @param off the offset of where to begin decoding
+ * @param len the length of characters to decode
+ * @param decodabet the decodabet for decoding Base64 content
+ * @return decoded data
+ */
+ public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
+ throws Base64DecoderException {
+ int len34 = len * 3 / 4;
+ byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
+ int outBuffPosn = 0;
+
+ byte[] b4 = new byte[4];
+ int b4Posn = 0;
+ int i = 0;
+ byte sbiCrop = 0;
+ byte sbiDecode = 0;
+ for (i = 0; i < len; i++) {
+ sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
+ sbiDecode = decodabet[sbiCrop];
+
+ if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
+ if (sbiDecode >= EQUALS_SIGN_ENC) {
+ // An equals sign (for padding) must not occur at position 0 or 1
+ // and must be the last byte[s] in the encoded value
+ if (sbiCrop == EQUALS_SIGN) {
+ int bytesLeft = len - i;
+ byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
+ if (b4Posn == 0 || b4Posn == 1) {
+ throw new Base64DecoderException(
+ "invalid padding byte '=' at byte offset " + i);
+ } else if ((b4Posn == 3 && bytesLeft > 2)
+ || (b4Posn == 4 && bytesLeft > 1)) {
+ throw new Base64DecoderException(
+ "padding byte '=' falsely signals end of encoded value "
+ + "at offset " + i);
+ } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
+ throw new Base64DecoderException(
+ "encoded value has invalid trailing byte");
+ }
+ break;
+ }
+
+ b4[b4Posn++] = sbiCrop;
+ if (b4Posn == 4) {
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ b4Posn = 0;
+ }
+ }
+ } else {
+ throw new Base64DecoderException("Bad Base64 input character at " + i
+ + ": " + source[i + off] + "(decimal)");
+ }
+ }
+
+ // Because web safe encoding allows non padding base64 encodes, we
+ // need to pad the rest of the b4 buffer with equal signs when
+ // b4Posn != 0. There can be at most 2 equal signs at the end of
+ // four characters, so the b4 buffer must have two or three
+ // characters. This also catches the case where the input is
+ // padded with EQUALS_SIGN
+ if (b4Posn != 0) {
+ if (b4Posn == 1) {
+ throw new Base64DecoderException("single trailing character at offset "
+ + (len - 1));
+ }
+ b4[b4Posn++] = EQUALS_SIGN;
+ outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
+ }
+
+ byte[] out = new byte[outBuffPosn];
+ System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
+ return out;
+ }
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java
new file mode 100644
index 000000000..884294712
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Base64DecoderException.java
@@ -0,0 +1,34 @@
+/* Copyright (c) 2012 Google Inc.
+ * Copyright (c) 2015 The Qt Company Ltd.
+ *
+ * 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 org.qtproject.qt.android.purchasing;
+
+/**
+ * Exception thrown when encountering an invalid Base64 input character.
+ *
+ * @author nelson
+ */
+public class Base64DecoderException extends Exception {
+ public Base64DecoderException() {
+ super();
+ }
+
+ public Base64DecoderException(String s) {
+ super(s);
+ }
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java
new file mode 100644
index 000000000..1930840a3
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java
@@ -0,0 +1,375 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2021 The Qt Company Ltd.
+ ** Contact: https://www.qt.io/licensing/
+ **
+ ** This file is part of the examples of the Qt Toolkit.
+ **
+ ** $QT_BEGIN_LICENSE:BSD$
+ ** Commercial License Usage
+ ** Licensees holding valid commercial Qt licenses may use this file in
+ ** accordance with the commercial license agreement provided with the
+ ** Software or, alternatively, in accordance with the terms contained in
+ ** a written agreement between you and The Qt Company. For licensing terms
+ ** and conditions see https://www.qt.io/terms-conditions. For further
+ ** information use the contact form at https://www.qt.io/contact-us.
+ **
+ ** BSD License Usage
+ ** Alternatively, you may use this file under the terms of the BSD license
+ ** as follows:
+ **
+ ** "Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions are
+ ** met:
+ ** * Redistributions of source code must retain the above copyright
+ ** notice, this list of conditions and the following disclaimer.
+ ** * Redistributions in binary form must reproduce the above copyright
+ ** notice, this list of conditions and the following disclaimer in
+ ** the documentation and/or other materials provided with the
+ ** distribution.
+ ** * Neither the name of The Qt Company Ltd nor the names of its
+ ** contributors may be used to endorse or promote products derived
+ ** from this software without specific prior written permission.
+ **
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ **
+ ** $QT_END_LICENSE$
+ **
+ ****************************************************************************/
+
+package org.qtproject.qt.android.purchasing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.billingclient.api.AcknowledgePurchaseParams;
+import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
+import com.android.billingclient.api.BillingClient;
+import com.android.billingclient.api.BillingClientStateListener;
+import com.android.billingclient.api.BillingFlowParams;
+import com.android.billingclient.api.BillingResult;
+import com.android.billingclient.api.ConsumeParams;
+import com.android.billingclient.api.ConsumeResponseListener;
+import com.android.billingclient.api.Purchase;
+import com.android.billingclient.api.Purchase.PurchaseState;
+import com.android.billingclient.api.PurchasesResponseListener;
+import com.android.billingclient.api.PurchasesUpdatedListener;
+import com.android.billingclient.api.SkuDetails;
+import com.android.billingclient.api.SkuDetailsParams;
+import com.android.billingclient.api.SkuDetailsResponseListener;
+
+
+/***********************************************************************
+ ** More info: https://developer.android.com/google/play/billing
+ ** Add Dependencies below to build.gradle file:
+
+dependencies {
+ def billing_version = "4.0.0"
+ implementation "com.android.billingclient:billing:$billing_version"
+}
+
+***********************************************************************/
+
+public class InAppPurchase implements PurchasesUpdatedListener
+{
+ private Context m_context = null;
+ private long m_nativePointer;
+ private String m_publicKey = null;
+ private int purchaseRequestCode;
+
+
+ private BillingClient billingClient;
+
+ public static final int RESULT_OK = BillingClient.BillingResponseCode.OK;
+ public static final int RESULT_USER_CANCELED = BillingClient.BillingResponseCode.USER_CANCELED;
+ public static final String TYPE_INAPP = BillingClient.SkuType.INAPP;
+ public static final String TAG = "InAppPurchase";
+
+ // Should be in sync with InAppTransaction::FailureReason
+ public static final int FAILUREREASON_NOFAILURE = 0;
+ public static final int FAILUREREASON_USERCANCELED = 1;
+ public static final int FAILUREREASON_ERROR = 2;
+
+ public InAppPurchase(Context context, long nativePointer)
+ {
+ m_context = context;
+ m_nativePointer = nativePointer;
+ }
+
+ public void initializeConnection(){
+ billingClient = BillingClient.newBuilder(m_context)
+ .enablePendingPurchases()
+ .setListener(this)
+ .build();
+ billingClient.startConnection(new BillingClientStateListener() {
+ @Override
+ public void onBillingSetupFinished(BillingResult billingResult) {
+ if (billingResult.getResponseCode() == RESULT_OK) {
+ purchasedProductsQueried(m_nativePointer);
+ }
+ }
+
+ @Override
+ public void onBillingServiceDisconnected() {
+ Log.w(TAG, "Billing service disconnected");
+ }
+ });
+ }
+
+ @Override
+ public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
+
+ int responseCode = billingResult.getResponseCode();
+
+ if (purchases == null) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
+ return;
+ }
+
+ if (billingResult.getResponseCode() == RESULT_OK) {
+ handlePurchase(purchases);
+ } else if (responseCode == RESULT_USER_CANCELED) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_USERCANCELED, "");
+ } else {
+ String errorString = getErrorString(responseCode);
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, errorString);
+ }
+ }
+
+ //Get list of purchases from onPurchasesUpdated
+ private void handlePurchase(List<Purchase> purchases) {
+
+ for (Purchase purchase : purchases) {
+ try {
+ if (m_publicKey != null && !Security.verifyPurchase(m_publicKey, TYPE_INAPP, purchase.getSignature())) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Signature could not be verified");
+ return;
+ }
+ int purchaseState = purchase.getPurchaseState();
+ if (purchaseState != PurchaseState.PURCHASED) {
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Unexpected purchase state in result");
+ return;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, e.getMessage());
+ }
+ purchaseSucceeded(purchaseRequestCode, purchase.getSignature(), TYPE_INAPP, purchase.getPurchaseToken(), purchase.getOrderId(), purchase.getPurchaseTime());
+ }
+ }
+
+ public void queryDetails(final String[] productIds) {
+
+ int index = 0;
+ while (index < productIds.length) {
+ List<String> productIdList = new ArrayList<>();
+ for (int i = index; i < Math.min(index + 20, productIds.length); ++i) {
+ productIdList.add(productIds[i]);
+ }
+ index += productIdList.size();
+
+ SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
+ params.setSkusList(productIdList).setType(BillingClient.SkuType.INAPP);
+ billingClient.querySkuDetailsAsync(params.build(),
+ new SkuDetailsResponseListener() {
+ @Override
+ public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
+ int responseCode = billingResult.getResponseCode();
+
+ if (responseCode != RESULT_OK) {
+ Log.e(TAG, "queryDetails: Couldn't retrieve sku details.");
+ return;
+ }
+ if (skuDetailsList == null) {
+ Log.e(TAG, "queryDetails: No details list in response.");
+ return;
+ }
+
+ for (SkuDetails skuDetails : skuDetailsList) {
+ try {
+ String queriedProductId = skuDetails.getSku();
+ String queriedPrice = skuDetails.getPrice();
+ String queriedTitle = skuDetails.getTitle();
+ String queriedDescription = skuDetails.getDescription();
+ registerProduct(m_nativePointer,
+ queriedProductId,
+ queriedPrice,
+ queriedTitle,
+ queriedDescription);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+
+
+ queryPurchasedProducts(productIdList);
+ }
+ }
+
+ //Launch Google purchasing screen
+ public void launchBillingFlow(String identifier, int requestCode){
+
+ purchaseRequestCode = requestCode;
+ List<String> skuList = new ArrayList<>();
+ skuList.add(identifier);
+ SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
+ params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
+ billingClient.querySkuDetailsAsync(params.build(),
+ new SkuDetailsResponseListener() {
+ @Override
+ public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
+
+ if (billingResult.getResponseCode() != RESULT_OK) {
+ Log.e(TAG, "Unable to launch Google Play purchase screen");
+ String errorString = getErrorString(requestCode);
+ purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString);
+ return;
+ }
+ else if (skuDetailsList == null){
+ purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
+ return;
+ }
+
+ BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
+ .setSkuDetails(skuDetailsList.get(0))
+ .build();
+
+ //Results will be delivered to onPurchasesUpdated
+ billingClient.launchBillingFlow((Activity) m_context, purchaseParams);
+ }
+ });
+ }
+
+ public void consumePurchase(String purchaseToken){
+
+ ConsumeResponseListener listener = new ConsumeResponseListener() {
+ @Override
+ public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
+ if (billingResult.getResponseCode() != RESULT_OK) {
+ Log.e(TAG, "Unable to consume purchase. Response code: " + billingResult.getResponseCode());
+ }
+ }
+ };
+ ConsumeParams consumeParams =
+ ConsumeParams.newBuilder()
+ .setPurchaseToken(purchaseToken)
+ .build();
+ billingClient.consumeAsync(consumeParams, listener);
+ }
+
+ public void acknowledgeUnlockablePurchase(String purchaseToken){
+
+ AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
+ .setPurchaseToken(purchaseToken)
+ .build();
+
+ AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
+ @Override
+ public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
+ if (billingResult.getResponseCode() != RESULT_OK){
+ Log.e(TAG, "Unable to acknowledge purchase. Response code: " + billingResult.getResponseCode());
+ }
+ }
+ };
+ billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
+ }
+
+ public void queryPurchasedProducts(List<String> productIdList) {
+
+ billingClient.queryPurchasesAsync(TYPE_INAPP, new PurchasesResponseListener() {
+ @Override
+ public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> list) {
+ for (Purchase purchase : list) {
+
+ if (productIdList.contains(purchase.getSkus().get(0))) {
+ registerPurchased(m_nativePointer,
+ purchase.getSkus().get(0),
+ purchase.getSignature(),
+ TYPE_INAPP,
+ purchase.getPurchaseToken(),
+ purchase.getDeveloperPayload(),
+ purchase.getPurchaseTime());
+ }
+ }
+ }
+ });
+ }
+
+ private String getErrorString(int responseCode){
+ String errorString;
+ switch (responseCode) {
+ case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE: errorString = "Billing unavailable"; break;
+ case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE: errorString = "Item unavailable"; break;
+ case BillingClient.BillingResponseCode.DEVELOPER_ERROR: errorString = "Developer error"; break;
+ case BillingClient.BillingResponseCode.ERROR: errorString = "Fatal error occurred"; break;
+ case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED: errorString = "Item already owned"; break;
+ case BillingClient.BillingResponseCode.ITEM_NOT_OWNED: errorString = "Item not owned"; break;
+ default: errorString = "Unknown billing error " + responseCode; break;
+ };
+ return errorString;
+ }
+
+ public void setPublicKey(String publicKey)
+ {
+ m_publicKey = publicKey;
+ }
+
+ private void purchaseFailed(int requestCode, int failureReason, String errorString)
+ {
+ purchaseFailed(m_nativePointer, requestCode, failureReason, errorString);
+ }
+
+
+ private void purchaseSucceeded(int requestCode,
+ String signature,
+ String purchaseData,
+ String purchaseToken,
+ String orderId,
+ long timestamp)
+ {
+ purchaseSucceeded(m_nativePointer, requestCode, signature, purchaseData, purchaseToken, orderId, timestamp);
+ }
+
+ private native static void queryFailed(long nativePointer, String productId);
+ private native static void purchasedProductsQueried(long nativePointer);
+ private native static void registerProduct(long nativePointer,
+ String productId,
+ String price,
+ String title,
+ String description);
+ private native static void purchaseFailed(long nativePointer,
+ int requestCode,
+ int failureReason,
+ String errorString);
+ private native static void purchaseSucceeded(long nativePointer,
+ int requestCode,
+ String signature,
+ String data,
+ String purchaseToken,
+ String orderId,
+ long timestamp);
+ private native static void registerPurchased(long nativePointer,
+ String identifier,
+ String signature,
+ String data,
+ String purchaseToken,
+ String orderId,
+ long timestamp);
+}
diff --git a/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java
new file mode 100644
index 000000000..5ce0ab681
--- /dev/null
+++ b/examples/demos/hangman/purchasing/android/src/org/qtproject/qt/android/purchasing/Security.java
@@ -0,0 +1,131 @@
+/* Copyright (c) 2012 Google Inc.
+ * Copyright (c) 2015 The Qt Company Ltd.
+ *
+ * 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 org.qtproject.qt.android.purchasing;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * Security-related methods. For a secure implementation, all of this code
+ * should be implemented on a server that communicates with the
+ * application on the device. For the sake of simplicity and clarity of this
+ * example, this code is included here and is executed on the device. If you
+ * must verify the purchases on the phone, you should obfuscate this code to
+ * make it harder for an attacker to replace the code with stubs that treat all
+ * purchases as verified.
+ */
+public class Security {
+ private static final String TAG = "IABUtil/Security";
+
+ private static final String KEY_FACTORY_ALGORITHM = "RSA";
+ private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
+
+ /**
+ * Verifies that the data was signed with the given signature, and returns
+ * the verified purchase. The data is in JSON format and signed
+ * with a private key. The data also contains the {@link PurchaseState}
+ * and product ID of the purchase.
+ * @param base64PublicKey the base64-encoded public key to use for verifying.
+ * @param signedData the signed JSON string (signed, not encrypted)
+ * @param signature the signature for the data, signed with the private key
+ */
+ public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
+ if (signedData == null) {
+ Log.e(TAG, "data is null");
+ return false;
+ }
+
+ boolean verified = false;
+ if (!TextUtils.isEmpty(signature)) {
+ PublicKey key = Security.generatePublicKey(base64PublicKey);
+ verified = Security.verify(key, signedData, signature);
+ if (!verified) {
+ Log.w(TAG, "signature does not match data.");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Generates a PublicKey instance from a string containing the
+ * Base64-encoded public key.
+ *
+ * @param encodedPublicKey Base64-encoded public key
+ * @throws IllegalArgumentException if encodedPublicKey is invalid
+ */
+ public static PublicKey generatePublicKey(String encodedPublicKey) {
+ try {
+ byte[] decodedKey = Base64.decode(encodedPublicKey);
+ KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
+ return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ } catch (InvalidKeySpecException e) {
+ Log.e(TAG, "Invalid key specification.");
+ throw new IllegalArgumentException(e);
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Base64 decoding failed.");
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Verifies that the signature from the server matches the computed
+ * signature on the data. Returns true if the data is correctly signed.
+ *
+ * @param publicKey public key associated with the developer account
+ * @param signedData signed data from server
+ * @param signature server signature
+ * @return true if the data and signature match
+ */
+ public static boolean verify(PublicKey publicKey, String signedData, String signature) {
+ Signature sig;
+ try {
+ sig = Signature.getInstance(SIGNATURE_ALGORITHM);
+ sig.initVerify(publicKey);
+ sig.update(signedData.getBytes());
+ if (!sig.verify(Base64.decode(signature))) {
+ Log.e(TAG, "Signature verification failed.");
+ return false;
+ }
+ return true;
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG, "NoSuchAlgorithmException.");
+ } catch (InvalidKeyException e) {
+ Log.e(TAG, "Invalid key specification.");
+ } catch (SignatureException e) {
+ Log.e(TAG, "Signature exception.");
+ } catch (Base64DecoderException e) {
+ Log.e(TAG, "Base64 decoding failed.");
+ }
+ return false;
+ }
+}