Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
abook_check
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abook_android
abook_check
Commits
4cf6d8f7
Commit
4cf6d8f7
authored
Apr 15, 2021
by
kim changgyun
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'features/1.2.363_41804' into 'features/1.2.363'
Task #41804 Androidアプリ課金ライブラリ更新 See merge request
!108
parents
3d1d8b46
fabe1ccc
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
0 additions
and
1617 deletions
+0
-1617
ABVJE_UI_Android/src/com/android/vending/billing/IInAppBillingService.aidl
+0
-144
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/IabException.java
+0
-44
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/IabHelper.java
+0
-1049
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/IabResult.java
+0
-45
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/Inventory.java
+0
-95
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/Purchase.java
+0
-63
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/Security.java
+0
-119
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/SkuDetails.java
+0
-58
No files found.
ABVJE_UI_Android/src/com/android/vending/billing/IInAppBillingService.aidl
deleted
100644 → 0
View file @
3d1d8b46
/*
*
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
);
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/IabException.java
deleted
100644 → 0
View file @
3d1d8b46
/* Copyright (c) 2012 Google Inc.
*
* 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
jp
.
agentec
.
abook
.
abv
.
cl
.
billing
;
/**
* Exception thrown when something went wrong with in-app billing.
* An IabException has an associated IabResult (an error).
* To get the IAB result that caused this exception to be thrown,
* call {@link #getResult()}.
*/
public
class
IabException
extends
Exception
{
IabResult
mResult
;
public
IabException
(
IabResult
r
)
{
this
(
r
,
null
);
}
public
IabException
(
int
response
,
String
message
)
{
this
(
new
IabResult
(
response
,
message
));
}
public
IabException
(
IabResult
r
,
Exception
cause
)
{
super
(
r
.
getMessage
(),
cause
);
mResult
=
r
;
}
public
IabException
(
int
response
,
String
message
,
Exception
cause
)
{
this
(
new
IabResult
(
response
,
message
),
cause
);
}
/** Returns the IAB result (error) that this exception signals. */
public
IabResult
getResult
()
{
return
mResult
;
}
}
\ No newline at end of file
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/IabHelper.java
deleted
100644 → 0
View file @
3d1d8b46
/* Copyright (c) 2012 Google Inc.
*
* 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
jp
.
agentec
.
abook
.
abv
.
cl
.
billing
;
import
java.util.ArrayList
;
import
java.util.List
;
import
jp.agentec.abook.abv.bl.common.log.Logger
;
import
org.json.JSONException
;
import
android.app.Activity
;
import
android.app.PendingIntent
;
import
android.content.ComponentName
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.content.IntentSender.SendIntentException
;
import
android.content.ServiceConnection
;
import
android.os.Bundle
;
import
android.os.Handler
;
import
android.os.IBinder
;
import
android.os.RemoteException
;
import
android.text.TextUtils
;
import
com.android.vending.billing.IInAppBillingService
;
/**
* Provides convenience methods for in-app billing. You can create one instance of this
* class for your application and use it to process in-app billing operations.
* It provides synchronous (blocking) and asynchronous (non-blocking) methods for
* many common in-app billing operations, as well as automatic signature
* verification.
*
* After instantiating, you must perform setup in order to start using the object.
* To perform setup, call the {@link #startSetup} method and provide a listener;
* that listener will be notified when setup is complete, after which (and not before)
* you may call other methods.
*
* After setup is complete, you will typically want to request an inventory of owned
* items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync}
* and related methods.
*
* When you are done with this object, don't forget to call {@link #dispose}
* to ensure proper cleanup. This object holds a binding to the in-app billing
* service, which will leak unless you dispose of it correctly. If you created
* the object on an Activity's onCreate method, then the recommended
* place to dispose of it is the Activity's onDestroy method.
*
* A note about threading: When using this object from a background thread, you may
* call the blocking versions of methods; when using from a UI thread, call
* only the asynchronous versions and handle the results via callbacks.
* Also, notice that you can only call one asynchronous operation at a time;
* attempting to start a second asynchronous operation while the first one
* has not yet completed will result in an exception being thrown.
*
* @author Bruno Oliveira (Google)
*
*/
public
class
IabHelper
{
// Is debug logging enabled?
boolean
mDebugLog
=
false
;
String
mDebugTag
=
"IabHelper"
;
// Is setup done?
boolean
mSetupDone
=
false
;
// Has this object been disposed of? (If so, we should ignore callbacks, etc)
boolean
mDisposed
=
false
;
// Are subscriptions supported?
boolean
mSubscriptionsSupported
=
false
;
// Is an asynchronous operation in progress?
// (only one at a time can be in progress)
boolean
mAsyncInProgress
=
false
;
// (for logging/debugging)
// if mAsyncInProgress == true, what asynchronous operation is in progress?
String
mAsyncOperation
=
""
;
// Context we were passed during initialization
Context
mContext
;
// Connection to the service
IInAppBillingService
mService
;
ServiceConnection
mServiceConn
;
// The request code used to launch purchase flow
int
mRequestCode
;
// The item type of the current purchase flow
String
mPurchasingItemType
;
// Public key for verifying signature, in base64 encoding
String
mSignatureBase64
=
null
;
// Billing response codes
public
static
final
int
BILLING_RESPONSE_RESULT_OK
=
0
;
public
static
final
int
BILLING_RESPONSE_RESULT_USER_CANCELED
=
1
;
public
static
final
int
BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE
=
3
;
public
static
final
int
BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE
=
4
;
public
static
final
int
BILLING_RESPONSE_RESULT_DEVELOPER_ERROR
=
5
;
public
static
final
int
BILLING_RESPONSE_RESULT_ERROR
=
6
;
public
static
final
int
BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED
=
7
;
public
static
final
int
BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED
=
8
;
// IAB Helper error codes
public
static
final
int
IABHELPER_ERROR_BASE
=
-
1000
;
public
static
final
int
IABHELPER_REMOTE_EXCEPTION
=
-
1001
;
public
static
final
int
IABHELPER_BAD_RESPONSE
=
-
1002
;
public
static
final
int
IABHELPER_VERIFICATION_FAILED
=
-
1003
;
public
static
final
int
IABHELPER_SEND_INTENT_FAILED
=
-
1004
;
public
static
final
int
IABHELPER_USER_CANCELLED
=
-
1005
;
public
static
final
int
IABHELPER_UNKNOWN_PURCHASE_RESPONSE
=
-
1006
;
public
static
final
int
IABHELPER_MISSING_TOKEN
=
-
1007
;
public
static
final
int
IABHELPER_UNKNOWN_ERROR
=
-
1008
;
public
static
final
int
IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE
=
-
1009
;
public
static
final
int
IABHELPER_INVALID_CONSUMPTION
=
-
1010
;
// Keys for the responses from InAppBillingService
public
static
final
String
RESPONSE_CODE
=
"RESPONSE_CODE"
;
public
static
final
String
RESPONSE_GET_SKU_DETAILS_LIST
=
"DETAILS_LIST"
;
public
static
final
String
RESPONSE_BUY_INTENT
=
"BUY_INTENT"
;
public
static
final
String
RESPONSE_INAPP_PURCHASE_DATA
=
"INAPP_PURCHASE_DATA"
;
public
static
final
String
RESPONSE_INAPP_SIGNATURE
=
"INAPP_DATA_SIGNATURE"
;
public
static
final
String
RESPONSE_INAPP_ITEM_LIST
=
"INAPP_PURCHASE_ITEM_LIST"
;
public
static
final
String
RESPONSE_INAPP_PURCHASE_DATA_LIST
=
"INAPP_PURCHASE_DATA_LIST"
;
public
static
final
String
RESPONSE_INAPP_SIGNATURE_LIST
=
"INAPP_DATA_SIGNATURE_LIST"
;
public
static
final
String
INAPP_CONTINUATION_TOKEN
=
"INAPP_CONTINUATION_TOKEN"
;
// Item types
public
static
final
String
ITEM_TYPE_INAPP
=
"inapp"
;
public
static
final
String
ITEM_TYPE_SUBS
=
"subs"
;
// some fields on the getSkuDetails response bundle
public
static
final
String
GET_SKU_DETAILS_ITEM_LIST
=
"ITEM_ID_LIST"
;
public
static
final
String
GET_SKU_DETAILS_ITEM_TYPE_LIST
=
"ITEM_TYPE_LIST"
;
/**
* Creates an instance. After creation, it will not yet be ready to use. You must perform
* setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
* block and is safe to call from a UI thread.
*
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
* @param base64PublicKey Your application's public key, encoded in base64.
* This is used for verification of purchase signatures. You can find your app's base64-encoded
* public key in your application's page on Google Play Developer Console. Note that this
* is NOT your "developer public key".
*/
public
IabHelper
(
Context
ctx
,
String
base64PublicKey
)
{
mContext
=
ctx
.
getApplicationContext
();
mSignatureBase64
=
base64PublicKey
;
logDebug
(
"IAB helper created."
);
}
/**
* Enables or disable debug logging through LogCat.
*/
public
void
enableDebugLogging
(
boolean
enable
,
String
tag
)
{
checkNotDisposed
();
mDebugLog
=
enable
;
mDebugTag
=
tag
;
}
public
void
enableDebugLogging
(
boolean
enable
)
{
checkNotDisposed
();
mDebugLog
=
enable
;
}
/**
* Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
* when the setup process is complete.
*/
public
interface
OnIabSetupFinishedListener
{
/**
* Called to notify that setup is complete.
*
* @param result The result of the setup process.
*/
public
void
onIabSetupFinished
(
IabResult
result
);
}
/**
* Starts the setup process. This will start up the setup process asynchronously.
* You will be notified through the listener when the setup process is complete.
* This method is safe to call from a UI thread.
*
* @param listener The listener to notify when the setup process is complete.
*/
public
void
startSetup
(
final
OnIabSetupFinishedListener
listener
)
{
// If already set up, can't do it again.
checkNotDisposed
();
if
(
mSetupDone
)
{
throw
new
IllegalStateException
(
"IAB helper is already set up."
);
}
// Connection to IAB service
logDebug
(
"Starting in-app billing setup."
);
mServiceConn
=
new
ServiceConnection
()
{
@Override
public
void
onServiceDisconnected
(
ComponentName
name
)
{
logDebug
(
"Billing service disconnected."
);
mService
=
null
;
}
@Override
public
void
onServiceConnected
(
ComponentName
name
,
IBinder
service
)
{
if
(
mDisposed
)
{
return
;
}
logDebug
(
"Billing service connected."
);
mService
=
IInAppBillingService
.
Stub
.
asInterface
(
service
);
String
packageName
=
mContext
.
getPackageName
();
try
{
logDebug
(
"Checking for in-app billing 3 support."
);
// check for in-app billing v3 support
int
response
=
mService
.
isBillingSupported
(
3
,
packageName
,
ITEM_TYPE_INAPP
);
if
(
response
!=
BILLING_RESPONSE_RESULT_OK
)
{
if
(
listener
!=
null
)
{
listener
.
onIabSetupFinished
(
new
IabResult
(
response
,
"Error checking for billing v3 support."
));
}
// if in-app purchases aren't supported, neither are subscriptions.
mSubscriptionsSupported
=
false
;
return
;
}
logDebug
(
"In-app billing version 3 supported for "
+
packageName
);
// check for v3 subscriptions support
response
=
mService
.
isBillingSupported
(
3
,
packageName
,
ITEM_TYPE_SUBS
);
if
(
response
==
BILLING_RESPONSE_RESULT_OK
)
{
logDebug
(
"Subscriptions AVAILABLE."
);
mSubscriptionsSupported
=
true
;
}
else
{
logDebug
(
"Subscriptions NOT AVAILABLE. Response: "
+
response
);
}
mSetupDone
=
true
;
}
catch
(
RemoteException
e
)
{
if
(
listener
!=
null
)
{
listener
.
onIabSetupFinished
(
new
IabResult
(
IABHELPER_REMOTE_EXCEPTION
,
"RemoteException while setting up in-app billing."
));
}
e
.
printStackTrace
();
return
;
}
if
(
listener
!=
null
)
{
listener
.
onIabSetupFinished
(
new
IabResult
(
BILLING_RESPONSE_RESULT_OK
,
"Setup successful."
));
}
}
};
Intent
serviceIntent
=
new
Intent
(
"com.android.vending.billing.InAppBillingService.BIND"
);
serviceIntent
.
setPackage
(
"com.android.vending"
);
if
(
mContext
.
getPackageManager
().
queryIntentServices
(
serviceIntent
,
0
).
isEmpty
())
{
// no service available to handle that Intent
if
(
listener
!=
null
)
{
listener
.
onIabSetupFinished
(
new
IabResult
(
BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE
,
"Billing service unavailable on device."
));
}
}
else
{
// service available to handle that Intent
mContext
.
bindService
(
serviceIntent
,
mServiceConn
,
Context
.
BIND_AUTO_CREATE
);
}
}
/**
* Dispose of object, releasing resources. It's very important to call this
* method when you are done with this object. It will release any resources
* used by it such as service connections. Naturally, once the object is
* disposed of, it can't be used again.
*/
public
void
dispose
()
{
logDebug
(
"Disposing."
);
mSetupDone
=
false
;
if
(
mServiceConn
!=
null
)
{
logDebug
(
"Unbinding from service."
);
if
(
mContext
!=
null
)
{
mContext
.
unbindService
(
mServiceConn
);
}
}
mDisposed
=
true
;
mContext
=
null
;
mServiceConn
=
null
;
mService
=
null
;
mPurchaseListener
=
null
;
}
private
void
checkNotDisposed
()
{
if
(
mDisposed
)
{
throw
new
IllegalStateException
(
"IabHelper was disposed of, so it cannot be used."
);
}
}
/** Returns whether subscriptions are supported. */
public
boolean
subscriptionsSupported
()
{
checkNotDisposed
();
return
mSubscriptionsSupported
;
}
/**
* Callback that notifies when a purchase is finished.
*/
public
interface
OnIabPurchaseFinishedListener
{
/**
* Called to notify that an in-app purchase finished. If the purchase was successful,
* then the sku parameter specifies which item was purchased. If the purchase failed,
* the sku and extraData parameters may or may not be null, depending on how far the purchase
* process went.
*
* @param result The result of the purchase.
* @param info The purchase information (null if purchase failed)
*/
public
void
onIabPurchaseFinished
(
IabResult
result
,
Purchase
info
);
}
// The listener registered on launchPurchaseFlow, which we have to call back when
// the purchase finishes
OnIabPurchaseFinishedListener
mPurchaseListener
;
public
void
launchPurchaseFlow
(
Activity
act
,
String
sku
,
int
requestCode
,
OnIabPurchaseFinishedListener
listener
)
{
launchPurchaseFlow
(
act
,
sku
,
requestCode
,
listener
,
""
);
}
public
void
launchPurchaseFlow
(
Activity
act
,
String
sku
,
int
requestCode
,
OnIabPurchaseFinishedListener
listener
,
String
extraData
)
{
launchPurchaseFlow
(
act
,
sku
,
ITEM_TYPE_INAPP
,
requestCode
,
listener
,
extraData
);
}
public
void
launchSubscriptionPurchaseFlow
(
Activity
act
,
String
sku
,
int
requestCode
,
OnIabPurchaseFinishedListener
listener
)
{
launchSubscriptionPurchaseFlow
(
act
,
sku
,
requestCode
,
listener
,
""
);
}
public
void
launchSubscriptionPurchaseFlow
(
Activity
act
,
String
sku
,
int
requestCode
,
OnIabPurchaseFinishedListener
listener
,
String
extraData
)
{
launchPurchaseFlow
(
act
,
sku
,
ITEM_TYPE_SUBS
,
requestCode
,
listener
,
extraData
);
}
/**
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
* which will involve bringing up the Google Play screen. The calling activity will be paused while
* the user interacts with Google Play, and the result will be delivered via the activity's
* {@link android.app.Activity#onActivityResult} method, at which point you must call
* this object's {@link #handleActivityResult} method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
*
* @param act The calling activity.
* @param sku The sku of the item to purchase.
* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
* @param requestCode A request code (to differentiate from other responses --
* as in {@link android.app.Activity#startActivityForResult}).
* @param listener The listener to notify when the purchase process finishes
* @param extraData Extra data (developer payload), which will be returned with the purchase data
* when the purchase completes. This extra data will be permanently bound to that purchase
* and will always be returned when the purchase is queried.
*/
public
void
launchPurchaseFlow
(
Activity
act
,
String
sku
,
String
itemType
,
int
requestCode
,
OnIabPurchaseFinishedListener
listener
,
String
extraData
)
{
checkNotDisposed
();
checkSetupDone
(
"launchPurchaseFlow"
);
flagStartAsync
(
"launchPurchaseFlow"
);
IabResult
result
;
if
(
itemType
.
equals
(
ITEM_TYPE_SUBS
)
&&
!
mSubscriptionsSupported
)
{
IabResult
r
=
new
IabResult
(
IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE
,
"Subscriptions are not available."
);
flagEndAsync
();
if
(
listener
!=
null
)
{
listener
.
onIabPurchaseFinished
(
r
,
null
);
}
return
;
}
try
{
logDebug
(
"Constructing buy intent for "
+
sku
+
", item type: "
+
itemType
);
Bundle
buyIntentBundle
=
mService
.
getBuyIntent
(
3
,
mContext
.
getPackageName
(),
sku
,
itemType
,
extraData
);
int
response
=
getResponseCodeFromBundle
(
buyIntentBundle
);
if
(
response
!=
BILLING_RESPONSE_RESULT_OK
)
{
logError
(
"Unable to buy item, Error response: "
+
getResponseDesc
(
response
));
flagEndAsync
();
result
=
new
IabResult
(
response
,
"Unable to buy item"
);
if
(
listener
!=
null
)
{
listener
.
onIabPurchaseFinished
(
result
,
null
);
}
return
;
}
PendingIntent
pendingIntent
=
buyIntentBundle
.
getParcelable
(
RESPONSE_BUY_INTENT
);
logDebug
(
"Launching buy intent for "
+
sku
+
". Request code: "
+
requestCode
);
mRequestCode
=
requestCode
;
mPurchaseListener
=
listener
;
mPurchasingItemType
=
itemType
;
if
(
pendingIntent
!=
null
)
{
act
.
startIntentSenderForResult
(
pendingIntent
.
getIntentSender
(),
requestCode
,
new
Intent
(),
Integer
.
valueOf
(
0
),
Integer
.
valueOf
(
0
),
Integer
.
valueOf
(
0
));
}
}
catch
(
SendIntentException
e
)
{
logError
(
"SendIntentException while launching purchase flow for sku "
+
sku
);
e
.
printStackTrace
();
flagEndAsync
();
result
=
new
IabResult
(
IABHELPER_SEND_INTENT_FAILED
,
"Failed to send intent."
);
if
(
listener
!=
null
)
{
listener
.
onIabPurchaseFinished
(
result
,
null
);
}
}
catch
(
RemoteException
e
)
{
logError
(
"RemoteException while launching purchase flow for sku "
+
sku
);
e
.
printStackTrace
();
flagEndAsync
();
result
=
new
IabResult
(
IABHELPER_REMOTE_EXCEPTION
,
"Remote exception while starting purchase flow"
);
if
(
listener
!=
null
)
{
listener
.
onIabPurchaseFinished
(
result
,
null
);
}
}
}
/**
* Handles an activity result that's part of the purchase flow in in-app billing. If you
* are calling {@link #launchPurchaseFlow}, then you must call this method from your
* Activity's {@link android.app.Activity@onActivityResult} method. This method
* MUST be called from the UI thread of the Activity.
*
* @param requestCode The requestCode as you received it.
* @param resultCode The resultCode as you received it.
* @param data The data (Intent) as you received it.
* @return Returns true if the result was related to a purchase flow and was handled;
* false if the result was not related to a purchase, in which case you should
* handle it normally.
*/
public
boolean
handleActivityResult
(
int
requestCode
,
int
resultCode
,
Intent
data
)
{
IabResult
result
;
if
(
requestCode
!=
mRequestCode
)
{
return
false
;
}
checkNotDisposed
();
checkSetupDone
(
"handleActivityResult"
);
// end of async purchase operation that started on launchPurchaseFlow
flagEndAsync
();
if
(
data
==
null
)
{
logError
(
"Null data in IAB activity result."
);
result
=
new
IabResult
(
IABHELPER_BAD_RESPONSE
,
"Null data in IAB result"
);
if
(
mPurchaseListener
!=
null
)
{
mPurchaseListener
.
onIabPurchaseFinished
(
result
,
null
);
}
return
true
;
}
int
responseCode
=
getResponseCodeFromIntent
(
data
);
String
purchaseData
=
data
.
getStringExtra
(
RESPONSE_INAPP_PURCHASE_DATA
);
String
dataSignature
=
data
.
getStringExtra
(
RESPONSE_INAPP_SIGNATURE
);
if
(
resultCode
==
Activity
.
RESULT_OK
&&
responseCode
==
BILLING_RESPONSE_RESULT_OK
)
{
logDebug
(
"Successful resultcode from purchase activity."
);
logDebug
(
"Purchase data: "
+
purchaseData
);
logDebug
(
"Data signature: "
+
dataSignature
);
logDebug
(
"Extras: "
+
data
.
getExtras
());
logDebug
(
"Expected item type: "
+
mPurchasingItemType
);
if
(
purchaseData
==
null
||
dataSignature
==
null
)
{
logError
(
"BUG: either purchaseData or dataSignature is null."
);
logDebug
(
"Extras: "
+
data
.
getExtras
().
toString
());
result
=
new
IabResult
(
IABHELPER_UNKNOWN_ERROR
,
"IAB returned null purchaseData or dataSignature"
);
if
(
mPurchaseListener
!=
null
)
{
mPurchaseListener
.
onIabPurchaseFinished
(
result
,
null
);
}
return
true
;
}
Purchase
purchase
;
try
{
purchase
=
new
Purchase
(
mPurchasingItemType
,
purchaseData
,
dataSignature
);
String
sku
=
purchase
.
getSku
();
// Verify signature
if
(!
Security
.
verifyPurchase
(
mSignatureBase64
,
purchaseData
,
dataSignature
))
{
logError
(
"Purchase signature verification FAILED for sku "
+
sku
);
result
=
new
IabResult
(
IABHELPER_VERIFICATION_FAILED
,
"Signature verification failed for sku "
+
sku
);
if
(
mPurchaseListener
!=
null
)
{
mPurchaseListener
.
onIabPurchaseFinished
(
result
,
purchase
);
}
return
true
;
}
logDebug
(
"Purchase signature successfully verified."
);
}
catch
(
JSONException
e
)
{
logError
(
"Failed to parse purchase data."
);
e
.
printStackTrace
();
result
=
new
IabResult
(
IABHELPER_BAD_RESPONSE
,
"Failed to parse purchase data."
);
if
(
mPurchaseListener
!=
null
)
{
mPurchaseListener
.
onIabPurchaseFinished
(
result
,
null
);
}
return
true
;
}
if
(
mPurchaseListener
!=
null
)
{
mPurchaseListener
.
onIabPurchaseFinished
(
new
IabResult
(
BILLING_RESPONSE_RESULT_OK
,
"Success"
),
purchase
);
}
}
else
if
(
resultCode
==
Activity
.
RESULT_OK
)
{
// result code was OK, but in-app billing response was not OK.
logDebug
(
"Result code was OK but in-app billing response was not OK: "
+
getResponseDesc
(
responseCode
));
if
(
mPurchaseListener
!=
null
)
{
result
=
new
IabResult
(
responseCode
,
"Problem purchashing item."
);
mPurchaseListener
.
onIabPurchaseFinished
(
result
,
null
);
}
}
else
if
(
resultCode
==
Activity
.
RESULT_CANCELED
)
{
logDebug
(
"Purchase canceled - Response: "
+
getResponseDesc
(
responseCode
));
result
=
new
IabResult
(
IABHELPER_USER_CANCELLED
,
"User canceled."
);
if
(
mPurchaseListener
!=
null
)
{
mPurchaseListener
.
onIabPurchaseFinished
(
result
,
null
);
}
}
else
{
logError
(
"Purchase failed. Result code: "
+
Integer
.
toString
(
resultCode
)
+
". Response: "
+
getResponseDesc
(
responseCode
));
result
=
new
IabResult
(
IABHELPER_UNKNOWN_PURCHASE_RESPONSE
,
"Unknown purchase response."
);
if
(
mPurchaseListener
!=
null
)
{
mPurchaseListener
.
onIabPurchaseFinished
(
result
,
null
);
}
}
return
true
;
}
public
Inventory
queryInventory
(
boolean
querySkuDetails
,
List
<
String
>
moreSkus
)
throws
IabException
{
return
queryInventory
(
querySkuDetails
,
moreSkus
,
null
);
}
/**
* Queries the inventory. This will query all owned items from the server, as well as
* information on additional skus, if specified. This method may block or take long to execute.
* Do not call from a UI thread. For that, use the non-blocking version.
*
* @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
* as purchase information.
* @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @throws IabException if a problem occurs while refreshing the inventory.
*/
public
Inventory
queryInventory
(
boolean
querySkuDetails
,
List
<
String
>
moreItemSkus
,
List
<
String
>
moreSubsSkus
)
throws
IabException
{
checkNotDisposed
();
checkSetupDone
(
"queryInventory"
);
try
{
Inventory
inv
=
new
Inventory
();
int
r
=
queryPurchases
(
inv
,
ITEM_TYPE_INAPP
);
if
(
r
!=
BILLING_RESPONSE_RESULT_OK
)
{
throw
new
IabException
(
r
,
"Error refreshing inventory (querying owned items)."
);
}
if
(
querySkuDetails
)
{
r
=
querySkuDetails
(
ITEM_TYPE_INAPP
,
inv
,
moreItemSkus
);
if
(
r
!=
BILLING_RESPONSE_RESULT_OK
)
{
throw
new
IabException
(
r
,
"Error refreshing inventory (querying prices of items)."
);
}
}
// if subscriptions are supported, then also query for subscriptions
if
(
mSubscriptionsSupported
)
{
r
=
queryPurchases
(
inv
,
ITEM_TYPE_SUBS
);
if
(
r
!=
BILLING_RESPONSE_RESULT_OK
)
{
throw
new
IabException
(
r
,
"Error refreshing inventory (querying owned subscriptions)."
);
}
if
(
querySkuDetails
)
{
r
=
querySkuDetails
(
ITEM_TYPE_SUBS
,
inv
,
moreItemSkus
);
if
(
r
!=
BILLING_RESPONSE_RESULT_OK
)
{
throw
new
IabException
(
r
,
"Error refreshing inventory (querying prices of subscriptions)."
);
}
}
}
return
inv
;
}
catch
(
RemoteException
e
)
{
throw
new
IabException
(
IABHELPER_REMOTE_EXCEPTION
,
"Remote exception while refreshing inventory."
,
e
);
}
catch
(
JSONException
e
)
{
throw
new
IabException
(
IABHELPER_BAD_RESPONSE
,
"Error parsing JSON response while refreshing inventory."
,
e
);
}
}
/**
* Listener that notifies when an inventory query operation completes.
*/
public
interface
QueryInventoryFinishedListener
{
/**
* Called to notify that an inventory query operation completed.
*
* @param result The result of the operation.
* @param inv The inventory.
*/
public
void
onQueryInventoryFinished
(
IabResult
result
,
Inventory
inv
);
}
/**
* Asynchronous wrapper for inventory query. This will perform an inventory
* query as described in {@link #queryInventory}, but will do so asynchronously
* and call back the specified listener upon completion. This method is safe to
* call from a UI thread.
*
* @param querySkuDetails as in {@link #queryInventory}
* @param moreSkus as in {@link #queryInventory}
* @param listener The listener to notify when the refresh operation completes.
*/
public
void
queryInventoryAsync
(
final
boolean
querySkuDetails
,
final
List
<
String
>
moreSkus
,
final
QueryInventoryFinishedListener
listener
)
{
final
Handler
handler
=
new
Handler
();
checkNotDisposed
();
checkSetupDone
(
"queryInventory"
);
flagStartAsync
(
"refresh inventory"
);
(
new
Thread
(
new
Runnable
()
{
@Override
public
void
run
()
{
IabResult
result
=
new
IabResult
(
BILLING_RESPONSE_RESULT_OK
,
"Inventory refresh successful."
);
Inventory
inv
=
null
;
try
{
inv
=
queryInventory
(
querySkuDetails
,
moreSkus
);
}
catch
(
IabException
ex
)
{
result
=
ex
.
getResult
();
}
finally
{
flagEndAsync
();
}
final
IabResult
result_f
=
result
;
final
Inventory
inv_f
=
inv
;
if
(!
mDisposed
&&
listener
!=
null
)
{
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
listener
.
onQueryInventoryFinished
(
result_f
,
inv_f
);
}
});
}
}
})).
start
();
}
// public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
// queryInventoryAsync(true, null, listener);
// }
//
// public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
// queryInventoryAsync(querySkuDetails, null, listener);
// }
/**
* Consumes a given in-app product. Consuming can only be done on an item
* that's owned, and as a result of consumption, the user will no longer own it.
* This method may block or take long to return. Do not call from the UI thread.
* For that, see {@link #consumeAsync}.
*
* @param itemInfo The PurchaseInfo that represents the item to consume.
* @throws IabException if there is a problem during consumption.
*/
void
consume
(
Purchase
itemInfo
)
throws
IabException
{
checkNotDisposed
();
checkSetupDone
(
"consume"
);
if
(!
itemInfo
.
mItemType
.
equals
(
ITEM_TYPE_INAPP
))
{
throw
new
IabException
(
IABHELPER_INVALID_CONSUMPTION
,
"Items of type '"
+
itemInfo
.
mItemType
+
"' can't be consumed."
);
}
try
{
String
token
=
itemInfo
.
getToken
();
String
sku
=
itemInfo
.
getSku
();
if
(
token
==
null
||
token
.
equals
(
""
))
{
logError
(
"Can't consume "
+
sku
+
". No token."
);
throw
new
IabException
(
IABHELPER_MISSING_TOKEN
,
"PurchaseInfo is missing token for sku: "
+
sku
+
" "
+
itemInfo
);
}
logDebug
(
"Consuming sku: "
+
sku
+
", token: "
+
token
);
int
response
=
mService
.
consumePurchase
(
3
,
mContext
.
getPackageName
(),
token
);
if
(
response
==
BILLING_RESPONSE_RESULT_OK
)
{
logDebug
(
"Successfully consumed sku: "
+
sku
);
}
else
{
logDebug
(
"Error consuming consuming sku "
+
sku
+
". "
+
getResponseDesc
(
response
));
throw
new
IabException
(
response
,
"Error consuming sku "
+
sku
);
}
}
catch
(
RemoteException
e
)
{
throw
new
IabException
(
IABHELPER_REMOTE_EXCEPTION
,
"Remote exception while consuming. PurchaseInfo: "
+
itemInfo
,
e
);
}
}
/**
* Callback that notifies when a consumption operation finishes.
*/
public
interface
OnConsumeFinishedListener
{
/**
* Called to notify that a consumption has finished.
*
* @param purchase The purchase that was (or was to be) consumed.
* @param result The result of the consumption operation.
*/
public
void
onConsumeFinished
(
Purchase
purchase
,
IabResult
result
);
}
/**
* Callback that notifies when a multi-item consumption operation finishes.
*/
public
interface
OnConsumeMultiFinishedListener
{
/**
* Called to notify that a consumption of multiple items has finished.
*
* @param purchases The purchases that were (or were to be) consumed.
* @param results The results of each consumption operation, corresponding to each
* sku.
*/
public
void
onConsumeMultiFinished
(
List
<
Purchase
>
purchases
,
List
<
IabResult
>
results
);
}
/**
* Asynchronous wrapper to item consumption. Works like {@link #consume}, but
* performs the consumption in the background and notifies completion through
* the provided listener. This method is safe to call from a UI thread.
*
* @param purchase The purchase to be consumed.
* @param listener The listener to notify when the consumption operation finishes.
*/
public
void
consumeAsync
(
Purchase
purchase
,
OnConsumeFinishedListener
listener
)
{
checkNotDisposed
();
checkSetupDone
(
"consume"
);
List
<
Purchase
>
purchases
=
new
ArrayList
<>();
purchases
.
add
(
purchase
);
consumeAsyncInternal
(
purchases
,
listener
,
null
);
}
/**
* Same as {@link #consumeAsync}, but for multiple items at once.
* @param purchases The list of PurchaseInfo objects representing the purchases to consume.
* @param listener The listener to notify when the consumption operation finishes.
*/
public
void
consumeAsync
(
List
<
Purchase
>
purchases
,
OnConsumeMultiFinishedListener
listener
)
{
checkNotDisposed
();
checkSetupDone
(
"consume"
);
consumeAsyncInternal
(
purchases
,
null
,
listener
);
}
/**
* Returns a human-readable description for the given response code.
*
* @param code The response code
* @return A human-readable string explaining the result code.
* It also includes the result code numerically.
*/
public
static
String
getResponseDesc
(
int
code
)
{
String
[]
iab_msgs
=
(
"0:OK/1:User Canceled/2:Unknown/"
+
"3:Billing Unavailable/4:Item unavailable/"
+
"5:Developer Error/6:Error/7:Item Already Owned/"
+
"8:Item not owned"
).
split
(
"/"
);
String
[]
iabhelper_msgs
=
(
"0:OK/-1001:Remote exception during initialization/"
+
"-1002:Bad response received/"
+
"-1003:Purchase signature verification failed/"
+
"-1004:Send intent failed/"
+
"-1005:User cancelled/"
+
"-1006:Unknown purchase response/"
+
"-1007:Missing token/"
+
"-1008:Unknown error/"
+
"-1009:Subscriptions not available/"
+
"-1010:Invalid consumption attempt"
).
split
(
"/"
);
if
(
code
<=
IABHELPER_ERROR_BASE
)
{
int
index
=
IABHELPER_ERROR_BASE
-
code
;
if
(
index
>=
0
&&
index
<
iabhelper_msgs
.
length
)
{
return
iabhelper_msgs
[
index
];
}
else
{
return
String
.
valueOf
(
code
)
+
":Unknown IAB Helper Error"
;
}
}
else
if
(
code
<
0
||
code
>=
iab_msgs
.
length
)
{
return
String
.
valueOf
(
code
)
+
":Unknown"
;
}
else
{
return
iab_msgs
[
code
];
}
}
// Checks that setup was done; if not, throws an exception.
void
checkSetupDone
(
String
operation
)
{
if
(!
mSetupDone
)
{
logError
(
"Illegal state for operation ("
+
operation
+
"): IAB helper is not set up."
);
throw
new
IllegalStateException
(
"IAB helper is not set up. Can't perform operation: "
+
operation
);
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int
getResponseCodeFromBundle
(
Bundle
b
)
{
Object
o
=
b
.
get
(
RESPONSE_CODE
);
if
(
o
==
null
)
{
logDebug
(
"Bundle with null response code, assuming OK (known issue)"
);
return
BILLING_RESPONSE_RESULT_OK
;
}
else
if
(
o
instanceof
Integer
)
{
return
((
Integer
)
o
).
intValue
();
}
else
if
(
o
instanceof
Long
)
{
return
(
int
)
((
Long
)
o
).
longValue
();
}
else
{
logError
(
"Unexpected type for bundle response code."
);
logError
(
o
.
getClass
().
getName
());
throw
new
RuntimeException
(
"Unexpected type for bundle response code: "
+
o
.
getClass
().
getName
());
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int
getResponseCodeFromIntent
(
Intent
i
)
{
Object
o
=
i
.
getExtras
().
get
(
RESPONSE_CODE
);
if
(
o
==
null
)
{
logError
(
"Intent with no response code, assuming OK (known issue)"
);
return
BILLING_RESPONSE_RESULT_OK
;
}
else
if
(
o
instanceof
Integer
)
{
return
((
Integer
)
o
).
intValue
();
}
else
if
(
o
instanceof
Long
)
{
return
(
int
)
((
Long
)
o
).
longValue
();
}
else
{
logError
(
"Unexpected type for intent response code."
);
logError
(
o
.
getClass
().
getName
());
throw
new
RuntimeException
(
"Unexpected type for intent response code: "
+
o
.
getClass
().
getName
());
}
}
void
flagStartAsync
(
String
operation
)
{
// FIXME: mAsyncInProgressが正しくリセットされていない
// if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
// operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
mAsyncOperation
=
operation
;
mAsyncInProgress
=
true
;
logDebug
(
"Starting async operation: "
+
operation
);
}
void
flagEndAsync
()
{
logDebug
(
"Ending async operation: "
+
mAsyncOperation
);
mAsyncOperation
=
""
;
mAsyncInProgress
=
false
;
}
int
queryPurchases
(
Inventory
inv
,
String
itemType
)
throws
JSONException
,
RemoteException
{
// Query purchases
logDebug
(
"Querying owned items, item type: "
+
itemType
);
logDebug
(
"Package name: "
+
mContext
.
getPackageName
());
boolean
verificationFailed
=
false
;
String
continueToken
=
null
;
do
{
logDebug
(
"Calling getPurchases with continuation token: "
+
continueToken
);
Bundle
ownedItems
=
mService
.
getPurchases
(
3
,
mContext
.
getPackageName
(),
itemType
,
continueToken
);
int
response
=
getResponseCodeFromBundle
(
ownedItems
);
logDebug
(
"Owned items response: "
+
String
.
valueOf
(
response
));
if
(
response
!=
BILLING_RESPONSE_RESULT_OK
)
{
logDebug
(
"getPurchases() failed: "
+
getResponseDesc
(
response
));
return
response
;
}
if
(!
ownedItems
.
containsKey
(
RESPONSE_INAPP_ITEM_LIST
)
||
!
ownedItems
.
containsKey
(
RESPONSE_INAPP_PURCHASE_DATA_LIST
)
||
!
ownedItems
.
containsKey
(
RESPONSE_INAPP_SIGNATURE_LIST
))
{
logError
(
"Bundle returned from getPurchases() doesn't contain required fields."
);
return
IABHELPER_BAD_RESPONSE
;
}
ArrayList
<
String
>
ownedSkus
=
ownedItems
.
getStringArrayList
(
RESPONSE_INAPP_ITEM_LIST
);
ArrayList
<
String
>
purchaseDataList
=
ownedItems
.
getStringArrayList
(
RESPONSE_INAPP_PURCHASE_DATA_LIST
);
ArrayList
<
String
>
signatureList
=
ownedItems
.
getStringArrayList
(
RESPONSE_INAPP_SIGNATURE_LIST
);
if
(
purchaseDataList
!=
null
&&
signatureList
!=
null
)
{
for
(
int
i
=
0
;
i
<
purchaseDataList
.
size
();
++
i
)
{
String
purchaseData
=
purchaseDataList
.
get
(
i
);
String
signature
=
signatureList
.
get
(
i
);
String
sku
=
null
;
if
(
ownedSkus
!=
null
)
{
sku
=
ownedSkus
.
get
(
i
);
}
if
(
sku
!=
null
&&
sku
.
startsWith
(
"android.test"
))
{
// なぜかsignatureがemptyになっている
continue
;
}
Logger
.
d
(
getClass
().
getName
(),
"sku=%s, mSignatureBase64=%s, purchaseData=%s, signature=%s"
,
sku
,
mSignatureBase64
,
purchaseData
,
signature
);
if
(
Security
.
verifyPurchase
(
mSignatureBase64
,
purchaseData
,
signature
))
{
logDebug
(
"Sku is owned: "
+
sku
);
Purchase
purchase
=
new
Purchase
(
itemType
,
purchaseData
,
signature
);
logDebug
(
"Purchase data: "
+
purchaseData
);
if
(
TextUtils
.
isEmpty
(
purchase
.
getToken
()))
{
logWarn
(
"BUG: empty/null token!"
);
}
// Record ownership and token
inv
.
addPurchase
(
purchase
);
}
else
{
logWarn
(
"Purchase signature verification **FAILED**. Not adding item."
);
logDebug
(
" Purchase data: "
+
purchaseData
);
logDebug
(
" Signature: "
+
signature
);
verificationFailed
=
true
;
}
}
}
continueToken
=
ownedItems
.
getString
(
INAPP_CONTINUATION_TOKEN
);
logDebug
(
"Continuation token: "
+
continueToken
);
}
while
(!
TextUtils
.
isEmpty
(
continueToken
));
return
verificationFailed
?
IABHELPER_VERIFICATION_FAILED
:
BILLING_RESPONSE_RESULT_OK
;
}
int
querySkuDetails
(
String
itemType
,
Inventory
inv
,
List
<
String
>
moreSkus
)
throws
RemoteException
,
JSONException
{
logDebug
(
"Querying SKU details."
);
ArrayList
<
String
>
skuList
=
new
ArrayList
<>();
// skuList.addAll(inv.getAllOwnedSkus(itemType)); // これをすると購入済みを検索対象に加えてしまって20件制限を超えてしまう
if
(
moreSkus
!=
null
)
{
for
(
String
sku
:
moreSkus
)
{
if
(!
skuList
.
contains
(
sku
))
{
skuList
.
add
(
sku
);
}
}
}
if
(
skuList
.
size
()
==
0
)
{
logDebug
(
"queryPrices: nothing to do because there are no SKUs."
);
return
BILLING_RESPONSE_RESULT_OK
;
}
Bundle
querySkus
=
new
Bundle
();
querySkus
.
putStringArrayList
(
GET_SKU_DETAILS_ITEM_LIST
,
skuList
);
Bundle
skuDetails
=
mService
.
getSkuDetails
(
3
,
mContext
.
getPackageName
(),
itemType
,
querySkus
);
if
(!
skuDetails
.
containsKey
(
RESPONSE_GET_SKU_DETAILS_LIST
))
{
int
response
=
getResponseCodeFromBundle
(
skuDetails
);
if
(
response
==
BILLING_RESPONSE_RESULT_OK
)
{
logError
(
"getSkuDetails() returned a bundle with neither an error nor a detail list."
);
return
IABHELPER_BAD_RESPONSE
;
}
else
{
logDebug
(
"getSkuDetails() failed: "
+
getResponseDesc
(
response
));
return
response
;
}
}
ArrayList
<
String
>
responseList
=
skuDetails
.
getStringArrayList
(
RESPONSE_GET_SKU_DETAILS_LIST
);
if
(
responseList
!=
null
)
{
for
(
String
thisResponse
:
responseList
)
{
SkuDetails
d
=
new
SkuDetails
(
itemType
,
thisResponse
);
logDebug
(
"Got sku details: "
+
d
);
inv
.
addSkuDetails
(
d
);
}
}
return
BILLING_RESPONSE_RESULT_OK
;
}
void
consumeAsyncInternal
(
final
List
<
Purchase
>
purchases
,
final
OnConsumeFinishedListener
singleListener
,
final
OnConsumeMultiFinishedListener
multiListener
)
{
final
Handler
handler
=
new
Handler
();
flagStartAsync
(
"consume"
);
(
new
Thread
(
new
Runnable
()
{
@Override
public
void
run
()
{
final
List
<
IabResult
>
results
=
new
ArrayList
<>();
for
(
Purchase
purchase
:
purchases
)
{
try
{
consume
(
purchase
);
results
.
add
(
new
IabResult
(
BILLING_RESPONSE_RESULT_OK
,
"Successful consume of sku "
+
purchase
.
getSku
()));
}
catch
(
IabException
ex
)
{
results
.
add
(
ex
.
getResult
());
}
}
flagEndAsync
();
if
(!
mDisposed
&&
singleListener
!=
null
)
{
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
singleListener
.
onConsumeFinished
(
purchases
.
get
(
0
),
results
.
get
(
0
));
}
});
}
if
(!
mDisposed
&&
multiListener
!=
null
)
{
handler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
multiListener
.
onConsumeMultiFinished
(
purchases
,
results
);
}
});
}
}
})).
start
();
}
void
logDebug
(
String
msg
)
{
if
(
mDebugLog
)
{
Logger
.
d
(
mDebugTag
,
msg
);
}
}
void
logError
(
String
msg
)
{
Logger
.
e
(
mDebugTag
,
"In-app billing error: "
+
msg
);
}
void
logWarn
(
String
msg
)
{
Logger
.
w
(
mDebugTag
,
"In-app billing warning: "
+
msg
);
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/IabResult.java
deleted
100644 → 0
View file @
3d1d8b46
/* Copyright (c) 2012 Google Inc.
*
* 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
jp
.
agentec
.
abook
.
abv
.
cl
.
billing
;
/**
* Represents the result of an in-app billing operation.
* A result is composed of a response code (an integer) and possibly a
* message (String). You can get those by calling
* {@link #getResponse} and {@link #getMessage()}, respectively. You
* can also inquire whether a result is a success or a failure by
* calling {@link #isSuccess()} and {@link #isFailure()}.
*/
public
class
IabResult
{
int
mResponse
;
String
mMessage
;
public
IabResult
(
int
response
,
String
message
)
{
mResponse
=
response
;
if
(
message
==
null
||
message
.
trim
().
length
()
==
0
)
{
mMessage
=
IabHelper
.
getResponseDesc
(
response
);
}
else
{
mMessage
=
message
+
" (response: "
+
IabHelper
.
getResponseDesc
(
response
)
+
")"
;
}
}
public
int
getResponse
()
{
return
mResponse
;
}
public
String
getMessage
()
{
return
mMessage
;
}
public
boolean
isSuccess
()
{
return
mResponse
==
IabHelper
.
BILLING_RESPONSE_RESULT_OK
;
}
public
boolean
isFailure
()
{
return
!
isSuccess
();
}
public
String
toString
()
{
return
"IabResult: "
+
getMessage
();
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/Inventory.java
deleted
100644 → 0
View file @
3d1d8b46
/* Copyright (c) 2012 Google Inc.
*
* 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
jp
.
agentec
.
abook
.
abv
.
cl
.
billing
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
/**
* Represents a block of information about in-app items.
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
*/
public
class
Inventory
{
Map
<
String
,
SkuDetails
>
mSkuMap
=
new
HashMap
<>();
Map
<
String
,
Purchase
>
mPurchaseMap
=
new
HashMap
<>();
Inventory
()
{
}
/** Returns the listing details for an in-app product. */
public
SkuDetails
getSkuDetails
(
String
sku
)
{
return
mSkuMap
.
get
(
sku
);
}
/** Returns purchase information for a given product, or null if there is no purchase. */
public
Purchase
getPurchase
(
String
sku
)
{
return
mPurchaseMap
.
get
(
sku
);
}
/** Returns whether or not there exists a purchase of the given product. */
public
boolean
hasPurchase
(
String
sku
)
{
return
mPurchaseMap
.
containsKey
(
sku
);
}
/** Return whether or not details about the given product are available. */
public
boolean
hasDetails
(
String
sku
)
{
return
mSkuMap
.
containsKey
(
sku
);
}
/**
* Erase a purchase (locally) from the inventory, given its product ID. This just
* modifies the Inventory object locally and has no effect on the server! This is
* useful when you have an existing Inventory object which you know to be up to date,
* and you have just consumed an item successfully, which means that erasing its
* purchase data from the Inventory you already have is quicker than querying for
* a new Inventory.
*/
public
void
erasePurchase
(
String
sku
)
{
if
(
mPurchaseMap
.
containsKey
(
sku
))
{
mPurchaseMap
.
remove
(
sku
);
}
}
/** Returns a list of all owned product IDs. */
List
<
String
>
getAllOwnedSkus
()
{
return
new
ArrayList
<>(
mPurchaseMap
.
keySet
());
}
/** Returns a list of all owned product IDs of a given type */
List
<
String
>
getAllOwnedSkus
(
String
itemType
)
{
List
<
String
>
result
=
new
ArrayList
<>();
for
(
Purchase
p
:
mPurchaseMap
.
values
())
{
if
(
p
.
getItemType
().
equals
(
itemType
))
{
result
.
add
(
p
.
getSku
());
}
}
return
result
;
}
/** Returns a list of all purchases. */
List
<
Purchase
>
getAllPurchases
()
{
return
new
ArrayList
<>(
mPurchaseMap
.
values
());
}
void
addSkuDetails
(
SkuDetails
d
)
{
mSkuMap
.
put
(
d
.
getSku
(),
d
);
}
void
addPurchase
(
Purchase
p
)
{
mPurchaseMap
.
put
(
p
.
getSku
(),
p
);
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/Purchase.java
deleted
100644 → 0
View file @
3d1d8b46
/* Copyright (c) 2012 Google Inc.
*
* 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
jp
.
agentec
.
abook
.
abv
.
cl
.
billing
;
import
org.json.JSONException
;
import
org.json.JSONObject
;
/**
* Represents an in-app billing purchase.
*/
public
class
Purchase
{
String
mItemType
;
// ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
String
mOrderId
;
String
mPackageName
;
String
mSku
;
long
mPurchaseTime
;
int
mPurchaseState
;
String
mDeveloperPayload
;
String
mToken
;
String
mOriginalJson
;
String
mSignature
;
public
Purchase
(
String
itemType
,
String
jsonPurchaseInfo
,
String
signature
)
throws
JSONException
{
mItemType
=
itemType
;
mOriginalJson
=
jsonPurchaseInfo
;
JSONObject
o
=
new
JSONObject
(
mOriginalJson
);
mOrderId
=
o
.
optString
(
"orderId"
);
mPackageName
=
o
.
optString
(
"packageName"
);
mSku
=
o
.
optString
(
"productId"
);
mPurchaseTime
=
o
.
optLong
(
"purchaseTime"
);
mPurchaseState
=
o
.
optInt
(
"purchaseState"
);
mDeveloperPayload
=
o
.
optString
(
"developerPayload"
);
mToken
=
o
.
optString
(
"token"
,
o
.
optString
(
"purchaseToken"
));
mSignature
=
signature
;
}
public
String
getItemType
()
{
return
mItemType
;
}
public
String
getOrderId
()
{
return
mOrderId
;
}
public
String
getPackageName
()
{
return
mPackageName
;
}
public
String
getSku
()
{
return
mSku
;
}
public
long
getPurchaseTime
()
{
return
mPurchaseTime
;
}
public
int
getPurchaseState
()
{
return
mPurchaseState
;
}
public
String
getDeveloperPayload
()
{
return
mDeveloperPayload
;
}
public
String
getToken
()
{
return
mToken
;
}
public
String
getOriginalJson
()
{
return
mOriginalJson
;
}
public
String
getSignature
()
{
return
mSignature
;
}
@Override
public
String
toString
()
{
return
"PurchaseInfo(type:"
+
mItemType
+
"):"
+
mOriginalJson
;
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/Security.java
deleted
100644 → 0
View file @
3d1d8b46
/* Copyright (c) 2012 Google Inc.
*
* 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
jp
.
agentec
.
abook
.
abv
.
cl
.
billing
;
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
;
import
android.text.TextUtils
;
import
android.util.Log
;
/**
* 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 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
(
TextUtils
.
isEmpty
(
signedData
)
||
TextUtils
.
isEmpty
(
base64PublicKey
)
||
TextUtils
.
isEmpty
(
signature
))
{
Log
.
e
(
TAG
,
"Purchase verification failed: missing data."
);
return
false
;
}
PublicKey
key
=
Security
.
generatePublicKey
(
base64PublicKey
);
return
Security
.
verify
(
key
,
signedData
,
signature
);
}
/**
* 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
;
}
}
ABVJE_UI_Android/src/jp/agentec/abook/abv/cl/billing/SkuDetails.java
deleted
100644 → 0
View file @
3d1d8b46
/* Copyright (c) 2012 Google Inc.
*
* 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
jp
.
agentec
.
abook
.
abv
.
cl
.
billing
;
import
org.json.JSONException
;
import
org.json.JSONObject
;
/**
* Represents an in-app product's listing details.
*/
public
class
SkuDetails
{
String
mItemType
;
String
mSku
;
String
mType
;
String
mPrice
;
String
mTitle
;
String
mDescription
;
String
mJson
;
public
SkuDetails
(
String
jsonSkuDetails
)
throws
JSONException
{
this
(
IabHelper
.
ITEM_TYPE_INAPP
,
jsonSkuDetails
);
}
public
SkuDetails
(
String
itemType
,
String
jsonSkuDetails
)
throws
JSONException
{
mItemType
=
itemType
;
mJson
=
jsonSkuDetails
;
JSONObject
o
=
new
JSONObject
(
mJson
);
mSku
=
o
.
optString
(
"productId"
);
mType
=
o
.
optString
(
"type"
);
mPrice
=
o
.
optString
(
"price"
);
mTitle
=
o
.
optString
(
"title"
);
mDescription
=
o
.
optString
(
"description"
);
}
public
String
getSku
()
{
return
mSku
;
}
public
String
getType
()
{
return
mType
;
}
public
String
getPrice
()
{
return
mPrice
;
}
public
String
getTitle
()
{
return
mTitle
;
}
public
String
getDescription
()
{
return
mDescription
;
}
@Override
public
String
toString
()
{
return
"SkuDetails:"
+
mJson
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment