All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.robotmedia.billing.BillingService Maven / Gradle / Ivy

/*
 * Copyright 2013 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: [email protected]
 * Site:  http://se.solovyev.org
 */

package net.robotmedia.billing;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.vending.billing.IMarketBillingService;
import net.robotmedia.billing.utils.Compatibility;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class BillingService extends Service implements ServiceConnection, IBillingService {

	@Nonnull
	private static final String TAG = BillingService.class.getSimpleName();

	private static final String ACTION_MARKET_BILLING_SERVICE = "com.android.vending.billing.MarketBillingService.BIND";

	// all operations must be synchronized
	@Nonnull
	private static final List pendingRequests = new LinkedList();

	private static final int MAX_RETRIES = 3;

	@Nullable
	private static IMarketBillingService service;

	static void checkBillingSupported(@Nonnull Context context) {
		context.startService(createIntent(context, BillingRequestType.CHECK_BILLING_SUPPORTED));
	}

	static void confirmNotifications(@Nonnull Context context, @Nonnull String[] notifyIds) {
		final Intent intent = createIntent(context, BillingRequestType.CONFIRM_NOTIFICATIONS);
		intent.putExtra(BillingRequestType.EXTRA_NOTIFY_IDS, notifyIds);
		context.startService(intent);
	}

	static void confirmNotifications(@Nonnull Context context, @Nonnull Collection notifyIds) {
		confirmNotifications(context, notifyIds.toArray(new String[notifyIds.size()]));
	}

	static void getPurchaseInformation(@Nonnull Context context, @Nonnull Collection notifyIds, long nonce) {
		getPurchaseInformation(context, notifyIds.toArray(new String[notifyIds.size()]), nonce);
	}

	static void getPurchaseInformation(@Nonnull Context context, @Nonnull String[] notifyIds, long nonce) {
		final Intent intent = createIntent(context, BillingRequestType.GET_PURCHASE_INFORMATION);
		intent.putExtra(BillingRequestType.EXTRA_NOTIFY_IDS, notifyIds);
		intent.putExtra(BillingRequestType.EXTRA_NONCE, nonce);
		context.startService(intent);
	}

	static void requestPurchase(@Nonnull Context context, @Nonnull String productId, @Nullable String developerPayload) {
		final Intent intent = createIntent(context, BillingRequestType.REQUEST_PURCHASE);
		intent.putExtra(BillingRequestType.EXTRA_ITEM_ID, productId);
		intent.putExtra(BillingRequestType.EXTRA_DEVELOPER_PAYLOAD, developerPayload);
		context.startService(intent);
	}

	static void restoreTransactions(@Nonnull Context context, long nonce) {
		final Intent intent = createIntent(context, BillingRequestType.RESTORE_TRANSACTIONS);
		intent.setClass(context, BillingService.class);
		intent.putExtra(BillingRequestType.EXTRA_NONCE, nonce);
		context.startService(intent);
	}

	@Nonnull
	private static Intent createIntent(@Nonnull Context context, @Nonnull BillingRequestType action) {
		final Intent result = new Intent(action.toIntentAction(context));

		result.setClass(context, BillingService.class);

		return result;
	}

	private void bindMarketBillingService() {
		try {
			final boolean bindResult = bindService(new Intent(ACTION_MARKET_BILLING_SERVICE), this, Context.BIND_AUTO_CREATE);
			if (!bindResult) {
				Log.e(this.getClass().getSimpleName(), "Could not bind to MarketBillingService");
			}
		} catch (SecurityException e) {
			Log.e(this.getClass().getSimpleName(), "Could not bind to MarketBillingService", e);
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
		BillingService.service = IMarketBillingService.Stub.asInterface(service);
		runPendingRequests();
	}

	@Override
	public void onServiceDisconnected(ComponentName name) {
		service = null;
	}

	// This is the old onStart method that will be called on the pre-2.0
	// platform.  On 2.0 or later we override onStartCommand() so this
	// method will not be called.
	@Override
	public void onStart(Intent intent, int startId) {
		handleCommand(intent, startId);
	}

	// @Override // Avoid compile errors on pre-2.0
	public int onStartCommand(Intent intent, int flags, int startId) {
		handleCommand(intent, startId);
		return Compatibility.START_NOT_STICKY;
	}

	private void handleCommand(@Nonnull Intent intent, int startId) {
		final BillingRequestType action = BillingRequestType.fromIntentAction(intent);
		if (action != null) {
			action.doAction(this, intent, startId);
		}
	}

	/**
	 * @return 'true' if all requests (on the moment of start) were processed during current procedure. False is possible only if service was disconnected during processing
	 */
	private boolean runPendingRequests() {
		int maxStartId = -1;

		synchronized (pendingRequests) {
			for (Iterator it = pendingRequests.iterator(); it.hasNext(); ) {
				final IBillingRequest request = it.next();

				// if service was disconnected during procedure => connect
				if (service != null) {
					runRequest(service, request, 1);
					it.remove();

					maxStartId = Math.max(maxStartId, request.getStartId());
				} else {
					bindMarketBillingService();
					return false;
				}
			}

		}

		if (maxStartId >= 0) {
			stopSelf(maxStartId);
		}

		return true;
	}

	private boolean runRequest(@Nonnull IMarketBillingService service, @Nonnull IBillingRequest request, int counter) {
		try {
			BillingController.debug("Running request: " + request.getRequestType());

			final long requestId = request.run(service);

			BillingController.onRequestSent(requestId, request);

			return true;
		} catch (RemoteException e) {
			BillingController.debug("Remote exception: " + e.getMessage());

			Log.w(this.getClass().getSimpleName(), "Remote billing service crashed");
			if (counter < MAX_RETRIES) {
				return runRequest(service, request, counter + 1);
			} else {
				return false;
			}
		}
	}

	@Override
	public void runRequestOrQueue(@Nonnull IBillingRequest request) {
		synchronized (pendingRequests) {
			pendingRequests.add(request);
		}

		if (service == null) {
			bindMarketBillingService();
		} else {
			runPendingRequests();
		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		// Ensure we're not leaking Android Market billing service
		if (service != null) {
			try {
				unbindService(this);
			} catch (IllegalArgumentException e) {
				// This might happen if the service was disconnected
			}
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy