I found this library (https://github.com/leolin310148/ShortcutBadger) to implement app icon counter badge in android.
badge works when i implement in activity. but i want to implement this when push notification is received. i currently have the initialize code in onMesseageReceived() method in Firebase messaging service but its not working.
see code below:
Thanks for your help
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Logger.i("Received message");
//Shourtcut Badger
int badgeCount = 1;
ShortcutBadger.applyCount(context, badgeCount);
//parseNotification(bundle);
parseNotification(remoteMessage.getData());
if (remoteMessage.getData() != null) {
String str = remoteMessage.getData().get("notification");
String from_user = "";
String message = "";
String name = "";
int type = 0;
if (str != null && str.length() > 0) {
try {
JSONObject jobj = new JSONObject(str);
from_user = jobj.optString("from");
message = jobj.optString("message");
name = jobj.optString("name");
type = Integer.parseInt(jobj.optString("type"));
if (type == LIKE_STATUS) {
handleStatusLikeByFriendPush(jobj, "" + type);
} else if (type == PHOTO_UPDATE) {
handlePhotoUpdateByFriendPush(jobj, "" + type);
} catch (Exception e) {
e.printStackTrace();
}
} else {
message = "";//bundle.getString("message");
from_user = "";//bundle.getString("source");
if (from_user != null && from_user.contains(IMHandler.AT + IMHandler.DOMAIN)) {
from_user = from_user.substring(0, from_user.indexOf(IMHandler.AT));
}
showNotification(from_user, message, type, name);
}
}
}
public void handleStatusLikeByFriendPush(JSONObject jobj, String pushType) {
// method
}
public void handlePhotoLikeByFriendPush(JSONObject jobj, String pushType) {
//method implement
}
}
private void showNotification(String from, String body, int type, String name) {
}
}
After lots of research i understand implementation of this Shortcut badge is not working in FCM service. I Am Able to resolve this issue by simply calling a a custom service from FCM service each time push notification is received and the custom service handles the badge codes and there affter destroys itself.
This way is working perfectly.
Related
I have implemented push notifications for my chat application using FCM. My application has groups same like WhatsApp groups. New notifications will receive when chatting inside a group. I need to clear the notifications which are already read.
I found a blog for this feature. But it is not working for me. My codes are added below:
In PCL:
public interface IPushCancel
{
void CancelPush(long id);
}
In Android:
public class PushCancelService : IPushCancel
{
public void CancelPush(long id)
{
var intent = CreateIntent(id);
var pendingIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, (int)id, intent, PendingIntentFlags.CancelCurrent);
var alarmManager = GetAlarmManager();
alarmManager.Cancel(pendingIntent);
var notificationManager = NotificationManagerCompat.From(Android.App.Application.Context);
notificationManager.CancelAll();
}
private AlarmManager GetAlarmManager()
{
var alarmManager = Android.App.Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;
return alarmManager;
}
private Intent CreateIntent(long id)
{
return new Intent(Android.App.Application.Context, typeof(MainActivity /*"Here we have to put GCM Listener class"*/))
.SetAction("LocalNotifierIntent" + id);
}
}
In IOS:
public class PushCancelService : IPushCancel
{
private const string NotificationKey = "LocalNotificationKey";
public void CancelPush(long id)
{
UIApplication.SharedApplication.CancelAllLocalNotifications();
var notifications = UIApplication.SharedApplication.ScheduledLocalNotifications;
var notification = notifications.Where(n => n.UserInfo.ContainsKey(NSObject.FromObject(NotificationKey))).FirstOrDefault(n => n.UserInfo[NotificationKey].Equals(NSObject.FromObject(id)));
UIApplication.SharedApplication.CancelAllLocalNotifications();
if (notification != null)
{
UIApplication.SharedApplication.CancelLocalNotification(notification);
UIApplication.SharedApplication.CancelAllLocalNotifications();
}
}
}
Notification ID
In MainActivity I am passing the notification_id like below:
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
var myData = JsonConvert.DeserializeObject<List<webContentList>>(webContentList);
string webcontentId = myData[0].webContentDefinitionId.ToString();
try
{
//passing groupid and notification id with a blank space
MessagingCenter.Send<object, string>(this, "webcontentId", webcontentId + " " + message.MessageId);
}
catch (Exception exc)
{
Console.WriteLine("exception:>>" + exc);
}
}
catch (Exception ex)
{
Console.WriteLine("Error:>>" + ex);
}
}
In IOS I am passing the notification_id like below from AppDelegate.cs:
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
var myData = JsonConvert.DeserializeObject<List<webContentList>>(userInfo[new NSString("webContentList")] as NSString);
string notificationId = (userInfo[new NSString("gcm.message_id")] as NSString).ToString();
if (UIApplication.SharedApplication.ApplicationState.Equals(UIApplicationState.Active))
{
//App is in foreground. Act on it.
if (myData != null)
{
string webcontentId = myData[0].webContentDefinitionId.ToString();
try
{
//Passing notification id and group id from here through MessagingCenter
MessagingCenter.Send<object, string>(this, "webcontentId", webcontentId + " " + notificationId);
}
catch (Exception exc)
{
Console.WriteLine("messagecenterexception:>>" + exc);
}
}
}
else
{
MessagingCenter.Send<object, List<webContentList>>(this, "messagedata", myData);
}
}
Finally, I am calling the notification clear DependencyService from my group page like below:
MessagingCenter.Subscribe<object, string>(this, "webcontentId", (object obj, string notifWebcontentId) => {
if (notifWebcontentId != "")
{
string[] multiArray = notifWebcontentId.Split(' ');
//Checking foreground group id and notification group id are same
if (multiArray[0] == webcontentId)
{
DependencyService.Get<IPushCancel>().CancelPush(long.Parse(multiArray[1]));
RefreshMessageList();
}
}
});
Observations
IOS
No error or exception is showing in ios, but code execution is not going inside of the following `if.
if (notification != null)
{
UIApplication.SharedApplication.CancelLocalNotification(notification);
UIApplication.SharedApplication.CancelAllLocalNotifications();
}
Android
Getting TargetInvocationException when sending notification id from Mainactitivity.
Exception Details:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.FormatException: Input string was not in a correct format.
Please help me to solve implement this feature. I need to clear the read message notifications automatically. Otherwise, the user will struggle by a lot of notifications.
I am aware of DelayInformationManager class, and I know this is the class to achieve this. However, I dont know how to use it, how to specify the Jid destination, how to actually send it and so on.
Could anyone provide me a short example demonstrating its usage?
The using following you can achieve Delayed Delivery using smack lib.
Send Delayed Delivery Receipt
public void sendReceipt(String toJID, String sender, final String stanzaID, final String id, final long millis, Message.Type msgType) {
if(isConnected()){
Message ack = null; //2017-11-17T15:21:50.063+00:00
try {
String fromJidGroup = toJID;
if(msgType == Message.Type.groupchat){
fromJidGroup = ActivityHelper.createJid(sender) ;
}else{
fromJidGroup = toJID;
}
ack = new Message(JidCreate.from(fromJidGroup), Message.Type.chat); //msgType
ack.addExtension(new DeliveryReceipt(id));
} catch (XmppStringprepException e) {
e.printStackTrace();
}
if(millis!=0) {
DelayInformation delay = new DelayInformation(new Date(millis));
ack.addExtension(delay);
}
if(stanzaID!=null){
ack.setStanzaId(stanzaID);
}
try {
if(connection.isSmEnabled() && connection!=null) {
//addStanzaIdAcknowledgedListener send successfully Receipt or not in server
connection.addStanzaIdAcknowledgedListener(ack.getStanzaId(), new StanzaListener() {
#Override
public void processPacket(Stanza stanza) throws SmackException.NotConnectedException, InterruptedException {
if(registerXmppListener!=null){
registerXmppListener.onStanzaIdAcknowledgedReceived(stanza);
}
}
});
}
connection.sendStanza(ack);
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException | StreamManagementException.StreamManagementNotEnabledException e) {
e.printStackTrace();
}
}
}
Receive Delayed Delivery , First register ReceiptReceivedListener with xmpp connection
private ReceiptReceivedListener mReceiptReceivedListener;
mReceiptReceivedListener = new ReceiptReceivedListener() {
#Override
public void onReceiptReceived(Jid from, Jid to, String rec_id, Stanza stanza) {
if(registerXmppListener!=null) {
Log.d("onReceipt","onReceipt stanza : " + stanza.toXML());
registerXmppListener.onDeliveryStatusReceived(from.toString(),to.toString(),rec_id,stanza);
}
}
};
mDeliveryReceiptManager.addReceiptReceivedListener(mReceiptReceivedListener);
onDeliveryStatusReceived Listener
public void changeMessageDeliveryStatus(String from, String to, String rec_id, Stanza stanza){
if(stanza instanceof Message) {
Message msg = (Message) stanza;
String jid = "";
if(msg.getType().equals(Message.Type.chat)){
jid = ActivityHelper.getBareJID(from);
}else if(msg.getType().equals(Message.Type.groupchat)){
jid = ActivityHelper.getSenderFromGroup(from);
}
String sender="";
long date = System.currentTimeMillis();
String stanza_id=stanza.getStanzaId();
int chat_type = 0;
int isPrivate = ChatConstants.ISPRIVATE_NO;
DelayInformation timestamp = (DelayInformation)msg.getExtension("delay", "urn:xmpp:delay");
if (timestamp == null)
timestamp = (DelayInformation)msg.getExtension("x", "jabber:x:delay");
if (timestamp != null)
date = timestamp.getStamp().getTime();
}
}
1.When the device receives push how to automate in Appium so that it will open the push notification and read the text.
i am using following code to tap notification with some needed text. you can use it and do whatever needed (did not update code it long time. some parts can be re-written much better. but still working with every phone i tested):
Page object first:
public class NativeNotificationPage extends Page {
#AndroidFindBy(id= "com.android.systemui:id/notification_panel")
private List<AndroidElement> notificationPanel;
//settings data
#HowToUseLocators(androidAutomation = LocatorGroupStrategy.ALL_POSSIBLE)
#AndroidFindBy(id = "com.android.systemui:id/clear_all_button")
#AndroidFindBy(id = "com.android.systemui:id/dismiss_text")
private List<AndroidElement> clearAllBtn;
//last items
#AndroidFindBy(id = "com.android.systemui:id/latestItems")
private List<AndroidElement> lastItemsContainer;
//events data
#AndroidFindBy(id = "android:id/status_bar_latest_event_content")
private List<AndroidElement> lastItemsContent;
#AndroidFindBy(id = "android:id/title")
private List<AndroidElement> itemTitle;
String itemTitle_Locator_Text = "android:id/title";
#HowToUseLocators(androidAutomation = LocatorGroupStrategy.ALL_POSSIBLE)
#AndroidFindBy (id = "android:id/big_text")
#AndroidFindBy (id = "android:id/text")
private List<AndroidElement> itemText;
String itemText_Phone_Locator_Text = "android:id/text";
String itemText_Tablet_Locator_Text = "android:id/big_text";
#AndroidFindBy(id = "android:id/time")
private List<AndroidElement> itemTime;
public NativeNotificationPage(WebDriver driver) {
super(driver);
}
public boolean isNativeNotificationPage() {
System.out.println(" check 'Notification' Screen loaded");
boolean bool;
setFastLookTiming();
bool = !notificationPanel.isEmpty();
setDefaultTiming();
return bool;
}
public boolean isNativeNotificationPage(int sec) {
System.out.println(" check 'Notification' Screen loaded");
boolean bool;
setLookTiming(sec);
bool = !notificationPanel.isEmpty();
setDefaultTiming();
return bool;
}
public boolean isClearAllBtnLoaded() {
System.out.println(" check 'Clear' button loaded");
boolean bool;
setLookTiming(3);
bool = !clearAllBtn.isEmpty();
setDefaultTiming();
return bool;
}
public int getLastItemsContentSize() {return lastItemsContent.size();}
public String getItemTitle(int num) {
try {
return lastItemsContent.get(num).findElement(MobileBy.className("android.widget.TextView")).getText();
} catch (Exception e) {
return null;
}
}
public String getItemText(int num) {
if (isPhone()) {
List<MobileElement> item = lastItemsContent.get(num).findElements(MobileBy.className("android.widget.TextView"));
String tmp = null;
for (int i=1;i<item.size();i++) {
if (tmp == null)
tmp = item.get(i).getText();
else
tmp = tmp + "," +item.get(i).getText();
}
return tmp;
} else {
setLookTiming(3);
if (lastItemsContent.get(num).findElements(MobileBy.id(itemText_Tablet_Locator_Text)).isEmpty()) {
setDefaultTiming();
return lastItemsContent.get(num).findElement(MobileBy.id(itemText_Phone_Locator_Text)).getText();
} else {
setDefaultTiming();
return lastItemsContent.get(num).findElement(MobileBy.id(itemText_Tablet_Locator_Text)).getText();
}
}
}
public boolean tapClearAllBtn() {
System.out.println(" tap 'Clear' button");
return tapElementInList_Android(clearAllBtn, 0);
}
public boolean tapNotificationByNum(int num) {
return tapElementInList_Android(lastItemsContent, num);
}
}
Now in test code:
public Boolean tapNotificationByTitleAndText_Android(String neededTitle, String neededText, AppiumDriver driver) {
((AndroidDriver) driver).openNotifications();
sleep(1);
nativeNotificationPage = new NativeNotificationPage(driver);
assertTrue("Native notification page is NOT loaded", nativeNotificationPage.isNativeNotificationPage());
int itemsListSize = nativeNotificationPage.getLastItemsContentSize();
System.out.println(" number of notifications is: " + itemsListSize);
assertTrue("Number of notifications is 0", itemsListSize != 0);
String title, text;
for (int i = 0; i < itemsListSize; i++) {
title = nativeNotificationPage.getItemTitle(i);
text = nativeNotificationPage.getItemText(i);
System.out.println(" notification title is: " + title);
System.out.println(" notification text is: " + text);
.....
return nativeNotificationPage.tapNotificationByNum(i);
}
return false;
}
You can open notifications pane using following inbuilt AndroidDriver's method.
driver.openNotifications();
If you want to scroll down to a specific Push, use following code -
WebElement element = driver.findElement(By.name("Element Name"));
HashMap<String, String> arguments = new HashMap<String, String>();
arguments.put("element", element.getId());
(JavascriptExecutor)driver.executeScript("mobile: scrollTo", arguments);
Note - driver is of type AndroidDriver. If not, then you should type-cast it.
Go through this library for better understanding of functions - https://github.com/appium/java-client
First things is, what type of push notification you are use.
two type of push notification firebase offer :
1.Notification
2.Data payload.
Following the last section in the GCM: Getting Started guide, there's some book-keeping to be done after receiving the results.
Quoting from the guide:
It's now necessary to parse the result and take the proper action in the following cases:
If the message was created but the result returned a canonical registration ID, it's necessary to replace the current registration
ID with the canonical one.
If the returned error is NotRegistered, it's necessary to remove that registration ID, because the application was uninstalled from
the device.
Here's a code snippet that handles these 2 conditions:
if (result.getMessageId() != null) {
String canonicalRegId = result.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// same device has more than on registration ID: update database
}
} else {
String error = result.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister database
}
}
The guide above refers to a single result, and not to the multicast case.
I'm not sure how to handle the multicast case:
ArrayList<String> devices = new ArrayList<String>();
for (String d : relevantDevices) {
devices.add(d);
}
Sender sender = new Sender(myApiKey);
Message message = new Message.Builder().addData("hello", "world").build();
try {
MulticastResult result = sender.send(message, devices, 5);
for (Result r : result.getResults()) {
if (r.getMessageId() != null) {
String canonicalRegId = r.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// same device has more than on registration ID: update database
// BUT WHICH DEVICE IS IT?
}
} else {
String error = r.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister database
// BUT WHICH DEVICE IS IT?
}
}
}
} catch (IOException ex) {
Log.err(TAG, "sending message failed", ex);
}
I submit a list of devices, and receive back a list of results.
The Result object doesn't contain the registration id, but only a canonical id if the first is obsolete.
It is undocumented if the two lists are co-related (ie. preserves order and size).
How can I be sure which result refer to which device?
-- UPDATE
I've pasted a snippet of the solution in a separate answer below
The results are in the order of your registration_id array that you sent to GCM server. e.g. if your registration_ids are:
[id1, id4, id7, id8]
Then the results array you get will have same order for id1, id4, id7, and id8.
You just need to parse each result accordingly, e.g. if the 2nd result has 'message_id' and 'registration_id' of 'id9', you know 'id4' is now obsolete and should be replaced by id9.
For the readers convenience, here is a snippet that handles response for multiple devices
public void sendMessageToMultipleDevices(String key, String value, ArrayList<String> devices) {
Sender sender = new Sender(myApiKey);
Message message = new Message.Builder().addData(key, value).build();
try {
MulticastResult result = sender.send(message, devices, 5);
MTLog.info(TAG, "result " + result.toString());
for (int i = 0; i < result.getTotal(); i++) {
Result r = result.getResults().get(i);
if (r.getMessageId() != null) {
String canonicalRegId = r.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// devices.get(i) has more than on registration ID: update database
}
} else {
String error = r.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from devices.get(i) - unregister database
}
}
}
} catch (IOException ex) {
MTLog.err(TAG, "sending message failed", ex);
}
}
This solution is done by google developer sample GCM Demo application
note the asyncSend for multicasting handle
List<GcmUsers> devices=SearchRegisterdDevicesByCourseCommand.execute(instructorId, courseId);
String status;
if ( devices.equals(Collections.<GcmUsers>emptyList())) {
status = "Message ignored as there is no device registered!";
} else {
// NOTE: check below is for demonstration purposes; a real application
// could always send a multicast, even for just one recipient
if (devices.size() == 1) {
// send a single message using plain post
GcmUsers gcmUsers = devices.get(0);
Message message = new Message.Builder().build();
Result result = sender.send(message, gcmUsers.getGcmRegid(), 5);
status = "Sent message to one device: " + result;
} else {
// send a multicast message using JSON
// must split in chunks of 1000 devices (GCM limit)
int total = devices.size();
List<String> partialDevices = new ArrayList<String>(total);
int counter = 0;
int tasks = 0;
for (GcmUsers device : devices) {
counter++;
partialDevices.add(device.getGcmRegid());
int partialSize = partialDevices.size();
if (partialSize == MULTICAST_SIZE || counter == total) {
asyncSend(partialDevices);
partialDevices.clear();
tasks++;
}
}
status = "Asynchronously sending " + tasks + " multicast messages to " +
total + " devices";
}
}
req.setAttribute(HomeServlet.ATTRIBUTE_STATUS, status.toString());
private void asyncSend(List<String> partialDevices) {
// make a copy
final List<String> devices = new ArrayList<String>(partialDevices);
threadPool.execute(new Runnable() {
public void run() {
Message message = new Message.Builder().build();
MulticastResult multicastResult;
try {
multicastResult = sender.send(message, devices, 5);
} catch (IOException e) {
logger.log(Level.SEVERE, "Error posting messages", e);
return;
}
List<Result> results = multicastResult.getResults();
// analyze the results
for (int i = 0; i < devices.size(); i++) {
String regId = devices.get(i);
Result result = results.get(i);
String messageId = result.getMessageId();
if (messageId != null) {
logger.fine("Succesfully sent message to device: " + regId +
"; messageId = " + messageId);
String canonicalRegId = result.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// same device has more than on registration id: update it
logger.info("canonicalRegId " + canonicalRegId);
Datastore.updateRegistration(regId, canonicalRegId);
}
} else {
String error = result.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister it
logger.info("Unregistered device: " + regId);
Datastore.unregister(regId);
} else {
logger.severe("Error sending message to " + regId + ": " + error);
}
}
}
}});
}
I am adding a in app purchase in my coding, it's working well while purchase but gives error and application closes when I try to add Restore_Transaction code when application is removed and installed again, I have added below coding
in onCreate i wrote
startService(new Intent(mContext, BillingService.class));
BillingHelper.setCompletedHandler(mTransactionHandler);
if (BillingHelper.isBillingSupported()) {
BillingHelper.restoreTransactionInformation(BillingSecurity
.generateNonce());
}
and then i called handler using
public Handler mTransactionHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (BillingHelper.latestPurchase.isPurchased()) {
showItem();
}
};
};
private void showItem() {
purchased = Purchased.getPurchaseInfo(getApplicationContext());
if (purchased == null) {
Date d = new Date();
Toast.makeText(getApplicationContext(), "--- Upgrated ---",
Toast.LENGTH_LONG).show();
purchased = new Purchased(getApplicationContext());
purchased.isPurchased = 1;
purchased.purchasedDate = d.getTime();
purchased.save();
Intent intent = new Intent(ActorGenieActivity.this,
SplashScreen.class);
startActivity(intent);
}
}
I found the answer to my question, thanx to anddev
You have to check for purchases not to be null
public static void verifyPurchase(String signedData, String signature) {
ArrayList<VerifiedPurchase> purchases = BillingSecurity.verifyPurchase(
signedData, signature);
if (purchases != null && !purchases.isEmpty()) {
latestPurchase = purchases.get(0);
confirmTransaction(new String[] { latestPurchase.notificationId });
if (mCompletedHandler != null) {
mCompletedHandler.sendEmptyMessage(0);
} else {
Log
.e(
TAG,
"verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");
}
}
}
and in Confirm_Notification u hav to check for
if (notifyIds[0] != null)
Follow this:
confirmTransaction(new String[] { latestPurchase.notificationId });
here and do this:
protected static void confirmTransaction(String[] notifyIds) {
if (amIDead()) {
return;
}
// there isn't a notifyid then this was the restore transaction call and this should be skipped
if (notifyIds[0] != null){
Log.i(TAG, "confirmTransaction()");
Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
......
......
}
Works like a charm form me.. Thanks Guys...
You can use the below code to get purchase history:
public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData,
String signature) {
if (signedData == null) {
//Log.e(TAG, "data is null");
return null;
}
if (Constans.DEBUG) {
//Log.i(TAG, "signedData: " + signedData);
}
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {
/**
* Compute your public key (that you got from the Android Market
* publisher site).
*
* Instead of just storing the entire literal string here embedded
* in the program, construct the key at runtime from pieces or use
* bit manipulation (for example, XOR with some other string) to
* hide the actual key. The key itself is not secret information,
* but we don't want to make it easy for an adversary to replace the
* public key with one of their own and then fake messages from the
* server.
*
* Generally, encryption keys / passwords should only be kept in
* memory long enough to perform the operation they need to perform.
*/
String base64EncodedPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKgldGQPL/xV9WKLmY62UVgEm7gsPI/T/nQxRKpYN17m8Sq3gO9nWD17wXew4oNaHmMAmArS7s7eFi3Z+XiyWil1iZvEOdBOdZD502BzujPoBa4Fu9eITPBO9tzBEdvNLXf8amnsRj53TA4bcxB2O6OcXrQIv3t3n5Dg5Nn+rJpoKSNUv7NEzJagG/2NhyjIysAObbvQ5SBQ5NgRtZlvhsTeQJPMLhRAoRcTK/+47VkhrxM3PppeGjoNRryn6d+RhMjs/nydvoQtP2V76UcUu4m+daDnK3PxOnwLt50hNtQhNf3VgixVrSKfHUWp240uEz9MHstjj8BWPH9BFF/TewIDAQAB";
PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
verified = Security.verify(key, signedData, signature);
if (!verified) {
//Log.w(TAG, "signature does not match data.");
return null;
}
}
JSONObject jObject;
JSONArray jTransactionsArray = null;
int numTransactions = 0;
long nonce = 0L;
try {
jObject = new JSONObject(signedData);
// The nonce might be null if the user backed out of the buy page.
nonce = jObject.optLong("nonce");
jTransactionsArray = jObject.optJSONArray("orders");
if (jTransactionsArray != null) {
numTransactions = jTransactionsArray.length();
}
} catch (JSONException e) {
return null;
}
if (!Security.isNonceKnown(nonce)) {
//Log.w(TAG, "Nonce not found: " + nonce);
return null;
}
ArrayList<VerifiedPurchase> purchases = new ArrayList<VerifiedPurchase>();
try {
for (int i = 0; i < numTransactions; i++) {
JSONObject jElement = jTransactionsArray.getJSONObject(i);
int response = jElement.getInt("purchaseState");
PurchaseState purchaseState = PurchaseState.valueOf(response);
String productId = jElement.getString("productId");
String packageName = jElement.getString("packageName");
long purchaseTime = jElement.getLong("purchaseTime");
String orderId = jElement.optString("orderId", "");
String notifyId = null;
if (jElement.has("notificationId")) {
notifyId = jElement.getString("notificationId");
}
String developerPayload = jElement.optString(
"developerPayload", null);
// If the purchase state is PURCHASED, then we require a
// verified nonce.
if (purchaseState == PurchaseState.PURCHASED && !verified) {
continue;
}
purchases.add(new VerifiedPurchase(purchaseState, notifyId,
productId, orderId, purchaseTime, developerPayload));
}
} catch (JSONException e) {
//Log.e(TAG, "JSON exception: ", e);
return null;
}
removeNonce(nonce);
return purchases;
}
You can call this method from the below method in BillingService class:
private void purchaseStateChanged(int startId, String signedData,
String signature) {
ArrayList<Security.VerifiedPurchase> purchases;
purchases = Security.verifyPurchase(signedData, signature);
if (purchases == null) {
return;
}
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
notifyList.add(vp.notificationId);
}
ResponseHandler.purchaseResponse(this, vp.purchaseState,
vp.productId, vp.orderId, vp.purchaseTime,
vp.developerPayload);
}
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList
.size()]);
confirmNotifications(startId, notifyIds);
}
}