summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>2014-02-13 11:32:19 +0100
committerAndy Nichols <andy.nichols@digia.com>2014-02-13 14:08:44 +0200
commitcc249300a9088039f1025c20afea74beac95411a (patch)
tree8e6f95d13b44763a0d7fc5e94ec8bf6602de5275
parentfcc2667febd3122eb1e2d4c0a119e12e609a0067 (diff)
Initial commit of Qt Mobile Extras
At this point, contains an unfinished in-app purchase API with an unfinished Android implementation. Change-Id: Id01b61f6d588557404a7df0b002b4b6d3cb00f33 Reviewed-by: Andy Nichols <andy.nichols@digia.com>
-rw-r--r--.gitattributes4
-rw-r--r--.gitignore126
-rw-r--r--.qmake.conf3
-rw-r--r--.tag1
-rw-r--r--qtmobile-extras.pro1
-rw-r--r--src/android/android.pro2
-rw-r--r--src/android/bundledjar.pro3
-rw-r--r--src/android/distributedjar.pro2
-rw-r--r--src/android/jar.pri52
-rw-r--r--src/android/src/com/android/vending/billing/IInAppBillingService.aidl144
-rw-r--r--src/android/src/com/digia/qt5/android/mobileextras/Base64.java571
-rw-r--r--src/android/src/com/digia/qt5/android/mobileextras/Base64DecoderException.java33
-rw-r--r--src/android/src/com/digia/qt5/android/mobileextras/QtInAppPurchase.java267
-rw-r--r--src/android/src/com/digia/qt5/android/mobileextras/Security.java131
-rw-r--r--src/imports/imports.pro3
-rw-r--r--src/imports/inapppurchase/inapppurchase.cpp68
-rw-r--r--src/imports/inapppurchase/inapppurchase.pro21
-rw-r--r--src/imports/inapppurchase/qinappproductconsumable.cpp31
-rw-r--r--src/imports/inapppurchase/qinappproductconsumable_p.h39
-rw-r--r--src/imports/inapppurchase/qinappproductqmltype.cpp177
-rw-r--r--src/imports/inapppurchase/qinappproductqmltype_p.h91
-rw-r--r--src/imports/inapppurchase/qinappproductunlockable.cpp30
-rw-r--r--src/imports/inapppurchase/qinappproductunlockable_p.h33
-rw-r--r--src/imports/inapppurchase/qinappstoreqmltype.cpp76
-rw-r--r--src/imports/inapppurchase/qinappstoreqmltype_p.h48
-rw-r--r--src/imports/inapppurchase/qmldir4
-rw-r--r--src/mobileextras/inapppurchase/android/android.pri10
-rw-r--r--src/mobileextras/inapppurchase/android/qandroidinappproduct.cpp40
-rw-r--r--src/mobileextras/inapppurchase/android/qandroidinappproduct_p.h54
-rw-r--r--src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend.cpp163
-rw-r--r--src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend_p.h83
-rw-r--r--src/mobileextras/inapppurchase/android/qandroidinapptransaction.cpp52
-rw-r--r--src/mobileextras/inapppurchase/android/qandroidinapptransaction_p.h62
-rw-r--r--src/mobileextras/inapppurchase/android/qandroidjni.cpp74
-rw-r--r--src/mobileextras/inapppurchase/inapppurchase.pri24
-rw-r--r--src/mobileextras/inapppurchase/qinappproduct.cpp106
-rw-r--r--src/mobileextras/inapppurchase/qinappproduct.h67
-rw-r--r--src/mobileextras/inapppurchase/qinapppurchasebackend.cpp59
-rw-r--r--src/mobileextras/inapppurchase/qinapppurchasebackend_p.h65
-rw-r--r--src/mobileextras/inapppurchase/qinapppurchasebackendfactory.cpp40
-rw-r--r--src/mobileextras/inapppurchase/qinapppurchasebackendfactory_p.h48
-rw-r--r--src/mobileextras/inapppurchase/qinappstore.cpp268
-rw-r--r--src/mobileextras/inapppurchase/qinappstore.h64
-rw-r--r--src/mobileextras/inapppurchase/qinappstore_p.h60
-rw-r--r--src/mobileextras/inapppurchase/qinapptransaction.cpp130
-rw-r--r--src/mobileextras/inapppurchase/qinapptransaction.h64
-rw-r--r--src/mobileextras/mobileextras.pro13
-rw-r--r--src/mobileextras/qmobileextrasglobal.h40
-rw-r--r--src/src.pro2
-rw-r--r--sync.profile17
50 files changed, 3566 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1a045fa
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+.tag export-subst
+.gitignore export-ignore
+.gitattributes export-ignore
+.commit-template export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4a5200a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,126 @@
+# This file is used to ignore files which are generated in the Qt build system
+# ----------------------------------------------------------------------------
+
+callgrind.out.*
+pcviewer.cfg
+*~
+*.a
+*.la
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.swp
+*.rej
+*.so
+*.pbxuser
+*.mode1
+*.mode1v3
+*_pch.h.cpp
+*_resource.rc
+.#*
+*.*#
+core
+.qmake.cache
+.qmake.vars
+*.prl
+tags
+.DS_Store
+*.debug
+Makefile*
+*.prl
+*.app
+*.pro.user
+*.qmlproject.user
+*.gcov
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+
+# Test generated files
+QObject.log
+tst_*
+!tst_*.*
+tst_*.log
+tst_*.debug
+tst_*~
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.vcxproj
+*.vcxproj.filters
+*.vcxproj.user
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Symlinks generated by configure
+.DS_Store
+.pch
+.rcc
+*.app
+
+
+# Directories to ignore
+# ---------------------
+
+debug
+include/*
+include/*/*
+lib/*
+!lib/fonts
+!lib/README
+plugins/*/*
+release
+tmp
+doc-build
+doc/html/*
+doc/qch
+doc-build
+.rcc
+.pch
+.metadata
+
+# runonphone crash dumps
+d_exc_*.txt
+d_exc_*.stk
+
+# Generated by abldfast.bat from devtools.
+.abldsteps.*
+
+# Carbide project files
+# ---------------------
+.project
+.cproject
+.make.cache
+*.d
+
+qtc-debugging-helper
+
+.pc/
+
+# INTEGRITY generated files
+*.gpj
+*.int
+*.ael
+*.dla
+*.dnm
+*.dep
+*.map
+work
diff --git a/.qmake.conf b/.qmake.conf
new file mode 100644
index 0000000..16328b7
--- /dev/null
+++ b/.qmake.conf
@@ -0,0 +1,3 @@
+load(qt_build_config)
+
+MODULE_VERSION = 5.3.0
diff --git a/.tag b/.tag
new file mode 100644
index 0000000..6828f88
--- /dev/null
+++ b/.tag
@@ -0,0 +1 @@
+$Format:%H$
diff --git a/qtmobile-extras.pro b/qtmobile-extras.pro
new file mode 100644
index 0000000..58c33f2
--- /dev/null
+++ b/qtmobile-extras.pro
@@ -0,0 +1 @@
+load(qt_parts)
diff --git a/src/android/android.pro b/src/android/android.pro
new file mode 100644
index 0000000..8d19c1b
--- /dev/null
+++ b/src/android/android.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS += bundledjar.pro distributedjar.pro
diff --git a/src/android/bundledjar.pro b/src/android/bundledjar.pro
new file mode 100644
index 0000000..f6348dc
--- /dev/null
+++ b/src/android/bundledjar.pro
@@ -0,0 +1,3 @@
+TARGET = QtMobileExtras-bundled
+CONFIG += bundled_jar_file
+include(jar.pri)
diff --git a/src/android/distributedjar.pro b/src/android/distributedjar.pro
new file mode 100644
index 0000000..80ad488
--- /dev/null
+++ b/src/android/distributedjar.pro
@@ -0,0 +1,2 @@
+TARGET = QtMobileExtras
+include(jar.pri)
diff --git a/src/android/jar.pri b/src/android/jar.pri
new file mode 100644
index 0000000..84fac10
--- /dev/null
+++ b/src/android/jar.pri
@@ -0,0 +1,52 @@
+CONFIG += java
+DESTDIR = $$[QT_INSTALL_PREFIX/get]/jar
+
+PATHPREFIX = $$PWD/src/com/digia/qt5/android/mobileextras/
+
+!build_pass {
+ isEmpty(SDK_ROOT): SDK_ROOT = $$(ANDROID_SDK_ROOT)
+ isEmpty(SDK_ROOT): SDK_ROOT = $$DEFAULT_ANDROID_SDK_ROOT
+
+ isEmpty(BUILD_TOOLS_REVISION) {
+ BUILD_TOOLS_REVISION = $$(ANDROID_BUILD_TOOLS_REVISION)
+ isEmpty(BUILD_TOOLS_REVISION) {
+ BUILD_TOOLS_REVISIONS = $$files($$SDK_ROOT/build-tools/*)
+ for (REVISION, BUILD_TOOLS_REVISIONS) {
+ BASENAME = $$basename(REVISION)
+ greaterThan(BASENAME, $$BUILD_TOOLS_REVISION): BUILD_TOOLS_REVISION = $$BASENAME
+ }
+ }
+ }
+
+ API_VERSION_TO_USE = $$(ANDROID_API_VERSION)
+ isEmpty(API_VERSION_TO_USE): API_VERSION_TO_USE = $$API_VERSION
+ isEmpty(API_VERSION_TO_USE): API_VERSION_TO_USE = android-10
+
+ FRAMEWORK_AIDL_FILE = $$SDK_ROOT/platforms/$$API_VERSION_TO_USE/framework.aidl
+ !exists($$FRAMEWORK_AIDL_FILE) {
+ error("The Path $$FRAMEWORK_AIDL_FILE does not exist. Make sure the ANDROID_SDK_ROOT and ANDROID_API_VERSION environment variables are correctly set.")
+ }
+
+ AIDL_CMD = $$SDK_ROOT/platform-tools/aidl
+ contains(QMAKE_HOST.os, Windows): AIDL_CMD += ".exe"
+ !exists($$AIDL_CMD): AIDL_CMD = $$SDK_ROOT/build-tools/$$BUILD_TOOLS_REVISION/aidl
+ !exists($$AIDL_CMD): error("The path $$AIDL_CMD does not exist. Please set the environment variable ANDROID_BUILD_TOOLS_REVISION to the revision of the build tools installed in your Android SDK.")
+
+ system($$AIDL_CMD -I$$PWD/src -p$$FRAMEWORK_AIDL_FILE $$PWD/src/com/android/vending/billing/IInAppBillingService.aidl $$PWD/src/com/android/vending/billing/IInAppBillingService.java)
+}
+
+JAVACLASSPATH += $$PWD/src/
+JAVASOURCES += \
+ $$PATHPREFIX/QtInAppPurchase.java \
+ $$PATHPREFIX/Security.java \
+ $$PATHPREFIX/Base64.java \
+ $$PATHPREFIX/Base64DecoderException.java
+ $$PWD/src/android/vending/billing/.java
+
+
+
+# install
+target.path = $$[QT_INSTALL_PREFIX]/jar
+INSTALLS += target
+
+OTHER_FILES += $$JAVASOURCES
diff --git a/src/android/src/com/android/vending/billing/IInAppBillingService.aidl b/src/android/src/com/android/vending/billing/IInAppBillingService.aidl
new file mode 100644
index 0000000..2a492f7
--- /dev/null
+++ b/src/android/src/com/android/vending/billing/IInAppBillingService.aidl
@@ -0,0 +1,144 @@
+/*
+ * 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.android.vending.billing;
+
+import android.os.Bundle;
+
+/**
+ * InAppBillingService is the service that provides in-app billing version 3 and beyond.
+ * This service provides the following features:
+ * 1. Provides a new API to get details of in-app items published for the app including
+ * price, type, title and description.
+ * 2. The purchase flow is synchronous and purchase information is available immediately
+ * after it completes.
+ * 3. Purchase information of in-app purchases is maintained within the Google Play system
+ * till the purchase is consumed.
+ * 4. An API to consume a purchase of an inapp item. All purchases of one-time
+ * in-app items are consumable and thereafter can be purchased again.
+ * 5. An API to get current purchases of the user immediately. This will not contain any
+ * consumed purchases.
+ *
+ * All calls will give a response code with the following possible values
+ * RESULT_OK = 0 - success
+ * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
+ * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
+ * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
+ * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
+ * RESULT_ERROR = 6 - Fatal error during the API action
+ * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
+ * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
+ */
+interface IInAppBillingService {
+ /**
+ * Checks support for the requested billing API version, package and in-app type.
+ * Minimum API version supported by this interface is 3.
+ * @param apiVersion the billing version which the app is using
+ * @param packageName the package name of the calling app
+ * @param type type of the in-app item being purchased "inapp" for one-time purchases
+ * and "subs" for subscription.
+ * @return RESULT_OK(0) on success, corresponding result code on failures
+ */
+ int isBillingSupported(int apiVersion, String packageName, String type);
+
+ /**
+ * Provides details of a list of SKUs
+ * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
+ * with a list JSON strings containing the productId, price, title and description.
+ * This API can be called with a maximum of 20 SKUs.
+ * @param apiVersion billing API version that the Third-party is using
+ * @param packageName the package name of the calling app
+ * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
+ * @return Bundle containing the following key-value pairs
+ * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+ * failure as listed above.
+ * "DETAILS_LIST" with a StringArrayList containing purchase information
+ * in JSON format similar to:
+ * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
+ * "title : "Example Title", "description" : "This is an example description" }'
+ */
+ Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
+
+ /**
+ * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
+ * the type, a unique purchase token and an optional developer payload.
+ * @param apiVersion billing API version that the app is using
+ * @param packageName package name of the calling app
+ * @param sku the SKU of the in-app item as published in the developer console
+ * @param type the type of the in-app item ("inapp" for one-time purchases
+ * and "subs" for subscription).
+ * @param developerPayload optional argument to be sent back with the purchase information
+ * @return Bundle containing the following key-value pairs
+ * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+ * failure as listed above.
+ * "BUY_INTENT" - PendingIntent to start the purchase flow
+ *
+ * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
+ * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
+ * If the purchase is successful, the result data will contain the following key-value pairs
+ * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+ * failure as listed above.
+ * "INAPP_PURCHASE_DATA" - String in JSON format similar to
+ * '{"orderId":"12999763169054705758.1371079406387615",
+ * "packageName":"com.example.app",
+ * "productId":"exampleSku",
+ * "purchaseTime":1345678900000,
+ * "purchaseToken" : "122333444455555",
+ * "developerPayload":"example developer payload" }'
+ * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
+ * was signed with the private key of the developer
+ * TODO: change this to app-specific keys.
+ */
+ Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
+ String developerPayload);
+
+ /**
+ * Returns the current SKUs owned by the user of the type and package name specified along with
+ * purchase information and a signature of the data to be validated.
+ * This will return all SKUs that have been purchased in V3 and managed items purchased using
+ * V1 and V2 that have not been consumed.
+ * @param apiVersion billing API version that the app is using
+ * @param packageName package name of the calling app
+ * @param type the type of the in-app items being requested
+ * ("inapp" for one-time purchases and "subs" for subscription).
+ * @param continuationToken to be set as null for the first call, if the number of owned
+ * skus are too many, a continuationToken is returned in the response bundle.
+ * This method can be called again with the continuation token to get the next set of
+ * owned skus.
+ * @return Bundle containing the following key-value pairs
+ * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+ * failure as listed above.
+ * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
+ * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
+ * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
+ * of the purchase information
+ * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
+ * next set of in-app purchases. Only set if the
+ * user has more owned skus than the current list.
+ */
+ Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
+
+ /**
+ * Consume the last purchase of the given SKU. This will result in this item being removed
+ * from all subsequent responses to getPurchases() and allow re-purchase of this item.
+ * @param apiVersion billing API version that the app is using
+ * @param packageName package name of the calling app
+ * @param purchaseToken token in the purchase information JSON that identifies the purchase
+ * to be consumed
+ * @return 0 if consumption succeeded. Appropriate error values for failures.
+ */
+ int consumePurchase(int apiVersion, String packageName, String purchaseToken);
+}
diff --git a/src/android/src/com/digia/qt5/android/mobileextras/Base64.java b/src/android/src/com/digia/qt5/android/mobileextras/Base64.java
new file mode 100644
index 0000000..2eb71fa
--- /dev/null
+++ b/src/android/src/com/digia/qt5/android/mobileextras/Base64.java
@@ -0,0 +1,571 @@
+// Portions copyright 2002, Google, Inc.
+// Portions copyright (c) 2014 Digia Plc and/or its subsidiary(-ies).
+//
+// 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.digia.qt5.android.mobileextras;
+
+// 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/src/android/src/com/digia/qt5/android/mobileextras/Base64DecoderException.java b/src/android/src/com/digia/qt5/android/mobileextras/Base64DecoderException.java
new file mode 100644
index 0000000..e24eb36
--- /dev/null
+++ b/src/android/src/com/digia/qt5/android/mobileextras/Base64DecoderException.java
@@ -0,0 +1,33 @@
+// Copyright 2002, Google, Inc.
+// Copyright (c) 2014 Digia Plc and/or its subsidiary(-ies).
+//
+// 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.digia.qt5.android.mobileextras;
+
+/**
+ * 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/src/android/src/com/digia/qt5/android/mobileextras/QtInAppPurchase.java b/src/android/src/com/digia/qt5/android/mobileextras/QtInAppPurchase.java
new file mode 100644
index 0000000..63fbc7a
--- /dev/null
+++ b/src/android/src/com/digia/qt5/android/mobileextras/QtInAppPurchase.java
@@ -0,0 +1,267 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+package com.digia.qt5.android.mobileextras;
+
+import java.util.ArrayList;
+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;
+import android.os.RemoteException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import org.json.JSONObject;
+import org.json.JSONException;
+
+
+import com.android.vending.billing.IInAppBillingService;
+
+public class QtInAppPurchase
+{
+ private Context m_context = null;
+ private IInAppBillingService m_service = null;
+ private String m_publicKey = null;
+ private long m_nativePointer;
+
+ public static final int RESULT_OK = 0;
+ public static final int RESULT_QTMOBILEEXTRAS_ERROR = 9; // No match with any already defined response codes
+ public static final String TAG = "QtInAppPurchase";
+ public static final String TYPE_INAPP = "inapp";
+ public static final int IAP_VERSION = 3;
+
+ private ServiceConnection m_serviceConnection = new ServiceConnection()
+ {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service)
+ {
+ m_service = IInAppBillingService.Stub.asInterface(service);
+ try {
+ int response = m_service.isBillingSupported(3, m_context.getPackageName(), TYPE_INAPP);
+ if (response != RESULT_OK) {
+ Log.e(TAG, "In-app billing not supported");
+ return;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+
+ // Asynchronously populate list of purchased products
+ final Handler handler = new Handler();
+ Thread thread = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ queryPurchasedProducts();
+ handler.post(new Runnable()
+ {
+ public void run() { purchasedProductsQueried(m_nativePointer); }
+ });
+ }
+ });
+ thread.start();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name)
+ {
+ m_service = null;
+ }
+ };
+
+ public QtInAppPurchase(Context context, long nativePointer)
+ {
+ m_context = context;
+ m_nativePointer = nativePointer;
+ }
+
+ public void initializeConnection()
+ {
+
+ Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
+ if (!m_context.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
+ m_context.bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE);
+ } else {
+ Log.e(TAG, "No in-app billing service available.");
+ purchasedProductsQueried(m_nativePointer);
+ }
+ }
+
+ private int bundleResponseCode(Bundle bundle)
+ {
+ Object o = bundle.get("RESPONSE_CODE");
+ if (o == null) {
+ // Works around known issue where the response code is not bundled.
+ return RESULT_OK;
+ } else if (o instanceof Integer) {
+ return ((Integer)o).intValue();
+ } else if (o instanceof Long) {
+ return (int)((Long)o).longValue();
+ }
+
+ Log.e(TAG, "Unexpected result for response code: " + o);
+ return RESULT_QTMOBILEEXTRAS_ERROR;
+ }
+
+ private void queryPurchasedProducts()
+ {
+ if (m_service == null) {
+ Log.e(TAG, "queryPurchasedProducts: Service not initialized");
+ return;
+ }
+
+ String continuationToken = null;
+ try {
+ do {
+ Bundle ownedItems = m_service.getPurchases(IAP_VERSION,
+ m_context.getPackageName(),
+ TYPE_INAPP,
+ continuationToken);
+ int responseCode = bundleResponseCode(ownedItems);
+ if (responseCode != RESULT_OK) {
+ Log.e(TAG, "queryPurchasedProducts: Failed to query purchases products");
+ return;
+ }
+
+ ArrayList<String> dataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
+ if (dataList == null) {
+ Log.e(TAG, "queryPurchasedProducts: No data list in bundle");
+ return;
+ }
+
+ ArrayList<String> signatureList = ownedItems.getStringArrayList("INAPP_PURCHASE_SIGNATURE_LIST");
+ if (signatureList == null) {
+ Log.e(TAG, "queryPurchasedProducts: No signature list in bundle");
+ return;
+ }
+
+ if (dataList.size() != signatureList.size()) {
+ Log.e(TAG, "queryPurchasedProducts: Mismatching sizes of lists in bundle");
+ return;
+ }
+
+ for (int i=0; i<dataList.size(); ++i) {
+ String data = dataList.get(i);
+ String signature = signatureList.get(i);
+
+ if (m_publicKey != null && !Security.verifyPurchase(m_publicKey, data, signature)) {
+ Log.e(TAG, "queryPurchasedProducts: Cannot verify signature of purchase");
+ return;
+ } else {
+ try {
+ JSONObject jo = new JSONObject(data);
+ String productId = jo.getString("productId");
+ int purchaseState = jo.getInt("purchaseState");
+
+ if (purchaseState == 0)
+ registerPurchased(m_nativePointer, productId, signature, data);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");
+
+ } while (continuationToken != null && continuationToken.length() > 0);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ private void queryDetails(final String productId)
+ {
+ if (m_service == null) {
+ Log.e(TAG, "queryDetails: Service not initialized");
+ queryFailed(m_nativePointer, productId);
+ return;
+ }
+
+ // Asynchronously query details about product
+ Thread thread = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try {
+ ArrayList<String> productIds = new ArrayList<String>();
+ productIds.add(productId);
+ Bundle productIdBundle = new Bundle();
+ productIdBundle.putStringArrayList("ITEM_ID_LIST", productIds);
+
+ Bundle bundle = m_service.getSkuDetails(IAP_VERSION,
+ m_context.getPackageName(),
+ "inapp",
+ productIdBundle);
+ int responseCode = bundleResponseCode(bundle);
+ if (responseCode != RESULT_OK) {
+ Log.e(TAG, "queryDetails: Couldn't retrieve sku details.");
+ queryFailed(m_nativePointer, productId);
+ return;
+ }
+
+ ArrayList<String> detailsList = bundle.getStringArrayList("DETAILS_LIST");
+ if (detailsList == null) {
+ Log.e(TAG, "queryDetails: No details list in response.");
+ queryFailed(m_nativePointer, productId);
+ return;
+ }
+
+ for (String details : detailsList) {
+ try {
+ JSONObject jo = new JSONObject(details);
+ String queriedProductId = jo.getString("productId");
+ String queriedPrice = jo.getString("price");
+ if (queriedProductId == null || queriedPrice == null) {
+ Log.e(TAG, "Data missing from product details.");
+ } else if (productId.equals(queriedProductId)) {
+ registerProduct(m_nativePointer, queriedProductId, queriedPrice);
+ return;
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ queryFailed(m_nativePointer, productId);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ queryFailed(m_nativePointer, productId);
+ }
+ }
+ });
+ thread.start();
+ }
+
+ public void setPublicKey(String publicKey)
+ {
+ m_publicKey = publicKey;
+ }
+
+ 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);
+ private native static void registerPurchased(long nativePointer, String productId, String signature, String data);
+}
diff --git a/src/android/src/com/digia/qt5/android/mobileextras/Security.java b/src/android/src/com/digia/qt5/android/mobileextras/Security.java
new file mode 100644
index 0000000..1c56746
--- /dev/null
+++ b/src/android/src/com/digia/qt5/android/mobileextras/Security.java
@@ -0,0 +1,131 @@
+/* Copyright (c) 2012 Google Inc.
+ * Copyright (c) 2014 Digia Plc and/or its subsidiary(-ies).
+ *
+ * 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.digia.qt5.android.mobileextras;
+
+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;
+ }
+}
diff --git a/src/imports/imports.pro b/src/imports/imports.pro
new file mode 100644
index 0000000..33e4bda
--- /dev/null
+++ b/src/imports/imports.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+SUBDIRS = inapppurchase
+
diff --git a/src/imports/inapppurchase/inapppurchase.cpp b/src/imports/inapppurchase/inapppurchase.cpp
new file mode 100644
index 0000000..b6d7fa2
--- /dev/null
+++ b/src/imports/inapppurchase/inapppurchase.cpp
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinappproductconsumable_p.h"
+#include "qinappstoreqmltype_p.h"
+
+#include <QtQml/qqmlextensionplugin.h>
+#include <QtQml/qqml.h>
+
+#include <QtMobileExtras/qinappproduct.h>
+#include <QtMobileExtras/qinapptransaction.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppPurchaseModule : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0")
+public:
+ void registerTypes(const char *uri)
+ {
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("QtMobileExtras"));
+
+ qmlRegisterType<QInAppStoreQmlType>(uri,
+ 1, 0,
+ "Store");
+ qmlRegisterType<QInAppProductConsumable>(uri,
+ 1, 0,
+ "ConsumableProduct");
+ qmlRegisterType<QInAppProductConsumable>(uri,
+ 1, 0,
+ "UnlockableProduct");
+ qmlRegisterUncreatableType<QInAppTransaction>(uri,
+ 1, 0,
+ "Transaction",
+ trUtf8("Transaction is provided by InAppStore"));
+ }
+
+ void initializeEngine(QQmlEngine *engine, const char *uri)
+ {
+ Q_UNUSED(uri);
+ Q_UNUSED(engine);
+ }
+};
+
+QT_END_NAMESPACE
+
+#include "inapppurchase.moc"
+
+
+
diff --git a/src/imports/inapppurchase/inapppurchase.pro b/src/imports/inapppurchase/inapppurchase.pro
new file mode 100644
index 0000000..3511dc5
--- /dev/null
+++ b/src/imports/inapppurchase/inapppurchase.pro
@@ -0,0 +1,21 @@
+CXX_MODULE = mobileextras
+TARGET = declarative_mobileextras
+TARGETPATH = QtMobileExtras
+IMPORT_VERSION = 1.0
+
+QT += qml quick mobileextras
+SOURCES += inapppurchase.cpp \
+ qinappproductconsumable.cpp \
+ qinappproductqmltype.cpp \
+ qinappproductunlockable.cpp \
+ qinappstoreqmltype.cpp
+
+load(qml_plugin)
+
+HEADERS += \
+ qinappproductconsumable_p.h \
+ qinappproductqmltype_p.h \
+ qinappproductunlockable_p.h \
+ qinappstoreqmltype_p.h
+
+OTHER_FILES += qmldir
diff --git a/src/imports/inapppurchase/qinappproductconsumable.cpp b/src/imports/inapppurchase/qinappproductconsumable.cpp
new file mode 100644
index 0000000..3ea684a
--- /dev/null
+++ b/src/imports/inapppurchase/qinappproductconsumable.cpp
@@ -0,0 +1,31 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinappproductconsumable_p.h"
+#include <QtMobileExtras/qinappstore.h>
+
+QT_BEGIN_NAMESPACE
+
+QInAppProductConsumable::QInAppProductConsumable(QObject *parent)
+ : QInAppProductQmlType(QInAppProduct::Consumable, parent)
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/imports/inapppurchase/qinappproductconsumable_p.h b/src/imports/inapppurchase/qinappproductconsumable_p.h
new file mode 100644
index 0000000..977fe8a
--- /dev/null
+++ b/src/imports/inapppurchase/qinappproductconsumable_p.h
@@ -0,0 +1,39 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPPRODUCTCONSUMABLE_P_H
+#define QINAPPPRODUCTCONSUMABLE_P_H
+
+#include "qinappproductqmltype_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QInAppStore;
+class QInAppTransaction;
+class QInAppProductConsumable : public QInAppProductQmlType
+{
+ Q_OBJECT
+public:
+ explicit QInAppProductConsumable(QObject *parent = 0);
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPPRODUCTCONSUMABLE_P_H
diff --git a/src/imports/inapppurchase/qinappproductqmltype.cpp b/src/imports/inapppurchase/qinappproductqmltype.cpp
new file mode 100644
index 0000000..566e9e9
--- /dev/null
+++ b/src/imports/inapppurchase/qinappproductqmltype.cpp
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinappproductqmltype_p.h"
+#include "qinappstoreqmltype_p.h"
+#include <QtMobileExtras/qinapptransaction.h>
+#include <QtMobileExtras/qinappstore.h>
+#include <QtCore/qcoreevent.h>
+
+QT_BEGIN_NAMESPACE
+
+QInAppProductQmlType::QInAppProductQmlType(QInAppProduct::ProductType requiredType, QObject *parent)
+ : QObject(parent)
+ , m_status(Uninitialized)
+ , m_requiredType(requiredType)
+ , m_componentComplete(false)
+ , m_store(0)
+ , m_product(0)
+{
+}
+
+void QInAppProductQmlType::setStore(QInAppStore *store)
+{
+ if (m_store == store && m_store != 0)
+ return;
+
+ if (m_store != 0)
+ m_store->disconnect(this);
+
+ m_store = store;
+ if (m_store == 0) {
+ qWarning("Parent of products should be a Store instance.");
+ } else {
+ connect(m_store, SIGNAL(productRegistered(QInAppProduct*)),
+ this, SLOT(handleProductRegistered(QInAppProduct *)));
+ connect(m_store, SIGNAL(productUnknown(QInAppProduct::ProductType,QString)),
+ this, SLOT(handleProductUnknown(QInAppProduct::ProductType,QString)));
+ connect(m_store, SIGNAL(transactionReady(QInAppTransaction*)),
+ this, SLOT(handleTransaction(QInAppTransaction*)));
+ }
+
+ updateProduct();
+}
+
+void QInAppProductQmlType::componentComplete()
+{
+ if (!m_componentComplete) {
+ m_componentComplete = true;
+ updateProduct();
+ }
+}
+
+void QInAppProductQmlType::setIdentifier(const QString &identifier)
+{
+ if (m_identifier == identifier || !m_componentComplete)
+ return;
+
+ m_identifier = identifier;
+ updateProduct();
+ emit identifierChanged();
+}
+
+void QInAppProductQmlType::updateProduct()
+{
+ qDebug("store == %p", m_store);
+ if (m_store == 0)
+ return;
+
+ QInAppProduct *product = m_store->registeredProduct(m_identifier);
+ qDebug("product == %p vs. %p", product, m_product);
+ if (product != 0 && product == m_product)
+ return;
+
+ Status oldStatus = m_status;
+ if (product == 0) {
+ m_status = PendingRegistration;
+ m_store->registerProduct(m_requiredType, m_identifier);
+ } else if (product->productType() != m_requiredType) {
+ product = 0;
+ m_status = Unknown;
+ } else {
+ m_status = Registered;
+ }
+
+ setProduct(product);
+ if (oldStatus != m_status)
+ emit statusChanged();
+}
+
+QString QInAppProductQmlType::identifier() const
+{
+ return m_identifier;
+}
+
+QInAppProductQmlType::Status QInAppProductQmlType::status() const
+{
+ return m_status;
+}
+
+QString QInAppProductQmlType::price() const
+{
+ return m_product != 0 ? m_product->price() : QString();
+}
+
+void QInAppProductQmlType::setProduct(QInAppProduct *product)
+{
+ if (m_product == product)
+ return;
+
+ QString oldPrice = price();
+ m_product = product;
+ if (price() != oldPrice)
+ emit priceChanged();
+}
+
+void QInAppProductQmlType::handleProductRegistered(QInAppProduct *product)
+{
+ if (product->identifier() == m_identifier && product->productType() == m_requiredType) {
+ setProduct(product);
+ if (m_status != Registered) {
+ m_status = Registered;
+ emit statusChanged();
+ }
+ } else if (product->identifier() == m_identifier) {
+ setProduct(0);
+ if (m_status != Unknown) {
+ m_status = Unknown;
+ emit statusChanged();
+ }
+ }
+}
+
+void QInAppProductQmlType::handleProductUnknown(QInAppProduct::ProductType, const QString &identifier)
+{
+ if (identifier == m_identifier) {
+ setProduct(0);
+ if (m_status != Unknown) {
+ m_status = Unknown;
+ emit statusChanged();
+ }
+ }
+}
+
+void QInAppProductQmlType::handleTransaction(QInAppTransaction *transaction)
+{
+ if (transaction->status() == QInAppTransaction::PurchaseApproved)
+ emit purchaseSucceeded(transaction);
+ else
+ emit purchaseFailed(transaction);
+}
+
+void QInAppProductQmlType::purchase()
+{
+ if (m_product != 0)
+ m_product->purchase();
+ else
+ qWarning("Attempted to purchase unregistered product");
+}
+
+QT_END_NAMESPACE
diff --git a/src/imports/inapppurchase/qinappproductqmltype_p.h b/src/imports/inapppurchase/qinappproductqmltype_p.h
new file mode 100644
index 0000000..1f6ae3e
--- /dev/null
+++ b/src/imports/inapppurchase/qinappproductqmltype_p.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPPRODUCTQMLTYPE_P_H
+#define QINAPPPRODUCTQMLTYPE_P_H
+
+#include <QtMobileExtras/qinappproduct.h>
+#include <QtQml/qqmlparserstatus.h>
+#include <QtQuick/qquickitem.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppStore;
+class QInAppTransaction;
+class QInAppProductQmlType : public QObject, public QQmlParserStatus
+{
+ Q_OBJECT
+ Q_ENUMS(Status)
+ Q_PROPERTY(QString identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged)
+ Q_PROPERTY(QString price READ price NOTIFY priceChanged)
+ Q_PROPERTY(Status status READ status NOTIFY statusChanged)
+public:
+ enum Status {
+ Uninitialized,
+ PendingRegistration,
+ Registered,
+ Unknown
+ };
+
+ explicit QInAppProductQmlType(QInAppProduct::ProductType productType,
+ QObject *parent = 0);
+
+ Q_INVOKABLE void purchase();
+
+ void setIdentifier(const QString &identifier);
+ QString identifier() const;
+
+ Status status() const;
+ QString price() const;
+
+ void setStore(QInAppStore *store);
+
+Q_SIGNALS:
+ void purchaseSucceeded(QInAppTransaction *transaction);
+ void purchaseFailed(QInAppTransaction *transaction);
+ void identifierChanged();
+ void statusChanged();
+ void priceChanged();
+
+protected:
+ void componentComplete();
+ void classBegin() {}
+
+private Q_SLOTS:
+ void handleTransaction(QInAppTransaction *transaction);
+ void handleProductRegistered(QInAppProduct *product);
+ void handleProductUnknown(QInAppProduct::ProductType, const QString &identifier);
+
+private:
+ void setProduct(QInAppProduct *product);
+ void updateProduct();
+
+ QString m_identifier;
+ Status m_status;
+ QInAppProduct::ProductType m_requiredType;
+ bool m_componentComplete;
+
+ QInAppStore *m_store;
+ QInAppProduct *m_product;
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPPRODUCTQMLTYPE_P_H
diff --git a/src/imports/inapppurchase/qinappproductunlockable.cpp b/src/imports/inapppurchase/qinappproductunlockable.cpp
new file mode 100644
index 0000000..2d268c7
--- /dev/null
+++ b/src/imports/inapppurchase/qinappproductunlockable.cpp
@@ -0,0 +1,30 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinappproductunlockable_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QInAppProductUnlockable::QInAppProductUnlockable(QObject *parent)
+ : QInAppProductQmlType(QInAppProduct::Unlockable, parent)
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/imports/inapppurchase/qinappproductunlockable_p.h b/src/imports/inapppurchase/qinappproductunlockable_p.h
new file mode 100644
index 0000000..715c5a1
--- /dev/null
+++ b/src/imports/inapppurchase/qinappproductunlockable_p.h
@@ -0,0 +1,33 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPPRODUCTUNLOCKABLE_P_H
+#define QINAPPPRODUCTUNLOCKABLE_P_H
+
+#include "qinappproductqmltype_p.h"
+
+class QInAppProductUnlockable : public QInAppProductQmlType
+{
+ Q_OBJECT
+public:
+ explicit QInAppProductUnlockable(QObject *parent = 0);
+};
+
+#endif // QINAPPPRODUCTUNLOCKABLE_P_H
diff --git a/src/imports/inapppurchase/qinappstoreqmltype.cpp b/src/imports/inapppurchase/qinappstoreqmltype.cpp
new file mode 100644
index 0000000..0d3d917
--- /dev/null
+++ b/src/imports/inapppurchase/qinappstoreqmltype.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinappstoreqmltype_p.h"
+#include <QtMobileExtras/qinappstore.h>
+
+static void addProduct(QQmlListProperty<QInAppProductQmlType> *property, QInAppProductQmlType *product)
+{
+ QInAppStoreQmlType *store = qobject_cast<QInAppStoreQmlType *>(property->object);
+ Q_ASSERT(store != 0);
+ product->setStore(store->store());
+
+ QList<QInAppProductQmlType *> *m_products = reinterpret_cast<QList<QInAppProductQmlType *> *>(property->data);
+ Q_ASSERT(m_products != 0);
+
+ m_products->append(product);
+}
+
+static int productCount(QQmlListProperty<QInAppProductQmlType> *property)
+{
+ QList<QInAppProductQmlType *> *m_products = reinterpret_cast<QList<QInAppProductQmlType *> *>(property->data);
+ Q_ASSERT(m_products != 0);
+
+ return m_products->size();
+}
+
+static void clearProducts(QQmlListProperty<QInAppProductQmlType> *property)
+{
+ QList<QInAppProductQmlType *> *m_products = reinterpret_cast<QList<QInAppProductQmlType *> *>(property->data);
+ Q_ASSERT(m_products != 0);
+
+ foreach (QInAppProductQmlType *product, m_products)
+ product->setStore(0);
+ m_products->clear();
+}
+
+static QInAppProductQmlType *productAt(QQmlListProperty<QInAppProductQmlType> *property, int index)
+{
+ QList<QInAppProductQmlType *> *m_products = reinterpret_cast<QList<QInAppProductQmlType *> *>(property->data);
+ Q_ASSERT(m_products != 0);
+
+ return m_products->at(index);
+}
+
+QInAppStoreQmlType::QInAppStoreQmlType(QObject *parent)
+ : QObject(parent)
+ , m_store(new QInAppStore(this))
+{
+}
+
+QInAppStore *QInAppStoreQmlType::store() const
+{
+ return m_store;
+}
+
+QQmlListProperty<QInAppProductQmlType> QInAppStoreQmlType::products()
+{
+ return QQmlListProperty<QInAppProductQmlType>(this, &m_products, &addProduct, &productCount, &productAt, &clearProducts);
+}
diff --git a/src/imports/inapppurchase/qinappstoreqmltype_p.h b/src/imports/inapppurchase/qinappstoreqmltype_p.h
new file mode 100644
index 0000000..746938c
--- /dev/null
+++ b/src/imports/inapppurchase/qinappstoreqmltype_p.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPSTOREQMLTYPE_P_H
+#define QINAPPSTOREQMLTYPE_P_H
+
+#include "qinappproductqmltype_p.h"
+#include <QtQuick/qquickitem.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppStore;
+class QInAppStoreQmlType : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QQmlListProperty<QInAppProductQmlType> products READ products DESIGNABLE false)
+ Q_CLASSINFO("DefaultProperty", "products")
+public:
+ explicit QInAppStoreQmlType(QObject *parent = 0);
+
+ QInAppStore *store() const;
+ QQmlListProperty<QInAppProductQmlType> products();
+
+private:
+ QInAppStore *m_store;
+ QList<QInAppProductQmlType *> m_products;
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPSTOREQMLTYPE_P_H
diff --git a/src/imports/inapppurchase/qmldir b/src/imports/inapppurchase/qmldir
new file mode 100644
index 0000000..2b0c271
--- /dev/null
+++ b/src/imports/inapppurchase/qmldir
@@ -0,0 +1,4 @@
+module QtMobileExtras
+plugin declarative_mobileextras
+typeinfo plugins.qmltypes
+classname QInAppPurchaseModule
diff --git a/src/mobileextras/inapppurchase/android/android.pri b/src/mobileextras/inapppurchase/android/android.pri
new file mode 100644
index 0000000..31ac238
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/android.pri
@@ -0,0 +1,10 @@
+INCLUDEPATH += $$PWD
+SOURCES += \
+ $$PWD/qandroidinapppurchasebackend.cpp \
+ $$PWD/qandroidjni.cpp \
+ $$PWD/qandroidinapptransaction.cpp \
+ $$PWD/qandroidinappproduct.cpp
+HEADERS += \
+ $$PWD/qandroidinapppurchasebackend_p.h \
+ $$PWD/qandroidinapptransaction_p.h \
+ $$PWD/qandroidinappproduct_p.h
diff --git a/src/mobileextras/inapppurchase/android/qandroidinappproduct.cpp b/src/mobileextras/inapppurchase/android/qandroidinappproduct.cpp
new file mode 100644
index 0000000..116c1d9
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/qandroidinappproduct.cpp
@@ -0,0 +1,40 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qandroidinappproduct_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QAndroidInAppProduct::QAndroidInAppProduct(const QString &price,
+ ProductType productType,
+ const QString &identifier,
+ QObject *parent)
+ : QInAppProduct(price, productType, identifier, parent)
+{
+}
+
+
+void QAndroidInAppProduct::purchase()
+{
+#warning Unimplemented
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/mobileextras/inapppurchase/android/qandroidinappproduct_p.h b/src/mobileextras/inapppurchase/android/qandroidinappproduct_p.h
new file mode 100644
index 0000000..e107e07
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/qandroidinappproduct_p.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QANDROIDINAPPPRODUCT_P_H
+#define QANDROIDINAPPPRODUCT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qinappproduct.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidInAppProduct : public QInAppProduct
+{
+ Q_OBJECT
+public:
+ explicit QAndroidInAppProduct(const QString &price,
+ ProductType productType,
+ const QString &identifier,
+ QObject *parent = 0);
+
+
+ void purchase();
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDINAPPPRODUCT_P_H
diff --git a/src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend.cpp b/src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend.cpp
new file mode 100644
index 0000000..4f11b17
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend.cpp
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qandroidinapppurchasebackend_p.h"
+#include "qandroidinappproduct_p.h"
+#include "qandroidinapptransaction_p.h"
+
+#include <QtAndroidExtras/qandroidfunctions.h>
+
+QT_BEGIN_NAMESPACE
+
+QAndroidInAppPurchaseBackend::QAndroidInAppPurchaseBackend(QObject *parent)
+ : QInAppPurchaseBackend(parent)
+ , m_mutex(QMutex::Recursive)
+ , m_isReady(false)
+{
+ m_javaObject = QAndroidJniObject("com/digia/qt5/android/mobileextras/QtInAppPurchase",
+ "(Landroid/content/Context;J)V",
+ QtAndroid::androidActivity().object<jobject>(),
+ reinterpret_cast<jlong>(this));
+ if (!m_javaObject.isValid()) {
+ qWarning("Cannot initialize IAP backend for Android due to missing dependency: QtInAppPurchase class");
+ return;
+ }
+}
+
+void QAndroidInAppPurchaseBackend::initialize()
+{
+ m_javaObject.callMethod<void>("initializeConnection");
+#warning Read tokens here
+}
+
+bool QAndroidInAppPurchaseBackend::isReady() const
+{
+ QMutexLocker locker(&m_mutex);
+ return m_isReady;
+}
+
+void QAndroidInAppPurchaseBackend::restorePurchases()
+{
+ // ### Go through existing purchases, remove finalization token and emit transactions
+#warning Unimplemented
+}
+
+void QAndroidInAppPurchaseBackend::queryProduct(QInAppProduct::ProductType productType,
+ const QString &identifier)
+{
+ QMutexLocker locker(&m_mutex);
+ if (m_productTypeForPendingId.contains(identifier)) {
+ qWarning("Product query already pending for %s", qPrintable(identifier));
+ return;
+ }
+
+ m_productTypeForPendingId[identifier] = productType;
+ m_javaObject.callMethod<void>("queryDetails",
+ "(Ljava/lang/String;)V",
+ QAndroidJniObject::fromString(identifier).object<jstring>());
+}
+
+void QAndroidInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ if (propertyName.compare(QStringLiteral("AndroidPublicKey"), Qt::CaseInsensitive) == 0) {
+ m_javaObject.callMethod<void>("setPublicKey",
+ "(Ljava/lang/String;)",
+ QAndroidJniObject::fromString(value).object<jstring>());
+ }
+}
+
+void QAndroidInAppPurchaseBackend::registerQueryFailure(const QString &productId)
+{
+ QMutexLocker locker(&m_mutex);
+ QHash<QString, QInAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(productId);
+ Q_ASSERT(it != m_productTypeForPendingId.end());
+
+ emit productQueryFailed(it.value(), it.key());
+ m_productTypeForPendingId.erase(it);
+}
+
+bool QAndroidInAppPurchaseBackend::transactionFinalizedForProduct(QInAppProduct *product)
+{
+ Q_ASSERT(m_signatureAndDataForPurchase.contains(product->identifier()));
+ return product->productType() == QInAppProduct::Consumable
+ || m_finalizedUnlockableProducts.contains(product->identifier());
+}
+
+void QAndroidInAppPurchaseBackend::createTransactionForProduct(QInAppTransaction::TransactionStatus status,
+ QInAppProduct *product,
+ const QPair<QString, QString> &signatureAndData)
+{
+ QAndroidInAppTransaction *transaction = new QAndroidInAppTransaction(signatureAndData.first,
+ signatureAndData.second,
+ status,
+ product,
+ this);
+ emit transactionReady(transaction);
+}
+
+void QAndroidInAppPurchaseBackend::checkFinalizationStatus(QInAppProduct *product)
+{
+ // 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, QPair<QString, QString> >::iterator it = m_signatureAndDataForPurchase.find(product->identifier());
+ if (it == m_signatureAndDataForPurchase.end())
+ return;
+
+ if (!transactionFinalizedForProduct(product))
+ createTransactionForProduct(QInAppTransaction::PurchaseApproved, product, it.value());
+
+ m_signatureAndDataForPurchase.erase(it);
+}
+
+void QAndroidInAppPurchaseBackend::registerProduct(const QString &productId, const QString &price)
+{
+ QMutexLocker locker(&m_mutex);
+ QHash<QString, QInAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(productId);
+ Q_ASSERT(it != m_productTypeForPendingId.end());
+
+ QAndroidInAppProduct *product = new QAndroidInAppProduct(price, it.value(), it.key(), this);
+ checkFinalizationStatus(product);
+
+ emit productQueryDone(product);
+ m_productTypeForPendingId.erase(it);
+}
+
+void QAndroidInAppPurchaseBackend::registerPurchased(const QString &productId, const QString &signature, const QString &data)
+{
+ QMutexLocker locker(&m_mutex);
+ m_signatureAndDataForPurchase.insert(productId, qMakePair(signature, data));
+}
+
+
+void QAndroidInAppPurchaseBackend::registerReady()
+{
+ QMutexLocker locker(&m_mutex);
+ m_isReady = true;
+ emit ready();
+}
+
+QT_END_NAMESPACE
diff --git a/src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend_p.h b/src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend_p.h
new file mode 100644
index 0000000..9422e2c
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/qandroidinapppurchasebackend_p.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef QANDROIDINAPPPURCHASEBACKEND_P_H
+#define QANDROIDINAPPPURCHASEBACKEND_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qinapppurchasebackend_p.h"
+#include "qinappproduct.h"
+#include "qinapptransaction.h"
+
+#include <QtCore/qmutex.h>
+#include <QtCore/qset.h>
+#include <QtAndroidExtras/qandroidjniobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidInAppPurchaseBackend : public QInAppPurchaseBackend
+{
+ Q_OBJECT
+public:
+ explicit QAndroidInAppPurchaseBackend(QObject *parent = 0);
+
+ void initialize();
+ bool isReady() const;
+
+ void queryProduct(QInAppProduct::ProductType productType, const QString &identifier);
+ void restorePurchases();
+
+ void setPlatformProperty(const QString &propertyName, const QString &value);
+
+ // Callbacks from Java
+ void registerQueryFailure(const QString &productId);
+ void registerProduct(const QString &productId, const QString &price);
+ void registerPurchased(const QString &productId, const QString &signature, const QString &data);
+ void registerReady();
+
+private:
+ void checkFinalizationStatus(QInAppProduct *product);
+ void createTransactionForProduct(QInAppTransaction::TransactionStatus status,
+ QInAppProduct *product,
+ const QPair<QString, QString> &signature);
+ bool transactionFinalizedForProduct(QInAppProduct *product);
+
+ mutable QMutex m_mutex;
+ bool m_isReady;
+ QAndroidJniObject m_javaObject;
+ QHash<QString, QInAppProduct::ProductType> m_productTypeForPendingId;
+ QHash<QString, QPair<QString, QString> > m_signatureAndDataForPurchase;
+ QSet<QString> m_finalizedUnlockableProducts;
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDINAPPPURCHASEBACKEND_P_H
diff --git a/src/mobileextras/inapppurchase/android/qandroidinapptransaction.cpp b/src/mobileextras/inapppurchase/android/qandroidinapptransaction.cpp
new file mode 100644
index 0000000..5e5ba20
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/qandroidinapptransaction.cpp
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qandroidinapptransaction_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QAndroidInAppTransaction::QAndroidInAppTransaction(const QString &signature,
+ const QString &data,
+ TransactionStatus status,
+ QInAppProduct *product,
+ QObject *parent)
+ : QInAppTransaction(status, product, parent)
+ , m_signature(signature)
+ , m_data(data)
+{
+}
+
+QString QAndroidInAppTransaction::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 QInAppTransaction::platformProperty(propertyName);
+}
+
+void QAndroidInAppTransaction::finalize()
+{
+ // ### consume consumable or store finalized data for unlockable
+#warning Unimplemented
+}
+
+QT_END_NAMESPACE
diff --git a/src/mobileextras/inapppurchase/android/qandroidinapptransaction_p.h b/src/mobileextras/inapppurchase/android/qandroidinapptransaction_p.h
new file mode 100644
index 0000000..f2dcfd8
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/qandroidinapptransaction_p.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef QANDROIDINAPPTRANSACTION_P_H
+#define QANDROIDINAPPTRANSACTION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qobject.h>
+#include "qinapptransaction.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidInAppTransaction : public QInAppTransaction
+{
+ Q_OBJECT
+public:
+ explicit QAndroidInAppTransaction(const QString &signature,
+ const QString &data,
+ TransactionStatus status,
+ QInAppProduct *product,
+ QObject *parent = 0);
+
+ void finalize();
+ QString platformProperty(const QString &propertyName) const;
+
+private:
+ QString m_signature;
+ QString m_data;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDINAPPTRANSACTION_P_H
diff --git a/src/mobileextras/inapppurchase/android/qandroidjni.cpp b/src/mobileextras/inapppurchase/android/qandroidjni.cpp
new file mode 100644
index 0000000..2bb2ebd
--- /dev/null
+++ b/src/mobileextras/inapppurchase/android/qandroidjni.cpp
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qandroidinapppurchasebackend_p.h"
+
+#include <QtAndroidExtras/qandroidjniobject.h>
+#include <jni.h>
+
+QT_USE_NAMESPACE
+
+static void queryFailed(jclass, jlong nativePointer, jstring productId)
+{
+ QAndroidInAppPurchaseBackend *backend = reinterpret_cast<QAndroidInAppPurchaseBackend *>(nativePointer);
+ backend->registerQueryFailure(QAndroidJniObject(productId).toString());
+}
+
+static void purchasedProductsQueried(jclass, jlong nativePointer)
+{
+ QAndroidInAppPurchaseBackend *backend = reinterpret_cast<QAndroidInAppPurchaseBackend *>(nativePointer);
+ backend->registerReady();
+}
+
+static void registerProduct(jclass, jlong nativePointer, jstring productId, jstring price)
+{
+ QAndroidInAppPurchaseBackend *backend = reinterpret_cast<QAndroidInAppPurchaseBackend *>(nativePointer);
+ backend->registerProduct(QAndroidJniObject(productId).toString(),
+ QAndroidJniObject(price).toString());
+}
+
+static void registerPurchased(jclass, jlong nativePointer, jstring productId, jstring signature, jstring data)
+{
+ QAndroidInAppPurchaseBackend *backend = reinterpret_cast<QAndroidInAppPurchaseBackend *>(nativePointer);
+ backend->registerPurchased(QAndroidJniObject(productId).toString(),
+ QAndroidJniObject(signature).toString(),
+ QAndroidJniObject(data).toString());
+
+}
+
+static JNINativeMethod methods[] = {
+ {"queryFailed", "(JLjava/lang/String;)V", (void *)queryFailed},
+ {"purchasedProductsQueried", "(J)V", (void *)purchasedProductsQueried},
+ {"registerProduct", "(JLjava/lang/String;Ljava/lang/String;)V", (void *)registerProduct},
+ {"registerPurchased", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", (void *)registerPurchased}
+};
+
+jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
+{
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_4) != JNI_OK)
+ return JNI_FALSE;
+
+ jclass clazz = env->FindClass("com/digia/qt5/android/mobileextras/QtInAppPurchase");
+ if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0)
+ return JNI_FALSE;
+
+ return JNI_VERSION_1_4;
+}
diff --git a/src/mobileextras/inapppurchase/inapppurchase.pri b/src/mobileextras/inapppurchase/inapppurchase.pri
new file mode 100644
index 0000000..abaa22d
--- /dev/null
+++ b/src/mobileextras/inapppurchase/inapppurchase.pri
@@ -0,0 +1,24 @@
+INCLUDEPATH += $$PWD
+
+ANDROID_PERMISSIONS += \
+ com.android.vending.BILLING
+
+HEADERS += \
+ $$PWD/qinappstore.h \
+ $$PWD/qinappproduct.h \
+ $$PWD/qinapptransaction.h \
+ $$PWD/qinappstore_p.h \
+ $$PWD/qinapppurchasebackend_p.h \
+ $$PWD/qinapppurchasebackendfactory_p.h
+
+SOURCES += \
+ $$PWD/qinappproduct.cpp \
+ $$PWD/qinapptransaction.cpp \
+ $$PWD/qinappstore.cpp \
+ $$PWD/qinapppurchasebackend.cpp \
+ $$PWD/qinapppurchasebackendfactory.cpp
+
+android {
+ QT += androidextras
+ include ($$PWD/android/android.pri)
+}
diff --git a/src/mobileextras/inapppurchase/qinappproduct.cpp b/src/mobileextras/inapppurchase/qinappproduct.cpp
new file mode 100644
index 0000000..91b7074
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinappproduct.cpp
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinappproduct.h"
+
+QT_BEGIN_NAMESPACE
+
+struct QInAppProductPrivate
+{
+ QInAppProductPrivate(const QString &price, QInAppProduct::ProductType type, const QString &id)
+ : localPrice(price)
+ , productType(type)
+ , identifier(id)
+ {
+ }
+
+ QString localPrice;
+ QInAppProduct::ProductType productType;
+ QString identifier;
+};
+
+/*!
+ * \class QInAppProduct
+ * \brief A product registered in the store
+ *
+ * QInAppProduct encapsulates a product in the external store after it has been registered in \c QInAppStore
+ * and confirmed to exist. It has an identifier which matches the identifier of the product in the external
+ * store, it has a price which is retrieved from the external store, and it has a product type.
+ *
+ * The product type can be either \c Consumable or \c Unlockable. The former type of products can be purchased
+ * any number of times as long as each transaction is finalized explicitly by the application. The latter type
+ * can only be purchased once.
+ */
+
+/*!
+ * \internal
+ */\
+QInAppProduct::QInAppProduct(const QString &price, ProductType productType, const QString &identifier, QObject *parent)
+ : QObject(parent)
+{
+ d = QSharedPointer<QInAppProductPrivate>(new QInAppProductPrivate(price, productType, identifier));
+}
+
+/*!
+ * \internal
+ */\
+QInAppProduct::~QInAppProduct()
+{
+}
+
+/*!
+ * Returns the price of the product as reported by the external store. This is usually the price in the
+ * locale of the current user.
+ */
+QString QInAppProduct::price() const
+{
+ return d->localPrice;
+}
+
+/*!
+ * Returns the identifier of the product. This matches the identifier of the product which is registered
+ * in the external store.
+ */
+QString QInAppProduct::identifier() const
+{
+ return d->identifier;
+}
+
+/*!
+ * Returns the type of the product. This can either be \c Consumable or \c Unlockable. The former are products
+ * which can be purchased any number of times (granted that each transaction is explicitly finalized by the
+ * application first) and the latter are products which can only be purchased once per user.
+ */
+QInAppProduct::ProductType QInAppProduct::productType() const
+{
+ return d->productType;
+}
+
+/*!
+ * \fn void QInAppProduct::purchase()
+ *
+ * Launches the purchase flow for this product. The purchase is done asynchronously. When the purchase has
+ * either been completed successfully or failed for some reason, the QInAppStore instance containing
+ * this product will emit a QInAppStore::transactionReady() signal with information about the transaction.
+ *
+ * \sa QInAppTransaction
+ */
+
+QT_END_NAMESPACE
diff --git a/src/mobileextras/inapppurchase/qinappproduct.h b/src/mobileextras/inapppurchase/qinappproduct.h
new file mode 100644
index 0000000..8593a0a
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinappproduct.h
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPPRODUCT_H
+#define QINAPPPRODUCT_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtMobileExtras/qmobileextrasglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppProductPrivate;
+class Q_MOBILEEXTRAS_EXPORT QInAppProduct: public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(ProductType)
+ Q_PROPERTY(QString identifier READ identifier CONSTANT)
+ Q_PROPERTY(ProductType productType READ productType CONSTANT)
+ Q_PROPERTY(QString price READ price CONSTANT)
+
+public:
+ enum ProductType
+ {
+ Consumable,
+ Unlockable
+ };
+
+ ~QInAppProduct();
+
+ QString identifier() const;
+ ProductType productType() const;
+
+ QString price() const;
+
+ Q_INVOKABLE virtual void purchase() = 0;
+
+protected:
+ explicit QInAppProduct(const QString &price, ProductType productType, const QString &identifier, QObject *parent = 0);
+
+private:
+ friend class QInAppStore;
+ Q_DISABLE_COPY(QInAppProduct)
+
+ QSharedPointer<QInAppProductPrivate> d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPPRODUCT_H
diff --git a/src/mobileextras/inapppurchase/qinapppurchasebackend.cpp b/src/mobileextras/inapppurchase/qinapppurchasebackend.cpp
new file mode 100644
index 0000000..cbd7751
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinapppurchasebackend.cpp
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinapppurchasebackend_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QInAppPurchaseBackend::QInAppPurchaseBackend(QObject *parent)
+ : QObject(parent)
+{
+}
+
+void QInAppPurchaseBackend::initialize()
+{
+ emit ready();
+}
+
+bool QInAppPurchaseBackend::isReady() const
+{
+ return true;
+}
+
+void QInAppPurchaseBackend::queryProduct(QInAppProduct::ProductType productType,
+ const QString &identifier)
+{
+ qWarning("QInAppPurchaseBackend not implemented on this platform!");
+ Q_UNUSED(productType);
+ Q_UNUSED(identifier);
+}
+
+void QInAppPurchaseBackend::restorePurchases()
+{
+ qWarning("QInAppPurchaseBackend not implemented on this platform!");
+}
+
+void QInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ Q_UNUSED(propertyName);
+ Q_UNUSED(value);
+}
+
+QT_END_NAMESPACE
diff --git a/src/mobileextras/inapppurchase/qinapppurchasebackend_p.h b/src/mobileextras/inapppurchase/qinapppurchasebackend_p.h
new file mode 100644
index 0000000..a35145a
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinapppurchasebackend_p.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPPURCHASEBACKEND_P_H
+#define QINAPPPURCHASEBACKEND_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qinappproduct.h"
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppProduct;
+class QInAppTransaction;
+class QInAppPurchaseBackend : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QInAppPurchaseBackend(QObject *parent = 0);
+
+ virtual void initialize();
+ virtual bool isReady() const;
+
+ virtual void queryProduct(QInAppProduct::ProductType productType, const QString &identifier);
+ virtual void restorePurchases();
+
+ virtual void setPlatformProperty(const QString &propertyName, const QString &value);
+
+Q_SIGNALS:
+ void ready();
+ void transactionReady(QInAppTransaction *transaction);
+ void productQueryFailed(QInAppProduct::ProductType productType, const QString &identifier);
+ void productQueryDone(QInAppProduct *product);
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPPURCHASEBACKEND_P_H
diff --git a/src/mobileextras/inapppurchase/qinapppurchasebackendfactory.cpp b/src/mobileextras/inapppurchase/qinapppurchasebackendfactory.cpp
new file mode 100644
index 0000000..aed17e9
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinapppurchasebackendfactory.cpp
@@ -0,0 +1,40 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinapppurchasebackendfactory_p.h"
+
+#if defined(Q_OS_ANDROID)
+# include "qandroidinapppurchasebackend_p.h"
+#else
+# include "qinapppurchasebackend_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+QInAppPurchaseBackend *QInAppPurchaseBackendFactory::create()
+{
+#if defined(Q_OS_ANDROID)
+ return new QAndroidInAppPurchaseBackend;
+#else
+ return new QInAppPurchaseBackend;
+#endif
+}
+
+QT_END_NAMESPACE
diff --git a/src/mobileextras/inapppurchase/qinapppurchasebackendfactory_p.h b/src/mobileextras/inapppurchase/qinapppurchasebackendfactory_p.h
new file mode 100644
index 0000000..722dd79
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinapppurchasebackendfactory_p.h
@@ -0,0 +1,48 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPPURCHASEBACKENDFACTORY_P_H
+#define QINAPPPURCHASEBACKENDFACTORY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppPurchaseBackend;
+class QInAppPurchaseBackendFactory
+{
+public:
+ static QInAppPurchaseBackend *create();
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPPURCHASEBACKENDFACTORY_P_H
diff --git a/src/mobileextras/inapppurchase/qinappstore.cpp b/src/mobileextras/inapppurchase/qinappstore.cpp
new file mode 100644
index 0000000..a1f56ae
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinappstore.cpp
@@ -0,0 +1,268 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinappstore.h"
+#include "qinappstore_p.h"
+#include "qinapppurchasebackend_p.h"
+#include "qinapppurchasebackendfactory_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QInAppStore
+ \brief The main entry point for managing in-app purchases
+
+ QInAppStore is used for managing in-app purchases in your application in a
+ cross-platform way.
+
+ \section1 Using the QInAppStore
+ In general there are two steps to completing an in-app purchase using the
+ API:
+
+ \section2 Initialize the store
+ Upon start-up of your application, connect all signals in QInAppStore to
+ related slots in your own QObject. Then use the registerProduct() function
+ to register the ID of each product you expect to find registered in the
+ external store, as well as its type.
+
+ Registering a product is asynchronous, and will at some point yield one of
+ the following two signals:
+ 1. productRegistered() if the product was found in the external store with
+ a matching type.
+ 2. productUnknown() if the product was not found in the external store with
+ the type you specified.
+
+ In addition, a transactionReady() signal may be emitted for any existing
+ transaction which has not yet been finalized. At this point, you should
+ check if the transaction has previously been registered. If it hasn't,
+ register it right away. Finally, call QInAppTransaction::finalize() on
+ the transaction.
+
+ \section2 Complete a purchase
+ Once the items have been successfully registered in the store, you can
+ purchase them. Get the previously registered QInAppProduct using
+ registeredProduct() and call QInAppProduct::purchase(). This call is
+ also asynchronous.
+
+ At some point later on, the transactionReady() signal will be emitted for
+ the purchase. Check QInAppTransaction::status() to see if the purchase was
+ completed successfully. If it was, then you must save the information about
+ the purchase in a safe way, so that the application can restore it later.
+
+ When you are done, call QInAppTransaction::finalize(), regardless of its
+ status. Transactions which are not finalized will be emitted again the next
+ time your application calls registerProduct() for the same product.
+
+ \note Please mind that QInAppStore does not save the purchased
+ state of items in the store for you. The application should store this
+ information in a safe way upon receiving the transactionReady() signal,
+ before calling QInAppTransaction::finalize().
+
+ \section1 Types of purchases
+ There are two types of purchases supported by QInAppStore:
+ QInAppProduct::Consumable and QInAppProduct::Unlockable. The former will be
+ consumed when the transaction is completed and QInAppTransaction::finalize()
+ is called, meaning that it can be purchased again, any number of times.
+ Unlockable items can only be purchased once.
+
+ Consumable products are temporary and can be purchased multiple times.
+ Examples could be a day-ticket on the bus or a magic sword in a computer game.
+ Note that when purchasing the same product multiple times, you should call
+ QInAppTransaction::finalize() on each transaction before you can purchase the
+ same product again.
+
+ Unlockable products are products that a user will buy once, and the purchase
+ of these items will be persistent. It can typically be used for things like
+ unlocking content or functionality in the application.
+
+ \section1 Restoring purchases
+ If your application has unlockable products, and does not store the purchase
+ states of these products in a way which makes it possible to restore them
+ when the user reinstalls the application, you should provide a way for the
+ user to restore the purchases manually.
+
+ Call the restorePurchases() function to begin this process. Granted that
+ the remote store supports it, you will then at some point get transactionReady()
+ for each unlockable item which has previously been purchased by the current user.
+
+ Save the purchase state of each product and call QInAppTransaction::finalize() as
+ you would for a regular purchase.
+
+ Since restorePurchases() may, on some platforms, cause the user to be prompted for
+ their password, it should usually be called as a reaction to user input. For instance
+ applications can have a button in the UI which will trigger restorePurchases() and
+ which users can hit manually if they have reinstalled the application (or installed
+ it on a new device) and need to unlock the features that they have previously paid
+ for.
+
+ \note This depends on support for this functionality in the remote store. If
+ the remote store does not save the purchase state of unlockable products for
+ you, the call will yield no transactionReady() signals, as if no products have
+ been purchased. Both the Android and iOS backends support restoring unlockable
+ products.
+
+*/
+
+QInAppStore::QInAppStore(QObject *parent)
+ : QObject(parent)
+{
+ d = QSharedPointer<QInAppStorePrivate>(new QInAppStorePrivate);
+ setupBackend();
+}
+
+QInAppStore::~QInAppStore()
+{
+}
+
+/*!
+ * \internal
+ */
+void QInAppStore::setupBackend()
+{
+ d->backend = QInAppPurchaseBackendFactory::create();
+
+ connect(d->backend, SIGNAL(ready()),
+ this, SLOT(registerPendingProducts()));
+ connect(d->backend, SIGNAL(transactionReady(QInAppTransaction *)),
+ this, SIGNAL(transactionReady(QInAppTransaction *)));
+ connect(d->backend, SIGNAL(productQueryFailed(QInAppProduct::ProductType,QString)),
+ this, SIGNAL(productUnknown(QInAppProduct::ProductType,QString)));
+ connect(d->backend, SIGNAL(productQueryDone(QInAppProduct *)),
+ this, SLOT(registerProduct(QInAppProduct*)));
+}
+
+/*!
+ * \internal
+ */
+void QInAppStore::registerProduct(QInAppProduct *product)
+{
+ d->registeredProducts[product->identifier()] = product;
+ emit productRegistered(product);
+}
+
+/*!
+ * \internal
+ *
+ * Called when the backend is finished initialized and will create products which were
+ * registered while the backend was still working.
+ */
+void QInAppStore::registerPendingProducts()
+{
+ QHash<QString, QInAppProduct::ProductType>::const_iterator it;
+ for (it = d->pendingProducts.constBegin(); it != d->pendingProducts.constEnd(); ++it) {
+ QString identifier = it.key();
+ QInAppProduct::ProductType productType = it.value();
+ d->backend->queryProduct(productType, identifier);
+ }
+ d->pendingProducts.clear();
+}
+
+/*!
+ * Requests existing purchases of unlockable items and will yield a transactionReady()
+ * signal for each unlockable product that the remote store confirms have previously been
+ * purchased by the current user.
+ *
+ * This function can typically be used for restoring unlockable products when the application
+ * has been reinstalled and lost the saved purchase states.
+ *
+ * \note Calling this function may prompt the user for their password on some platforms.
+ */
+void QInAppStore::restorePurchases()
+{
+ d->backend->restorePurchases();
+}
+
+/*!
+ * Sets the platform specific property given by \a propertyName to \a value. This can be used
+ * to pass information to the platform implementation. The properties will be silently ignored
+ * on other platforms.
+ *
+ * Currently, the only supported platform property is "AndroidPublicKey" which is used by the Android
+ * backend to verify purchases. If it is not set, purchases will be accepted with no verification.
+ * (You can also do the verification manually by getting the signature from the QInAppTransaction object
+ * for the purchase.) For more information, see
+ * \l{http://developer.android.com/google/play/billing/billing_integrate.html#billing-security}
+ * {the Android documentation for billing security}.
+ *
+ */
+void QInAppStore::setPlatformProperty(const QString &propertyName, const QString &value)
+{
+ d->backend->setPlatformProperty(propertyName, value);
+}
+
+/*!
+ * Registers a product identified by \a identifier and with the given \a productType.
+ * The \a identifier must match the identifier of the product in the remote store. If
+ * the remote store differentiates between consumable and unlockable products, the
+ * \a productType must also match this.
+ *
+ * Calling this function will asynchronously yield either a productRegistered() or a
+ * productUnknown() signal. It may also yield a transactionReady() signal if there is
+ * a pending transaction for the product which has not yet been finalized.
+ */
+void QInAppStore::registerProduct(QInAppProduct::ProductType productType, const QString &identifier)
+{
+ if (!d->backend->isReady()) {
+ if (!d->hasCalledInitialize) {
+ d->hasCalledInitialize = true;
+ d->backend->initialize();
+ }
+ d->pendingProducts[identifier] = productType;
+ } else {
+ d->backend->queryProduct(productType, identifier);
+ }
+}
+
+/*!
+ * Returns the previously registered product uniquely known by the \a identifier.
+ */
+QInAppProduct *QInAppStore::registeredProduct(const QString &identifier) const
+{
+ return d->registeredProducts.value(identifier);
+}
+
+/*!
+ * \fn QInAppStore::productRegistered(QInAppProduct *product)
+ *
+ * This signal is emitted when information about a \a product has been collected from the
+ * remote store. It is emitted as a reaction to a registerProduct() call for the same
+ * product.
+ *
+ * \sa productUnknown()
+ */
+
+/*! /fn QInAppStore::productUnknown(QInAppProduct::ProductType productType, const QString &identifier)
+ *
+ * This signal is emitted when a product was registered using registerProduct() and matching
+ * information could not be provided by the remote store.
+ *
+ * \sa productRegistered()
+ */
+
+/*!
+ * \fn QInAppStore::transactionReady(QInAppTransaction *transaction)
+ *
+ * This signal is emitted whenever there is a \a transaction which needs to be finalized.
+ * It is emitted either when a purchase request has been made for a product, when restorePurchases()
+ * has been called and the product was previously purchased, or when registerProduct() was called
+ * for a product and there was a pending transaction for the product which had not yet been finalized.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/mobileextras/inapppurchase/qinappstore.h b/src/mobileextras/inapppurchase/qinappstore.h
new file mode 100644
index 0000000..2c9fb4c
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinappstore.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPSTORE_H
+#define QINAPPSTORE_H
+
+#include "qinappproduct.h"
+
+#include <QtCore/qobject.h>
+#include <QtMobileExtras/qmobileextrasglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppStorePrivate;
+class QInAppProduct;
+class QInAppTransaction;
+class Q_MOBILEEXTRAS_EXPORT QInAppStore: public QObject
+{
+ Q_OBJECT
+public:
+ QInAppStore(QObject *parent = 0);
+ ~QInAppStore();
+
+ Q_INVOKABLE void restorePurchases();
+ Q_INVOKABLE void registerProduct(QInAppProduct::ProductType productType, const QString &identifier);
+ Q_INVOKABLE QInAppProduct *registeredProduct(const QString &identifier) const;
+ Q_INVOKABLE void setPlatformProperty(const QString &propertyName, const QString &value);
+
+Q_SIGNALS:
+ void productRegistered(QInAppProduct *product);
+ void productUnknown(QInAppProduct::ProductType productType, const QString &identifier);
+ void transactionReady(QInAppTransaction *transaction);
+
+private Q_SLOTS:
+ void registerPendingProducts();
+ void registerProduct(QInAppProduct *);
+
+private:
+ void setupBackend();
+
+ Q_DISABLE_COPY(QInAppStore)
+ QSharedPointer<QInAppStorePrivate> d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPSTORE_H
diff --git a/src/mobileextras/inapppurchase/qinappstore_p.h b/src/mobileextras/inapppurchase/qinappstore_p.h
new file mode 100644
index 0000000..5dd8c6e
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinappstore_p.h
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPSTORE_P_H
+#define QINAPPSTORE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qinappproduct.h"
+#include <QtCore/qmutex.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppProduct;
+class QInAppPurchaseBackend;
+
+class QInAppStorePrivate
+{
+public:
+ QInAppStorePrivate()
+ : backend(0)
+ , hasCalledInitialize(false)
+ {
+ }
+
+ QHash<QString, QInAppProduct::ProductType> pendingProducts;
+ QHash<QString, QInAppProduct *> registeredProducts;
+ QInAppPurchaseBackend *backend;
+ bool hasCalledInitialize;
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPSTORE_P_H
diff --git a/src/mobileextras/inapppurchase/qinapptransaction.cpp b/src/mobileextras/inapppurchase/qinapptransaction.cpp
new file mode 100644
index 0000000..ce4d124
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinapptransaction.cpp
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qinapptransaction.h"
+
+QT_BEGIN_NAMESPACE
+
+struct QInAppTransactionPrivate
+{
+ QInAppTransactionPrivate(QInAppTransaction::TransactionStatus s,
+ QInAppProduct *p)
+ : status(s)
+ , product(p)
+ {
+ }
+
+ QInAppTransaction::TransactionStatus status;
+ QInAppProduct *product;
+};
+
+/*!
+ * \class QInAppTransaction
+ * \brief Contains information about a transaction in the external app store
+ *
+ * QInAppTransaction contains information about a transaction in the external app store and is
+ * usually provided as a result of calling QInAppProduct::purchase(). When the purchase flow has
+ * been completed by the user (confirming the purchase, for instance by entering their password),
+ * the QInAppStore instance containing the product will emit a QInAppStore::transactionReady()
+ * signal with data about the transaction.
+ *
+ * The status() provides information on whether the transaction was successful or not. If it was
+ * successful, then the application should take appropriate action. When the necessary action has
+ * been performed, finalize() should be called. The finalize() function should be called regardless
+ * of the status of the transaction.
+ *
+ * It is important that the application stores the purchase information before calling finalize().
+ * If a transaction is not finalized (for example because the application was interrupted before
+ * it had a chance to save the information), then the transaction will be emitted again the next
+ * time the product is registered by QInAppStore::registerProduct().
+ *
+ * Transactions can also be emitted after calling QInAppStore::restorePurchases(), at which point
+ * a new transaction will be emitted for each previously purchased unlockable product.
+ *
+ * \note Since transactions may under certain circumstances be emitted for the same transaction
+ * several times, the application should always check if the transaction has been registered
+ * before. Do not expect each transaction to be unique.
+ */
+
+/*!
+ * \internal
+ */\
+QInAppTransaction::QInAppTransaction(TransactionStatus status, QInAppProduct *product, QObject *parent)
+ : QObject(parent)
+{
+ d = QSharedPointer<QInAppTransactionPrivate>(new QInAppTransactionPrivate(status, product));
+}
+
+/*!
+ * \internal
+ */\
+QInAppTransaction::~QInAppTransaction()
+{
+}
+
+/*!
+ * Returns the product which is the object of this transaction.
+ */
+QInAppProduct *QInAppTransaction::product() const
+{
+ return d->product;
+}
+
+/*!
+ * Returns the status of the transaction. If the purchase was successfully
+ * completed, the status will be PurchaseApproved. Otherwise, the purchase
+ * was unsuccessful.
+ */
+
+QInAppTransaction::TransactionStatus QInAppTransaction::status() const
+{
+ return d->status;
+}
+
+/*!
+ * Returns the platform-specific property given by \a propertyName.
+ *
+ * The following properties are available on Google Play:
+ * \list
+ * \li AndroidSignature: The signature of the transaction, as given by the
+ * private key for the application.
+ * \li AndroidPurchaseData: The purchase data returned by the Google Play store.
+ * \endlist
+ * These properties can be used to verify the purchase using the public key of
+ * your application. It is also possible to have the back-end verify the purchases
+ * by passing in the public key before registering products, using
+ * QInAppStore::setPlatformProperty().
+ */
+QString QInAppTransaction::platformProperty(const QString &propertyName) const
+{
+ Q_UNUSED(propertyName);
+ return QString();
+}
+
+/*!
+ * \fn void QInAppTransaction::finalize()
+ *
+ * Call this when the application has finished performing all necessary reactions
+ * to the purchase. If the status is PurchaseApproved, the application should
+ * store the information about the transaction in a safe way before finalizing it.
+ * All transactions should be finalized.
+ */
+
+QT_END_NAMESPACE
diff --git a/src/mobileextras/inapppurchase/qinapptransaction.h b/src/mobileextras/inapppurchase/qinapptransaction.h
new file mode 100644
index 0000000..cec5121
--- /dev/null
+++ b/src/mobileextras/inapppurchase/qinapptransaction.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QINAPPTRANSACTION_H
+#define QINAPPTRANSACTION_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtMobileExtras/qmobileextrasglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QInAppProduct;
+class QInAppTransactionPrivate;
+class Q_MOBILEEXTRAS_EXPORT QInAppTransaction: public QObject
+{
+ Q_OBJECT
+ Q_ENUMS(TransactionStatus)
+ Q_PROPERTY(TransactionStatus status READ status CONSTANT)
+ Q_PROPERTY(QInAppProduct * product READ product CONSTANT)
+public:
+ enum TransactionStatus {
+ Unknown,
+ PurchaseApproved,
+ PurchaseFailed
+ };
+
+ ~QInAppTransaction();
+
+ QInAppProduct *product() const;
+
+ Q_INVOKABLE virtual void finalize() = 0;
+ Q_INVOKABLE virtual QString platformProperty(const QString &propertyName) const;
+
+ TransactionStatus status() const;
+
+protected:
+ explicit QInAppTransaction(TransactionStatus status, QInAppProduct *product, QObject *parent = 0);
+
+private:
+ Q_DISABLE_COPY(QInAppTransaction)
+ QSharedPointer<QInAppTransactionPrivate> d;
+};
+
+QT_END_NAMESPACE
+
+#endif // QINAPPTRANSACTION_H
diff --git a/src/mobileextras/mobileextras.pro b/src/mobileextras/mobileextras.pro
new file mode 100644
index 0000000..2ee4a9c
--- /dev/null
+++ b/src/mobileextras/mobileextras.pro
@@ -0,0 +1,13 @@
+TARGET = QtMobileExtras
+QT = core
+
+load(qt_module)
+
+ANDROID_BUNDLED_JAR_DEPENDENCIES = \
+ jar/QtMobileExtras-bundled.jar
+ANDROID_JAR_DEPENDENCIES = \
+ jar/QtMobileExtras.jar
+
+HEADERS += qmobileextrasglobal.h
+
+include(inapppurchase/inapppurchase.pri)
diff --git a/src/mobileextras/qmobileextrasglobal.h b/src/mobileextras/qmobileextrasglobal.h
new file mode 100644
index 0000000..5cbfe8e
--- /dev/null
+++ b/src/mobileextras/qmobileextrasglobal.h
@@ -0,0 +1,40 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the Qt Mobile Extras Add-on.
+**
+** $QT_BEGIN_LICENSE$
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QTMOBILEEXTRASGLOBAL_H
+#define QTMOBILEEXTRASGLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef QT_STATIC
+# if defined(QT_BUILD_MOBILEEXTRAS_LIB)
+# define Q_MOBILEEXTRAS_EXPORT Q_DECL_EXPORT
+# else
+# define Q_MOBILEEXTRAS_EXPORT Q_DECL_IMPORT
+# endif
+#else
+# define Q_MOBILEEXTRAS_EXPORT
+#endif
+
+QT_END_NAMESPACE
+
+#endif // QTMOBILEEXTRASGLOBAL_H
diff --git a/src/src.pro b/src/src.pro
new file mode 100644
index 0000000..d14f46d
--- /dev/null
+++ b/src/src.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS = mobileextras android imports
diff --git a/sync.profile b/sync.profile
new file mode 100644
index 0000000..56897f4
--- /dev/null
+++ b/sync.profile
@@ -0,0 +1,17 @@
+%modules = ( # path to module name map
+ "QtMobileExtras" => "$basedir/src/mobileextras",
+);
+%moduleheaders = ( # restrict the module headers to those found in relative path
+);
+# Module dependencies.
+# Every module that is required to build this module should have one entry.
+# Each of the module version specifiers can take one of the following values:
+# - A specific Git revision.
+# - any git symbolic ref resolvable from the module's repository (e.g. "refs/heads/master" to track master branch)
+# - an empty string to use the same branch under test (dependencies will become "refs/heads/master" if we are in the master branch)
+#
+%dependencies = (
+ "qtbase" => "",
+ "qtandroidextras" => "",
+ "qtdeclarative" => "",
+);