/*
* Copyright 2014 serso aka se.solovyev
*
* 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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Contact details
*
* Email: se.solovyev@gmail.com
* Site: http://se.solovyev.org
*/
package org.solovyev.android.checkout;
import com.android.vending.billing.IInAppBillingService;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.TextUtils;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import static org.solovyev.android.checkout.ResponseCodes.EXCEPTION;
import static org.solovyev.android.checkout.ResponseCodes.OK;
/**
* Asynchronous operation which is done with connected billing service
*/
abstract class Request<R> {
@Nonnull
private static final AtomicInteger sCounter = new AtomicInteger(0);
protected final int mApiVersion;
private final int mId;
@Nonnull
private final RequestType mType;
@Nullable
private Object mTag;
@GuardedBy("this")
@Nullable
private RequestListener<R> mListener;
@GuardedBy("this")
private boolean mListenerCalled;
Request(@Nonnull RequestType type) {
this(type, Billing.V3);
}
Request(@Nonnull RequestType type, int apiVersion) {
mType = type;
mApiVersion = apiVersion;
mId = sCounter.getAndIncrement();
}
Request(@Nonnull RequestType type, @Nonnull Request<R> request) {
mType = type;
mId = request.mId;
mApiVersion = request.mApiVersion;
synchronized (request) {
mListener = request.mListener;
}
}
/**
* @return request id, unique identifier of the request in the application
*/
protected int getId() {
return mId;
}
abstract void start(@Nonnull IInAppBillingService service, @Nonnull String packageName)
throws RemoteException, RequestException;
/**
* @return request tag, object which is associated with this request
*/
@Nullable
Object getTag() {
return mTag;
}
void setTag(@Nullable Object tag) {
this.mTag = tag;
}
@Nonnull
RequestType getType() {
return mType;
}
/**
* Cancels this request, after this method is called request listener method will not be called
*/
void cancel() {
synchronized (this) {
if (mListener != null) {
Billing.cancel(mListener);
}
mListener = null;
}
}
/**
* @return true if request is cancelled
*/
boolean isCancelled() {
synchronized (this) {
return mListener == null;
}
}
protected void onSuccess(@Nonnull R result) {
final RequestListener<R> l = getListener();
if (l != null) {
if (checkListenerCalled()) return;
l.onSuccess(result);
}
}
private boolean checkListenerCalled() {
synchronized (this) {
if (mListenerCalled) {
return true;
}
mListenerCalled = true;
}
return false;
}
protected void onError(int response) {
Billing.error("Error response: " + ResponseCodes.toString(response) + " in " + this + " request");
onError(response, new BillingException(response));
}
public void onError(@Nonnull Exception e) {
Check.isFalse(e instanceof BillingException, "Use onError(int) instead");
Billing.error("Exception in " + this + " request: ", e);
onError(EXCEPTION, e);
}
private void onError(int response, @Nonnull Exception e) {
Check.notEquals(OK, response);
final RequestListener<R> l = getListener();
if (l != null) {
if (checkListenerCalled()) return;
l.onError(response, e);
}
}
protected final boolean handleError(@Nullable Bundle bundle) {
final int response = bundle != null ? bundle.getInt("RESPONSE_CODE") : ResponseCodes.ERROR;
return handleError(response);
}
protected final boolean handleError(int response) {
if (response != OK) {
onError(response);
return true;
}
return false;
}
@Nullable
RequestListener<R> getListener() {
synchronized (this) {
return mListener;
}
}
void setListener(@Nullable RequestListener<R> listener) {
synchronized (this) {
Check.isNull(this.mListener);
mListener = listener;
}
}
@Override
public String toString() {
final String cacheKey = getCacheKey();
if (!TextUtils.isEmpty(cacheKey)) {
return getClass().getSimpleName() + "(" + cacheKey + ")";
} else {
return getClass().getSimpleName();
}
}
/**
* @return key to be used in the cache, null if request should not be cached
*/
@Nullable
abstract String getCacheKey();
}