Skip to content

Commit 0a308f6

Browse files
author
Chris Bellew
committed
Updated in app purchase helper, and added listener for redeemed promo codes. Don't do automatic device scan on very first launch until login is chosen or skipped. Fixed scan all functionality. Styled chromecast and wear purchase popups.
1 parent 0b74466 commit 0a308f6

File tree

12 files changed

+381
-143
lines changed

12 files changed

+381
-143
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* Copyright (c) 2014 Google Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package com.android.vending.billing;
17+
18+
import android.content.BroadcastReceiver;
19+
import android.content.Context;
20+
import android.content.Intent;
21+
22+
/**
23+
* Receiver for the "com.android.vending.billing.PURCHASES_UPDATED" Action
24+
* from the Play Store.
25+
*
26+
* <p>It is possible that an in-app item may be acquired without the
27+
* application calling getBuyIntent(), for example if the item can be
28+
* redeemed from inside the Play Store using a promotional code. If this
29+
* application isn't running at the time, then when it is started a call
30+
* to getPurchases() will be sufficient notification. However, if the
31+
* application is already running in the background when the item is acquired,
32+
* a message to this BroadcastReceiver will indicate that the an item
33+
* has been acquired.</p>
34+
*/
35+
public class IabBroadcastReceiver extends BroadcastReceiver {
36+
/**
37+
* Listener interface for received broadcast messages.
38+
*/
39+
public interface IabBroadcastListener {
40+
void receivedBroadcast();
41+
}
42+
43+
/**
44+
* The Intent action that this Receiver should filter for.
45+
*/
46+
public static final String ACTION = "com.android.vending.billing.PURCHASES_UPDATED";
47+
48+
private final IabBroadcastListener mListener;
49+
50+
public IabBroadcastReceiver(IabBroadcastListener listener) {
51+
mListener = listener;
52+
}
53+
54+
@Override
55+
public void onReceive(Context context, Intent intent) {
56+
if (mListener != null) {
57+
mListener.receivedBroadcast();
58+
}
59+
}
60+
}

mobile/src/main/java/com/android/vending/billing/IabHelper.java

Lines changed: 130 additions & 71 deletions
Large diffs are not rendered by default.

mobile/src/main/java/com/android/vending/billing/Purchase.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class Purchase {
3232
String mToken;
3333
String mOriginalJson;
3434
String mSignature;
35+
boolean mIsAutoRenewing;
3536

3637
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
3738
mItemType = itemType;
@@ -44,6 +45,7 @@ public Purchase(String itemType, String jsonPurchaseInfo, String signature) thro
4445
mPurchaseState = o.optInt("purchaseState");
4546
mDeveloperPayload = o.optString("developerPayload");
4647
mToken = o.optString("token", o.optString("purchaseToken"));
48+
mIsAutoRenewing = o.optBoolean("autoRenewing");
4749
mSignature = signature;
4850
}
4951

@@ -57,6 +59,7 @@ public Purchase(String itemType, String jsonPurchaseInfo, String signature) thro
5759
public String getToken() { return mToken; }
5860
public String getOriginalJson() { return mOriginalJson; }
5961
public String getSignature() { return mSignature; }
62+
public boolean isAutoRenewing() { return mIsAutoRenewing; }
6063

6164
@Override
6265
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }

mobile/src/main/java/com/android/vending/billing/Security.java

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@
1616
package com.android.vending.billing;
1717

1818
import android.text.TextUtils;
19+
import android.util.Base64;
1920
import android.util.Log;
2021

21-
import org.json.JSONException;
22-
import org.json.JSONObject;
23-
24-
2522
import java.security.InvalidKeyException;
2623
import java.security.KeyFactory;
2724
import java.security.NoSuchAlgorithmException;
@@ -75,17 +72,14 @@ public static boolean verifyPurchase(String base64PublicKey, String signedData,
7572
*/
7673
public static PublicKey generatePublicKey(String encodedPublicKey) {
7774
try {
78-
byte[] decodedKey = Base64.decode(encodedPublicKey);
75+
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
7976
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
8077
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
8178
} catch (NoSuchAlgorithmException e) {
8279
throw new RuntimeException(e);
8380
} catch (InvalidKeySpecException e) {
8481
Log.e(TAG, "Invalid key specification.");
8582
throw new IllegalArgumentException(e);
86-
} catch (Base64DecoderException e) {
87-
Log.e(TAG, "Base64 decoding failed.");
88-
throw new IllegalArgumentException(e);
8983
}
9084
}
9185

@@ -99,12 +93,18 @@ public static PublicKey generatePublicKey(String encodedPublicKey) {
9993
* @return true if the data and signature match
10094
*/
10195
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
102-
Signature sig;
96+
byte[] signatureBytes;
10397
try {
104-
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
98+
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
99+
} catch (IllegalArgumentException e) {
100+
Log.e(TAG, "Base64 decoding failed.");
101+
return false;
102+
}
103+
try {
104+
Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
105105
sig.initVerify(publicKey);
106106
sig.update(signedData.getBytes());
107-
if (!sig.verify(Base64.decode(signature))) {
107+
if (!sig.verify(signatureBytes)) {
108108
Log.e(TAG, "Signature verification failed.");
109109
return false;
110110
}
@@ -115,8 +115,6 @@ public static boolean verify(PublicKey publicKey, String signedData, String sign
115115
Log.e(TAG, "Invalid key specification.");
116116
} catch (SignatureException e) {
117117
Log.e(TAG, "Signature exception.");
118-
} catch (Base64DecoderException e) {
119-
Log.e(TAG, "Base64 decoding failed.");
120118
}
121119
return false;
122120
}

mobile/src/main/java/com/android/vending/billing/SkuDetails.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
* Represents an in-app product's listing details.
2323
*/
2424
public class SkuDetails {
25-
String mItemType;
26-
String mSku;
27-
String mType;
28-
String mPrice;
29-
String mTitle;
30-
String mDescription;
31-
String mJson;
25+
private final String mItemType;
26+
private final String mSku;
27+
private final String mType;
28+
private final String mPrice;
29+
private final long mPriceAmountMicros;
30+
private final String mPriceCurrencyCode;
31+
private final String mTitle;
32+
private final String mDescription;
33+
private final String mJson;
3234

3335
public SkuDetails(String jsonSkuDetails) throws JSONException {
3436
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
@@ -41,13 +43,17 @@ public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
4143
mSku = o.optString("productId");
4244
mType = o.optString("type");
4345
mPrice = o.optString("price");
46+
mPriceAmountMicros = o.optLong("price_amount_micros");
47+
mPriceCurrencyCode = o.optString("price_currency_code");
4448
mTitle = o.optString("title");
4549
mDescription = o.optString("description");
4650
}
4751

4852
public String getSku() { return mSku; }
4953
public String getType() { return mType; }
5054
public String getPrice() { return mPrice; }
55+
public long getPriceAmountMicros() { return mPriceAmountMicros; }
56+
public String getPriceCurrencyCode() { return mPriceCurrencyCode; }
5157
public String getTitle() { return mTitle; }
5258
public String getDescription() { return mDescription; }
5359

mobile/src/main/java/com/atomjack/vcfp/PlexSubscription.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ public PlexSubscription() {
7070
}
7171

7272
public void setListener(PlexSubscriptionListener _listener) {
73-
if(_listener != null)
74-
Logger.d("Setting listener to %s", _listener.getClass().getSimpleName());
7573
listener = _listener;
7674
if(_listener != null) {
7775
notificationListener = _listener;

mobile/src/main/java/com/atomjack/vcfp/VoiceControlForPlexApplication.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.app.PendingIntent;
1010
import android.content.Context;
1111
import android.content.DialogInterface;
12+
import android.content.IntentFilter;
1213
import android.content.pm.PackageInfo;
1314
import android.graphics.Bitmap;
1415
import android.net.Uri;
@@ -20,6 +21,7 @@
2021
import android.view.View;
2122
import android.widget.RemoteViews;
2223

24+
import com.android.vending.billing.IabBroadcastReceiver;
2325
import com.android.vending.billing.IabHelper;
2426
import com.android.vending.billing.IabResult;
2527
import com.android.vending.billing.Inventory;
@@ -64,6 +66,7 @@
6466
import java.security.SecureRandom;
6567
import java.util.ArrayList;
6668
import java.util.Arrays;
69+
import java.util.Date;
6770
import java.util.HashMap;
6871
import java.util.LinkedHashMap;
6972
import java.util.List;
@@ -105,6 +108,8 @@ public enum NOTIFICATION_STATUS {
105108
initializing
106109
}
107110

111+
private IabBroadcastReceiver promoReceiver;
112+
108113
public static HashMap<String, String[]> chromecastVideoOptions = new LinkedHashMap<String, String[]>();
109114

110115
private NotificationManager mNotifyMgr;
@@ -127,6 +132,7 @@ public enum NOTIFICATION_STATUS {
127132

128133
// In-app purchasing
129134
private IabHelper mIabHelper;
135+
private boolean iabHelperSetupDone = false;
130136
// TODO: Obfuscate this somehow:
131137
String base64EncodedPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlgV+Gdi4nBVn2rRqi+oVLhenzbWcEVyUf1ulhvAElEf6c8iuX3OB4JZRYVhCE690mFaYUdEb8OG8p8wT7IrQmlZ0DRfP2X9csBJKd3qB+l9y11Ggujivythvoiz+uvDPhz54O6wGmUB8+oZXN+jk9MT5Eia3BZxJDvgFcmDe/KQTTKZoIk1Qs/4PSYFP8jaS/lc71yDyRmvAM+l1lv7Ld8h69hVvKFUr9BT/20lHQGohCIc91CJvKIP5DaptbE98DAlrTxjZRRpbi+wrLGKVbJpUOBgPC78qo3zPITn6M6N0tHkv1tHkGOeyLUbxOC0wFdXj33mUldV/rp3tHnld1wIDAQAB";
132138
private boolean inventoryQueried = false;
@@ -535,22 +541,34 @@ public void onIabSetupFinished(IabResult result) {
535541
// Logger.d("Hash: %s", getEmailHash());
536542
if (!result.isSuccess()) {
537543
// Oh noes, there was a problem.
538-
Logger.d("Problem setting up in-app billing: " + result);
544+
Logger.d("Problem setting up in-app billing: %s", result);
539545
return;
540546
}
541547

542-
543-
544548
// Have we been disposed of in the meantime? If so, quit.
545549
if (mIabHelper == null) return;
546550

551+
promoReceiver = new IabBroadcastReceiver(new IabBroadcastReceiver.IabBroadcastListener() {
552+
@Override
553+
public void receivedBroadcast() {
554+
mIabHelper.queryInventoryAsync(mGotInventoryListener);
555+
}
556+
});
557+
IntentFilter broadcastFilter = new IntentFilter(IabBroadcastReceiver.ACTION);
558+
registerReceiver(promoReceiver, broadcastFilter);
559+
547560
// IAB is fully set up. Now, let's get an inventory of stuff we own.
548561
Logger.d("Setup successful. Querying inventory.");
549562
mIabHelper.queryInventoryAsync(mGotInventoryListener);
550563
}
551564
});
552565
}
553566

567+
public void refreshInAppInventory() {
568+
if(mIabHelper != null && iabHelperSetupDone)
569+
mIabHelper.queryInventoryAsync(mGotInventoryListener);
570+
}
571+
554572
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
555573
public void onQueryInventoryFinished(IabResult result, final Inventory inventory) {
556574

@@ -563,6 +581,8 @@ public void onQueryInventoryFinished(IabResult result, final Inventory inventory
563581
return;
564582
}
565583

584+
iabHelperSetupDone = true;
585+
566586
Logger.d("Query inventory was successful.");
567587

568588
// Get the price for chromecast & wear support
@@ -810,4 +830,24 @@ public void handleUncaughtException (Thread thread, Throwable e) {
810830
e.printStackTrace();
811831
prefs.put(Preferences.CRASHED, true);
812832
}
833+
834+
@Override
835+
public void onTerminate() {
836+
super.onTerminate();
837+
838+
if(promoReceiver != null)
839+
unregisterReceiver(promoReceiver);
840+
841+
if(mIabHelper != null)
842+
mIabHelper.dispose();
843+
mIabHelper = null;
844+
}
845+
846+
public int getSecondsSinceLastServerScan() {
847+
Date now = new Date();
848+
Date lastServerScan = new Date(prefs.get(Preferences.LAST_SERVER_SCAN, 0l));
849+
Logger.d("now: %s", now);
850+
Logger.d("lastServerScan: %s", lastServerScan);
851+
return (int)((now.getTime() - lastServerScan.getTime())/1000);
852+
}
813853
}

0 commit comments

Comments
 (0)