NetGuard/app/src/main/java/eu/faircode/netguard/IAB.java

282 lines
10 KiB
Java
Raw Normal View History

2015-11-14 20:25:05 +00:00
package eu.faircode.netguard;
/*
This file is part of NetGuard.
NetGuard is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NetGuard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
2015-11-14 20:25:05 +00:00
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
2015-12-27 11:06:10 +00:00
import android.content.SharedPreferences;
2015-11-14 20:25:05 +00:00
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.android.vending.billing.IInAppBillingService;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
2015-12-27 11:06:10 +00:00
import java.util.Date;
import java.util.concurrent.CountDownLatch;
2015-11-14 20:25:05 +00:00
public class IAB implements ServiceConnection {
private static final String TAG = "Netguard.IAB";
private Context context;
2015-12-27 11:06:10 +00:00
private Event event;
2015-11-14 20:25:05 +00:00
private boolean available = false;
private IInAppBillingService service = null;
2015-11-18 16:56:05 +00:00
private static final int IAB_VERSION = 3;
2015-11-14 20:25:05 +00:00
// adb shell pm clear com.android.vending
// adb shell am start -n eu.faircode.netguard/eu.faircode.netguard.ActivityMain
2015-12-27 11:06:10 +00:00
private static final String SKU_PRO = "pro";
2015-11-14 20:25:05 +00:00
private static final String SKU_DONATE = "donation";
// private static final String SKU_DONATE = "android.test.purchased";
public static final String ACTION_IAB = "eu.faircode.netguard.IAB";
2015-11-14 20:25:05 +00:00
2015-12-27 11:06:10 +00:00
public interface Event {
void bound();
}
2015-11-14 20:25:05 +00:00
public IAB(Context context) {
this.context = context;
}
2015-12-27 11:06:10 +00:00
public void bind(Event event) {
this.event = event;
2015-11-26 07:41:20 +00:00
try {
Log.i(TAG, "Bind");
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
context.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, context);
}
2015-11-14 20:25:05 +00:00
}
public PendingIntent getIntentSender() throws RemoteException {
2015-12-27 09:44:50 +00:00
return (service != null && available ? getBuyIntent(SKU_DONATE) : null);
2015-11-14 20:25:05 +00:00
}
public void unbind() {
2015-11-26 07:41:20 +00:00
if (service != null)
try {
Log.i(TAG, "Unbind");
context.unbindService(this);
service = null;
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, context);
}
2015-11-14 20:25:05 +00:00
}
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.i(TAG, "Connected");
2015-12-27 11:06:10 +00:00
service = IInAppBillingService.Stub.asInterface(binder);
2015-11-14 20:25:05 +00:00
2015-12-27 11:06:10 +00:00
if (this.event == null)
try {
if (isPurchased(SKU_DONATE)) {
Intent intent = new Intent(ACTION_IAB);
intent.putExtra("RESULT_CODE", Activity.RESULT_OK);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
} else
available = (service != null && isAvailable(SKU_DONATE));
2015-11-14 20:25:05 +00:00
2015-12-27 11:06:10 +00:00
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, context);
}
else
event.bound();
2015-11-14 20:25:05 +00:00
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "Disconnected");
service = null;
available = false;
}
2015-12-27 09:44:50 +00:00
private boolean isAvailable(String sku) throws RemoteException, JSONException {
2015-11-26 07:41:20 +00:00
try {
// Get available SKUs
ArrayList<String> skuList = new ArrayList<>();
skuList.add(sku);
Bundle query = new Bundle();
query.putStringArrayList("ITEM_ID_LIST", skuList);
Bundle bundle = service.getSkuDetails(IAB_VERSION, context.getPackageName(), "inapp", query);
Log.i(TAG, "getSkuDetails");
Util.logBundle(bundle);
int response = (bundle == null ? -1 : bundle.getInt("RESPONSE_CODE", -1));
2015-12-27 09:44:50 +00:00
Log.i(TAG, "Response=" + getResult(response));
2015-11-26 07:41:20 +00:00
if (response != 0)
return false;
// Check available SKUs
boolean found = false;
ArrayList<String> details = bundle.getStringArrayList("DETAILS_LIST");
if (details != null)
for (String item : details) {
JSONObject object = new JSONObject(item);
if (sku.equals(object.getString("productId"))) {
found = true;
break;
}
2015-11-14 20:25:05 +00:00
}
2015-11-26 07:41:20 +00:00
Log.i(TAG, sku + "=" + found);
2015-11-14 20:25:05 +00:00
2015-11-26 07:41:20 +00:00
return found;
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, context);
return false;
}
2015-11-14 20:25:05 +00:00
}
2015-12-27 11:06:10 +00:00
public boolean hasPro() {
boolean pro = false;
final CountDownLatch latch = new CountDownLatch(1);
if (Util.isDebuggable(context))
return true;
bind(new Event() {
@Override
public void bound() {
try {
SharedPreferences prefs = context.getSharedPreferences("IAB", Context.MODE_PRIVATE);
boolean pro = false;
long now = new Date().getTime();
long refreshed = prefs.getLong("refreshed", 0);
if (!prefs.getBoolean("pro", false) || refreshed < now - 3 * 24 * 3600 * 1000L) {
Log.i(TAG, "Refreshing pro");
try {
pro = isPurchased(SKU_PRO);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("pro", pro);
editor.putLong("refreshed", now);
editor.apply();
Log.i(TAG, "Refreshed pro=" + pro);
} catch (RemoteException ignored) {
if (refreshed >= now - 5 * 24 * 3600 * 1000L) {
pro = prefs.getBoolean("pro", false);
Log.i(TAG, "Grace pro=" + pro);
} else {
SharedPreferences.Editor editor = prefs.edit();
editor.remove("pro");
editor.remove("refreshed");
editor.apply();
Log.i(TAG, "Cache expired");
}
}
} else {
pro = prefs.getBoolean("pro", false);
Log.i(TAG, "Cached pro=" + pro);
}
} finally {
try {
latch.await();
} catch (InterruptedException ignored) {
}
}
}
});
latch.countDown();
return pro;
}
2015-12-27 09:44:50 +00:00
private boolean isPurchased(String sku) throws RemoteException {
2015-11-26 07:41:20 +00:00
try {
// Get purchases
Bundle bundle = service.getPurchases(IAB_VERSION, context.getPackageName(), "inapp", null);
Log.i(TAG, "getPurchases");
Util.logBundle(bundle);
int response = (bundle == null ? -1 : bundle.getInt("RESPONSE_CODE", -1));
2015-12-27 09:44:50 +00:00
Log.i(TAG, "Response=" + getResult(response));
2015-11-26 07:41:20 +00:00
if (response != 0)
return false;
// Check purchases
ArrayList<String> skus = bundle.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
return (skus != null && skus.contains(sku));
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, context);
2015-11-14 20:25:05 +00:00
return false;
2015-11-26 07:41:20 +00:00
}
2015-11-14 20:25:05 +00:00
}
2015-12-27 09:44:50 +00:00
private PendingIntent getBuyIntent(String sku) throws RemoteException {
2015-11-26 07:41:20 +00:00
try {
Bundle bundle = service.getBuyIntent(IAB_VERSION, context.getPackageName(), sku, "inapp", "netguard");
Log.i(TAG, "getBuyIntent");
Util.logBundle(bundle);
int response = (bundle == null ? -1 : bundle.getInt("RESPONSE_CODE", -1));
2015-12-27 09:44:50 +00:00
Log.i(TAG, "Response=" + getResult(response));
2015-11-26 07:41:20 +00:00
if (response != 0 || !bundle.containsKey("BUY_INTENT")) {
2015-12-27 09:44:50 +00:00
Util.sendCrashReport(new IllegalStateException(getResult(response)), context);
2015-11-26 07:41:20 +00:00
return null;
}
return bundle.getParcelable("BUY_INTENT");
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, context);
2015-11-14 20:25:05 +00:00
return null;
2015-11-23 10:05:44 +00:00
}
2015-11-14 20:25:05 +00:00
}
2015-12-27 09:44:50 +00:00
public static String getResult(int responseCode) {
2015-11-14 20:25:05 +00:00
switch (responseCode) {
case 0:
return "OK";
case 1:
return "USER_CANCELED";
case 2:
return "SERVICE_UNAVAILABLE";
case 3:
return "BILLING_UNAVAILABLE";
case 4:
return "ITEM_UNAVAILABLE";
case 5:
return "DEVELOPER_ERROR";
case 6:
return "ERROR";
case 7:
return "ITEM_ALREADY_OWNED";
case 8:
return "ITEM_NOT_OWNED";
default:
return Integer.toString(responseCode);
}
}
}