[android] Implement handling of billing connection state and executing asyn request in the play store manager

This commit is contained in:
Alexander Zatsepin 2018-09-03 16:06:10 +03:00 committed by yoksnod
parent a78bfca626
commit 9a937e0498
7 changed files with 264 additions and 18 deletions

View file

@ -0,0 +1,28 @@
package com.mapswithme.maps.purchase;
import android.support.annotation.NonNull;
/**
* Represents a billing connection abstraction.
*/
public interface BillingConnection
{
/**
* Connects to the billing manager.
*/
void connect();
/**
* @return the connection state of the billing manager.
*/
@NonNull
State getState();
enum State
{
DISCONNECTED,
CONNECTING,
CONNECTED,
CLOSED
}
}

View file

@ -4,20 +4,43 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* Manages a billing flow.
* Manages a billing flow for the specific product.
*/
interface BillingManager
public interface BillingManager<T>
{
/**
* Launches the billing flow. Controls whole native part of billing.
* @param productId id of product which is going to be bought by user.
* Initializes the current billing manager.
*/
void launchBillingFlow(@NonNull String productId);
void initialize();
/**
* Destroys the billing manager.
*/
void destroy();
/**
* Launches the billing flow. Controls whole native part of billing.
*/
void launchBillingFlow();
/**
* Obtains a purchase token if it exists.
* @param productId id of product which token is needed for.
*/
@Nullable
String obtainTokenForProduct(@NonNull String productId);
String obtainPurchaseToken();
/**
* Queries product details. They will be delivered to the caller through callback {@link T}.
*/
void queryProductDetails();
/**
* Adds a billing callback.
*/
void addCallback(@NonNull T callback);
/**
* Removes the billing callback.
*/
void removeCallback(@NonNull T callback);
}

View file

@ -3,6 +3,8 @@ package com.mapswithme.maps.purchase;
import android.app.Activity;
import android.support.annotation.NonNull;
import com.android.billingclient.api.BillingClient;
public enum Factory
{
ADS_REMOVAL
@ -16,9 +18,9 @@ public enum Factory
@NonNull
@Override
BillingManager createBillingManager(@NonNull Activity activity)
public BillingManager createBillingManager(@NonNull Activity activity, @NonNull String productId)
{
return new PlayStoreBillingManager(activity);
return new PlayStoreBillingManager(activity, productId, BillingClient.SkuType.SUBS);
}
};
@ -26,5 +28,5 @@ public enum Factory
public abstract PurchaseValidator createPurchaseManager();
@NonNull
abstract BillingManager createBillingManager(@NonNull Activity activity);
public abstract BillingManager createBillingManager(@NonNull Activity activity, @NonNull String productId);
}

View file

@ -0,0 +1,10 @@
package com.mapswithme.maps.purchase;
import android.support.annotation.NonNull;
import java.util.List;
public interface PlayStoreBillingCallback
{
void onPurchaseDetailsLoaded(@NonNull List<PurchaseDetails> details);
}

View file

@ -0,0 +1,69 @@
package com.mapswithme.maps.purchase;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
class PlayStoreBillingConnection implements BillingConnection,
BillingClientStateListener
{
private static final Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private static final String TAG = PlayStoreBillingConnection.class.getSimpleName();
@NonNull
private final BillingClient mBillingClient;
@NonNull
private State mState = State.DISCONNECTED;
@Nullable
private final ConnectionListener mListener;
PlayStoreBillingConnection(@NonNull BillingClient billingClient,
@Nullable ConnectionListener listener)
{
mBillingClient = billingClient;
mListener = listener;
}
@Override
public void connect()
{
mState = State.CONNECTING;
mBillingClient.startConnection(this);
}
@NonNull
@Override
public State getState()
{
return mState;
}
@Override
public void onBillingSetupFinished(int responseCode)
{
LOGGER.i(TAG, "Setup finished. Response code: " + responseCode);
if (responseCode == BillingClient.BillingResponse.OK)
{
mState = State.CONNECTED;
if (mListener != null)
mListener.onConnected();
return;
}
mState = State.DISCONNECTED;
}
@Override
public void onBillingServiceDisconnected()
{
mState = State.DISCONNECTED;
}
interface ConnectionListener
{
void onConnected();
}
}

View file

@ -8,31 +8,91 @@ import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClient.BillingResponse;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.mapswithme.util.log.Logger;
import com.mapswithme.util.log.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PlayStoreBillingManager implements BillingManager, PurchasesUpdatedListener
public class PlayStoreBillingManager implements BillingManager<PlayStoreBillingCallback>,
PurchasesUpdatedListener,
PlayStoreBillingConnection.ConnectionListener
{
private final static Logger LOGGER = LoggerFactory.INSTANCE.getLogger(LoggerFactory.Type.MISC);
private final static String TAG = PlayStoreBillingManager.class.getSimpleName();
@NonNull
private final Activity mActivity;
@Nullable
private BillingClient mBillingClient;
@Nullable
private PlayStoreBillingCallback mCallback;
@NonNull
private final BillingClient mBillingClient;
PlayStoreBillingManager(@NonNull Activity activity)
private final String mProductId;
@NonNull
@BillingClient.SkuType
private final String mProductType;
@SuppressWarnings({ "NullableProblems" })
@NonNull
private BillingConnection mConnection;
@NonNull
private final List<Runnable> mPendingRequests = new ArrayList<>();
PlayStoreBillingManager(@NonNull Activity activity, @NonNull String productId,
@NonNull @BillingClient.SkuType String productType)
{
mActivity = activity;
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
mProductId = productId;
mProductType = productType;
}
@Override
public void launchBillingFlow(@NonNull String productId)
public void initialize()
{
LOGGER.i(TAG, "Creating play store billing client...");
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
mConnection = new PlayStoreBillingConnection(mBillingClient, this);
mConnection.connect();
}
@Override
public void destroy()
{
// Coming soon.
}
@Override
public void queryProductDetails()
{
executeServiceRequest(new QueryProductDetailsRequest());
}
private void executeServiceRequest(@NonNull Runnable task)
{
switch (mConnection.getState())
{
case CONNECTING:
mPendingRequests.add(task);
break;
case CONNECTED:
task.run();
break;
case DISCONNECTED:
mConnection.connect();
break;
case CLOSED:
throw new IllegalStateException("Billing service connection already closed, " +
"please initialize it again.");
}
}
@Override
public void launchBillingFlow()
{
@BillingResponse
int result = mBillingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS);
int result = getClientOrThrow().isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS);
if (result == BillingResponse.FEATURE_NOT_SUPPORTED)
{
LOGGER.w(TAG, "Subscription is not supported by this device!");
@ -44,14 +104,63 @@ public class PlayStoreBillingManager implements BillingManager, PurchasesUpdated
@Nullable
@Override
public String obtainTokenForProduct(@NonNull String productId)
public String obtainPurchaseToken()
{
return null;
}
@Override
public void addCallback(@NonNull PlayStoreBillingCallback callback)
{
mCallback = callback;
}
@Override
public void removeCallback(@NonNull PlayStoreBillingCallback callback)
{
mCallback = null;
}
@Override
public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases)
{
// Coming soon.
}
@NonNull
private BillingClient getClientOrThrow()
{
if (mBillingClient == null)
throw new IllegalStateException("Manager must be initialized! Call 'initialize' method first.");
return mBillingClient;
}
@Override
public void onConnected()
{
for(@NonNull Runnable request: mPendingRequests)
request.run();
mPendingRequests.clear();
}
private class QueryProductDetailsRequest implements Runnable
{
@Override
public void run()
{
SkuDetailsParams.Builder builder = SkuDetailsParams.newBuilder();
builder.setSkusList(Collections.singletonList(mProductId))
.setType(mProductType);
getClientOrThrow().querySkuDetailsAsync(builder.build(), this::onSkuDetailsResponseInternal);
}
private void onSkuDetailsResponseInternal(@BillingResponse int responseCode,
@Nullable List<SkuDetails> skuDetails)
{
LOGGER.i(TAG, "Product details response, code: " + responseCode + ", " +
"details: " + skuDetails);
}
}
}

View file

@ -0,0 +1,5 @@
package com.mapswithme.maps.purchase;
public class PurchaseDetails
{
}