From 3d24d20194bfda4e4e00c8b3315c2a3acc9a5843 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 21 Apr 2015 10:55:09 +0200 Subject: [PATCH 01/56] Started in-app purchase --- AndroidManifest.xml | 12 +- res/layout/in_app_purchasable.xml | 31 ++ res/layout/in_app_store.xml | 26 ++ res/values/non_localizable_custom.xml | 2 +- res/xml/preferences.xml | 4 + .../vending/billing/IInAppBillingService.aidl | 144 ++++++++ src/org/linphone/LinphonePreferences.java | 8 +- src/org/linphone/LinphoneUtils.java | 2 - src/org/linphone/SettingsFragment.java | 10 + .../purchase/InAppPurchaseActivity.java | 97 +++++ .../purchase/InAppPurchaseHelper.java | 331 ++++++++++++++++++ .../purchase/InAppPurchaseListener.java | 46 +++ src/org/linphone/purchase/Purchasable.java | 61 ++++ 13 files changed, 768 insertions(+), 6 deletions(-) create mode 100644 res/layout/in_app_purchasable.xml create mode 100644 res/layout/in_app_store.xml create mode 100644 src/com/android/vending/billing/IInAppBillingService.aidl create mode 100644 src/org/linphone/purchase/InAppPurchaseActivity.java create mode 100644 src/org/linphone/purchase/InAppPurchaseHelper.java create mode 100644 src/org/linphone/purchase/InAppPurchaseListener.java create mode 100644 src/org/linphone/purchase/Purchasable.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6776daa4e..038571cd8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="2400" android:installLocation="auto"> @@ -40,6 +40,8 @@ + + @@ -132,6 +134,14 @@ + + + + + + diff --git a/res/layout/in_app_purchasable.xml b/res/layout/in_app_purchasable.xml new file mode 100644 index 000000000..45a33e7b8 --- /dev/null +++ b/res/layout/in_app_purchasable.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/in_app_store.xml b/res/layout/in_app_store.xml new file mode 100644 index 000000000..d6ec049d1 --- /dev/null +++ b/res/layout/in_app_store.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/res/values/non_localizable_custom.xml b/res/values/non_localizable_custom.xml index de0d63ee6..16fe84ca0 100644 --- a/res/values/non_localizable_custom.xml +++ b/res/values/non_localizable_custom.xml @@ -2,7 +2,7 @@ false - + true 622464153529 diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 197455775..7e7af2c14 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -13,6 +13,10 @@ android:title="@string/setup_title" android:key="@string/setup_key"/> + + diff --git a/src/com/android/vending/billing/IInAppBillingService.aidl b/src/com/android/vending/billing/IInAppBillingService.aidl new file mode 100644 index 000000000..2a492f784 --- /dev/null +++ b/src/com/android/vending/billing/IInAppBillingService.aidl @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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. + */ + +package com.android.vending.billing; + +import android.os.Bundle; + +/** + * InAppBillingService is the service that provides in-app billing version 3 and beyond. + * This service provides the following features: + * 1. Provides a new API to get details of in-app items published for the app including + * price, type, title and description. + * 2. The purchase flow is synchronous and purchase information is available immediately + * after it completes. + * 3. Purchase information of in-app purchases is maintained within the Google Play system + * till the purchase is consumed. + * 4. An API to consume a purchase of an inapp item. All purchases of one-time + * in-app items are consumable and thereafter can be purchased again. + * 5. An API to get current purchases of the user immediately. This will not contain any + * consumed purchases. + * + * All calls will give a response code with the following possible values + * RESULT_OK = 0 - success + * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog + * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested + * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase + * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API + * RESULT_ERROR = 6 - Fatal error during the API action + * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned + * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned + */ +interface IInAppBillingService { + /** + * Checks support for the requested billing API version, package and in-app type. + * Minimum API version supported by this interface is 3. + * @param apiVersion the billing version which the app is using + * @param packageName the package name of the calling app + * @param type type of the in-app item being purchased "inapp" for one-time purchases + * and "subs" for subscription. + * @return RESULT_OK(0) on success, corresponding result code on failures + */ + int isBillingSupported(int apiVersion, String packageName, String type); + + /** + * Provides details of a list of SKUs + * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle + * with a list JSON strings containing the productId, price, title and description. + * This API can be called with a maximum of 20 SKUs. + * @param apiVersion billing API version that the Third-party is using + * @param packageName the package name of the calling app + * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "DETAILS_LIST" with a StringArrayList containing purchase information + * in JSON format similar to: + * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00", + * "title : "Example Title", "description" : "This is an example description" }' + */ + Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); + + /** + * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, + * the type, a unique purchase token and an optional developer payload. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param sku the SKU of the in-app item as published in the developer console + * @param type the type of the in-app item ("inapp" for one-time purchases + * and "subs" for subscription). + * @param developerPayload optional argument to be sent back with the purchase information + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "BUY_INTENT" - PendingIntent to start the purchase flow + * + * The Pending intent should be launched with startIntentSenderForResult. When purchase flow + * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. + * If the purchase is successful, the result data will contain the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "INAPP_PURCHASE_DATA" - String in JSON format similar to + * '{"orderId":"12999763169054705758.1371079406387615", + * "packageName":"com.example.app", + * "productId":"exampleSku", + * "purchaseTime":1345678900000, + * "purchaseToken" : "122333444455555", + * "developerPayload":"example developer payload" }' + * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that + * was signed with the private key of the developer + * TODO: change this to app-specific keys. + */ + Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, + String developerPayload); + + /** + * Returns the current SKUs owned by the user of the type and package name specified along with + * purchase information and a signature of the data to be validated. + * This will return all SKUs that have been purchased in V3 and managed items purchased using + * V1 and V2 that have not been consumed. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param type the type of the in-app items being requested + * ("inapp" for one-time purchases and "subs" for subscription). + * @param continuationToken to be set as null for the first call, if the number of owned + * skus are too many, a continuationToken is returned in the response bundle. + * This method can be called again with the continuation token to get the next set of + * owned skus. + * @return Bundle containing the following key-value pairs + * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on + * failure as listed above. + * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs + * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information + * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures + * of the purchase information + * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the + * next set of in-app purchases. Only set if the + * user has more owned skus than the current list. + */ + Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); + + /** + * Consume the last purchase of the given SKU. This will result in this item being removed + * from all subsequent responses to getPurchases() and allow re-purchase of this item. + * @param apiVersion billing API version that the app is using + * @param packageName package name of the calling app + * @param purchaseToken token in the purchase information JSON that identifies the purchase + * to be consumed + * @return 0 if consumption succeeded. Appropriate error values for failures. + */ + int consumePurchase(int apiVersion, String packageName, String purchaseToken); +} diff --git a/src/org/linphone/LinphonePreferences.java b/src/org/linphone/LinphonePreferences.java index bdb89344a..1e2bc7216 100644 --- a/src/org/linphone/LinphonePreferences.java +++ b/src/org/linphone/LinphonePreferences.java @@ -1145,10 +1145,14 @@ public class LinphonePreferences { } public void contactsMigrationDone(){ - getConfig().setBool("app", "contacts_migration_done",true); + getConfig().setBool("app", "contacts_migration_done", true); } public boolean isContactsMigrationDone(){ - return getConfig().getBool("app", "contacts_migration_done",false); + return getConfig().getBool("app", "contacts_migration_done", false); + } + + public String getInAppPurchaseValidatingServerUrl() { + return getConfig().getString("in-app-purchase", "server_url", null); } } diff --git a/src/org/linphone/LinphoneUtils.java b/src/org/linphone/LinphoneUtils.java index 177d11534..be4e956dd 100644 --- a/src/org/linphone/LinphoneUtils.java +++ b/src/org/linphone/LinphoneUtils.java @@ -36,7 +36,6 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCore; @@ -48,7 +47,6 @@ import org.linphone.mediastream.Version; import org.linphone.mediastream.video.capture.hwconf.Hacks; import android.app.Activity; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; diff --git a/src/org/linphone/SettingsFragment.java b/src/org/linphone/SettingsFragment.java index 8c0fc659d..f8949173e 100644 --- a/src/org/linphone/SettingsFragment.java +++ b/src/org/linphone/SettingsFragment.java @@ -35,6 +35,7 @@ import org.linphone.core.PayloadType; import org.linphone.mediastream.Log; import org.linphone.mediastream.Version; import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration; +import org.linphone.purchase.InAppPurchaseActivity; import org.linphone.setup.SetupActivity; import org.linphone.ui.LedPreference; import org.linphone.ui.PreferencesListFragment; @@ -56,6 +57,7 @@ import android.preference.PreferenceScreen; */ public class SettingsFragment extends PreferencesListFragment { private static final int WIZARD_INTENT = 1; + private static final int STORE_INTENT = 2; private LinphonePreferences mPrefs; private Handler mHandler = new Handler(); private LinphoneCoreListenerBase mListener; @@ -137,6 +139,14 @@ public class SettingsFragment extends PreferencesListFragment { return true; } }); + findPreference("in_app_store").setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(LinphoneService.instance(), InAppPurchaseActivity.class); + startActivityForResult(intent, STORE_INTENT); + return true; + } + }); } // Sets listener for each preference to update the matching value in linphonecore diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java new file mode 100644 index 000000000..78292e2e2 --- /dev/null +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -0,0 +1,97 @@ +package org.linphone.purchase; +/* +InAppPurchaseListener.java +Copyright (C) 2015 Belledonne Communications, Grenoble, France + +This program 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 2 +of the License, or (at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +import java.util.ArrayList; + +import org.linphone.R; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * @author Sylvain Berfini + */ +public class InAppPurchaseActivity extends Activity implements InAppPurchaseListener, OnClickListener { + private InAppPurchaseHelper inAppPurchaseHelper; + private LinearLayout purchasableItems; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + inAppPurchaseHelper = new InAppPurchaseHelper(this, this); + + setContentView(R.layout.in_app_store); + purchasableItems = (LinearLayout) findViewById(R.id.purchasable_items); + } + + @Override + protected void onDestroy() { + inAppPurchaseHelper.destroy(); + super.onDestroy(); + } + + @Override + public void onServiceAvailableForQueries() { + inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); + } + + @Override + public void onAvailableItemsForPurchaseQueryFinished(ArrayList items) { + purchasableItems.removeAllViews(); + + for (Purchasable item : items) { + View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItems); + TextView text = (TextView) layout.findViewById(R.id.text); + text.setText(item.getTitle() + " (" + item.getPrice() + ")"); + ImageView image = (ImageView) layout.findViewById(R.id.image); + image.setTag(item); + image.setOnClickListener(this); + } + } + + @Override + public void onPurchasedItemsQueryFinished(ArrayList items) { + + } + + @Override + public void onPurchasedItemConfirmationQueryFinished(Purchasable item) { + + } + + @Override + public void onClick(View v) { + Purchasable item = (Purchasable) v.getTag(); + inAppPurchaseHelper.purchaseItemAsync(item.getId(), "sylvain@sip.linphone.org"); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data); + } +} diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java new file mode 100644 index 000000000..52b9d1663 --- /dev/null +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -0,0 +1,331 @@ +package org.linphone.purchase; +/* +InAppPurchaseHelper.java +Copyright (C) 2015 Belledonne Communications, Grenoble, France + +This program 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 2 +of the License, or (at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; + +import org.json.JSONException; +import org.json.JSONObject; +import org.linphone.LinphonePreferences; +import org.linphone.mediastream.Log; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender.SendIntentException; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.vending.billing.IInAppBillingService; + +import de.timroes.axmlrpc.XMLRPCCallback; +import de.timroes.axmlrpc.XMLRPCClient; +import de.timroes.axmlrpc.XMLRPCException; +import de.timroes.axmlrpc.XMLRPCServerException; + +/** + * @author Sylvain Berfini + */ +public class InAppPurchaseHelper { + public static final int API_VERSION = 3; + public static final String TEST_ITEM = "android.test.purchased"; + public static final int ACTIVITY_RESULT_CODE_PURCHASE_ITEM = 11089; + + public static final String SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST"; + public static final String SKU_DETAILS_LIST = "DETAILS_LIST"; + public static final String SKU_DETAILS_PRODUCT_ID = "productId"; + public static final String SKU_DETAILS_PRICE = "price"; + public static final String SKU_DETAILS_TITLE = "title"; + public static final String SKU_DETAILS_DESC = "description"; + + public static final String ITEM_TYPE_INAPP = "inapp"; + public static final String ITEM_TYPE_SUBS = "subs"; + + public static final int RESPONSE_RESULT_OK = 0; + public static final String RESPONSE_CODE = "RESPONSE_CODE"; + public static final String RESPONSE_BUY_INTENT = "BUY_INTENT"; + public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA"; + public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE"; + public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST"; + public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST"; + public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST"; + public static final String RESPONSE_INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN"; + + public static final String PURCHASE_DETAILS_PRODUCT_ID = "productId"; + public static final String PURCHASE_DETAILS_ORDER_ID = "orderId"; + public static final String PURCHASE_DETAILS_AUTO_RENEWING = "autoRenewing"; + public static final String PURCHASE_DETAILS_PURCHASE_TIME = "purchaseTime"; + public static final String PURCHASE_DETAILS_PURCHASE_STATE = "purchaseState"; + public static final String PURCHASE_DETAILS_PAYLOAD = "developerPayload"; + public static final String PURCHASE_DETAILS_PURCHASE_TOKEN = "purchaseToken"; + + private Context mContext; + private InAppPurchaseListener mListener; + private IInAppBillingService mService; + private ServiceConnection mServiceConn; + private Handler mHandler = new Handler(); + + public InAppPurchaseHelper(Activity context, InAppPurchaseListener listener) { + mContext = context; + mListener = listener; + mServiceConn = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) { + mService = null; + Log.d("[In-app purchase] service disconnected"); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mService = IInAppBillingService.Stub.asInterface(service); + String packageName = mContext.getPackageName(); + try { + int response = mService.isBillingSupported(API_VERSION, packageName, ITEM_TYPE_SUBS); + if (response != RESPONSE_RESULT_OK) { + Log.e("[In-app purchase] Error: Subscriptions aren't supported!"); + } else { + Log.d("[In-app purchase] service connected and subsciptions are available"); + mListener.onServiceAvailableForQueries(); + } + } catch (RemoteException e) { + Log.e(e); + } + } + }; + + Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); + serviceIntent.setPackage("com.android.vending"); + if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) { + boolean ok = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); + if (!ok) { + Log.e("[In-app purchase] Error: Bind service failed"); + } else { + Log.d("[In-app purchase] service bound"); + } + } else { + Log.e("[In-app purchase] Error: Billing service unavailable on device."); + } + } + + private ArrayList getAvailableItemsForPurchase() { + ArrayList products = new ArrayList(); + ArrayList skuList = new ArrayList (); + skuList.add(TEST_ITEM); + Bundle querySkus = new Bundle(); + querySkus.putStringArrayList(SKU_DETAILS_ITEM_LIST, skuList); + + Bundle skuDetails = null; + try { + skuDetails = mService.getSkuDetails(API_VERSION, mContext.getPackageName(), ITEM_TYPE_SUBS, querySkus); + } catch (RemoteException e) { + Log.e(e); + } + + if (skuDetails != null) { + int response = skuDetails.getInt(RESPONSE_CODE); + if (response == RESPONSE_RESULT_OK) { + Log.d("[In-app purchase] response is OK"); + ArrayList responseList = skuDetails.getStringArrayList(SKU_DETAILS_LIST); + for (String thisResponse : responseList) { + try { + JSONObject object = new JSONObject(thisResponse); + String id = object.getString(SKU_DETAILS_PRODUCT_ID); + String price = object.getString(SKU_DETAILS_PRICE); + String title = object.getString(SKU_DETAILS_TITLE); + String desc = object.getString(SKU_DETAILS_DESC); + Log.d("[In-app purchase] found purchasable " + title + " (" + desc + ") for " + price + " with id " + id); + + Purchasable purchasable = new Purchasable(id).setTitle(title).setDescription(desc).setPrice(price); + products.add(purchasable); + } catch (JSONException e) { + Log.e(e); + } + } + } else { + Log.e("[In-app purchase] Error: responde code is not ok: " + response); + } + } + + return products; + } + + public void getAvailableItemsForPurchaseAsync() { + new Thread(new Runnable() { + public void run() { + final ArrayList items = getAvailableItemsForPurchase(); + if (mHandler != null && mListener != null) { + mHandler.post(new Runnable() { + public void run() { + mListener.onAvailableItemsForPurchaseQueryFinished(items); + } + }); + } + } + }).start(); + } + + public void getPurchasedItemsAsync() { + new Thread(new Runnable() { + public void run() { + + final ArrayList items = new ArrayList(); + String continuationToken = null; + do { + Bundle purchasedItems = null; + try { + purchasedItems = mService.getPurchases(API_VERSION, mContext.getPackageName(), ITEM_TYPE_SUBS, continuationToken); + } catch (RemoteException e) { + Log.e(e); + } + + if (purchasedItems != null) { + int response = purchasedItems.getInt(RESPONSE_CODE); + if (response == RESPONSE_RESULT_OK) { + Log.d("[In-app purchase] response is OK"); + ArrayList purchaseDataList = purchasedItems.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST); + ArrayList signatureList = purchasedItems.getStringArrayList(RESPONSE_INAPP_SIGNATURE_LIST); + continuationToken = purchasedItems.getString(RESPONSE_INAPP_CONTINUATION_TOKEN); + + for (int i = 0; i < purchaseDataList.size(); ++i) { + String purchaseData = purchaseDataList.get(i); + String signature = signatureList.get(i); + Log.d("[In-app purchase] Found purchase data: " + purchaseData); + + verifySignatureAsync(new VerifiedSignatureListener() { + @Override + public void onParsedAndVerifiedSignatureQueryFinished(Purchasable item) { + items.add(item); + } + }, purchaseData, signature); + } + } else { + Log.e("[In-app purchase] Error: responde code is not ok: " + response); + } + } + } while (continuationToken != null); + + if (mHandler != null && mListener != null) { + mHandler.post(new Runnable() { + public void run() { + mListener.onPurchasedItemsQueryFinished(items); + } + }); + } + } + }).start(); + } + + private void purchaseItem(String productId, String sipIdentity) { + Bundle buyIntentBundle = null; + try { + buyIntentBundle = mService.getBuyIntent(API_VERSION, mContext.getPackageName(), productId, ITEM_TYPE_SUBS, sipIdentity); + } catch (RemoteException e) { + Log.e(e); + } + + if (buyIntentBundle != null) { + PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT); + try { + ((Activity) mContext).startIntentSenderForResult(pendingIntent.getIntentSender(), ACTIVITY_RESULT_CODE_PURCHASE_ITEM, new Intent(), 0, 0, 0); + } catch (SendIntentException e) { + Log.e(e); + } + } + } + + public void purchaseItemAsync(final String productId, final String sipIdentity) { + new Thread(new Runnable() { + public void run() { + purchaseItem(productId, sipIdentity); + } + }).start(); + } + + public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data) { + if (requestCode == ACTIVITY_RESULT_CODE_PURCHASE_ITEM) { + int responseCode = data.getIntExtra(RESPONSE_CODE, 0); + String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA); + String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE); + + if (resultCode == Activity.RESULT_OK && responseCode == RESPONSE_RESULT_OK) { + Log.d("[In-app purchase] response is OK"); + verifySignatureAsync(new VerifiedSignatureListener() { + @Override + public void onParsedAndVerifiedSignatureQueryFinished(Purchasable item) { + mListener.onPurchasedItemConfirmationQueryFinished(item); + } + }, purchaseData, signature); + } else { + Log.e("[In-app purchase] Error: resultCode is " + resultCode + " and responseCode is " + responseCode); + } + } + } + + public void destroy() { + mContext.unbindService(mServiceConn); + } + + private void verifySignatureAsync(final VerifiedSignatureListener listener, String purchasedData, String signature) { + XMLRPCClient client = null; + try { + client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); + } catch (MalformedURLException e) { + Log.e(e); + } + + if (client != null) { + client.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + } + + @Override + public void onResponse(long id, Object result) { + try { + JSONObject object = new JSONObject((String)result); + String productId = object.getString(PURCHASE_DETAILS_PRODUCT_ID); + Log.d("[In-app purchase] Purchasable verified by server: " + productId); + Purchasable item = new Purchasable(productId); + //TODO parse JSON result to get the purchasable in it + listener.onParsedAndVerifiedSignatureQueryFinished(item); + } catch (JSONException e) { + Log.e(e); + } + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + } + }, "create_account_from_in_app_purchase", "android", purchasedData, signature); + } + } + + interface VerifiedSignatureListener { + void onParsedAndVerifiedSignatureQueryFinished(Purchasable item); + } +} diff --git a/src/org/linphone/purchase/InAppPurchaseListener.java b/src/org/linphone/purchase/InAppPurchaseListener.java new file mode 100644 index 000000000..c076a5905 --- /dev/null +++ b/src/org/linphone/purchase/InAppPurchaseListener.java @@ -0,0 +1,46 @@ +package org.linphone.purchase; +/* +InAppPurchaseListener.java +Copyright (C) 2015 Belledonne Communications, Grenoble, France + +This program 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 2 +of the License, or (at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +import java.util.ArrayList; + +/** + * @author Sylvain Berfini + */ +public interface InAppPurchaseListener { + void onServiceAvailableForQueries(); + + /** + * + * @param items + */ + void onAvailableItemsForPurchaseQueryFinished(ArrayList items); + + /** + * + * @param items + */ + void onPurchasedItemsQueryFinished(ArrayList items); + + /** + * + * @param item + */ + void onPurchasedItemConfirmationQueryFinished(Purchasable item); +} diff --git a/src/org/linphone/purchase/Purchasable.java b/src/org/linphone/purchase/Purchasable.java new file mode 100644 index 000000000..9d0b9c253 --- /dev/null +++ b/src/org/linphone/purchase/Purchasable.java @@ -0,0 +1,61 @@ +package org.linphone.purchase; +/* +Purchasable.java +Copyright (C) 2015 Belledonne Communications, Grenoble, France + +This program 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 2 +of the License, or (at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/** + * @author Sylvain Berfini + */ +public class Purchasable { + private String id, title, description, price; + + public Purchasable(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public Purchasable setTitle(String title) { + this.title = title; + return this; + } + + public String getDescription() { + return description; + } + + public Purchasable setDescription(String description) { + this.description = description; + return this; + } + + public String getPrice() { + return price; + } + + public Purchasable setPrice(String price) { + this.price = price; + return this; + } +} From 21d92c4c013081a67791d5905d0cc0a47a11a2d7 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 21 Apr 2015 14:55:10 +0200 Subject: [PATCH 02/56] Set the debuggable to false in release --- Makefile | 2 ++ release.patch | 13 +++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 release.patch diff --git a/Makefile b/Makefile index e46202344..948f110d2 100644 --- a/Makefile +++ b/Makefile @@ -465,7 +465,9 @@ release: update-project echo "What is the version name for the release ?"; \ read version; \ echo "version.name=$$version" > default.properties + patch -p1 < release.patch $(ANT) release + git checkout HEAD AndroidManifest.xml run-linphone: ant run diff --git a/release.patch b/release.patch new file mode 100644 index 000000000..c6f3f3c2f --- /dev/null +++ b/release.patch @@ -0,0 +1,13 @@ +diff --git a/AndroidManifest.xml b/AndroidManifest.xml +index 5ef1374..86e13be 100644 +--- a/AndroidManifest.xml ++++ b/AndroidManifest.xml +@@ -45,7 +45,7 @@ + + + +- ++ + + Date: Tue, 21 Apr 2015 14:56:11 +0200 Subject: [PATCH 03/56] First attempt to reach xmlrpc server --- res/raw/linphonerc_factory | 5 ++- .../purchase/InAppPurchaseActivity.java | 7 +++- .../purchase/InAppPurchaseHelper.java | 41 ++++++++++++++++--- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/res/raw/linphonerc_factory b/res/raw/linphonerc_factory index 0b0a6c92c..3a1c800cb 100644 --- a/res/raw/linphonerc_factory +++ b/res/raw/linphonerc_factory @@ -30,4 +30,7 @@ dtmf_player_amp=0.1 [misc] max_calls=10 log_collection_upload_server_url=https://www.linphone.org:444/lft.php -user_certificates_path=/data/data/org.linphone/files \ No newline at end of file +user_certificates_path=/data/data/org.linphone/files + +[in-app-purchase] +server_url=https://www.linphone.org/wizard2.php \ No newline at end of file diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 78292e2e2..28c323711 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import java.util.ArrayList; import org.linphone.R; +import org.linphone.mediastream.Log; import android.app.Activity; import android.content.Intent; @@ -31,6 +32,7 @@ import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; /** * @author Sylvain Berfini @@ -58,6 +60,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onServiceAvailableForQueries() { inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); + inAppPurchaseHelper.getPurchasedItemsAsync(); } @Override @@ -76,7 +79,9 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onPurchasedItemsQueryFinished(ArrayList items) { - + for (Purchasable item : items) { + Log.d("[In-app] Item " + item.getTitle() + " is already bought"); + } } @Override diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 52b9d1663..289a6977d 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -51,7 +51,7 @@ import de.timroes.axmlrpc.XMLRPCServerException; */ public class InAppPurchaseHelper { public static final int API_VERSION = 3; - public static final String TEST_ITEM = "android.test.purchased"; + public static final String TEST_ITEM = "test_account_subscription"; public static final int ACTIVITY_RESULT_CODE_PURCHASE_ITEM = 11089; public static final String SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST"; @@ -65,6 +65,15 @@ public class InAppPurchaseHelper { public static final String ITEM_TYPE_SUBS = "subs"; public static final int RESPONSE_RESULT_OK = 0; + public static final int RESULT_USER_CANCELED = 1; + public static final int RESULT_SERVICE_UNAVAILABLE = 2; + public static final int RESULT_BILLING_UNAVAILABLE = 3; + public static final int RESULT_ITEM_UNAVAILABLE = 4; + public static final int RESULT_DEVELOPER_ERROR = 5; + public static final int RESULT_ERROR = 6; + public static final int RESULT_ITEM_ALREADY_OWNED = 7; + public static final int RESULT_ITEM_NOT_OWNED = 8; + public static final String RESPONSE_CODE = "RESPONSE_CODE"; public static final String RESPONSE_BUY_INTENT = "BUY_INTENT"; public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA"; @@ -88,6 +97,28 @@ public class InAppPurchaseHelper { private ServiceConnection mServiceConn; private Handler mHandler = new Handler(); + private String responseCodeToErrorMessage(int responseCode) { + switch (responseCode) { + case RESULT_USER_CANCELED: + return "BILLING_RESPONSE_RESULT_USER_CANCELED"; + case RESULT_SERVICE_UNAVAILABLE: + return "BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE"; + case RESULT_BILLING_UNAVAILABLE: + return "BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE"; + case RESULT_ITEM_UNAVAILABLE: + return "BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE"; + case RESULT_DEVELOPER_ERROR: + return "BILLING_RESPONSE_RESULT_DEVELOPER_ERROR"; + case RESULT_ERROR: + return "BILLING_RESPONSE_RESULT_ERROR"; + case RESULT_ITEM_ALREADY_OWNED: + return "BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED"; + case RESULT_ITEM_NOT_OWNED: + return "BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED"; + } + return "UNKNOWN_RESPONSE_CODE"; + } + public InAppPurchaseHelper(Activity context, InAppPurchaseListener listener) { mContext = context; mListener = listener; @@ -165,7 +196,7 @@ public class InAppPurchaseHelper { } } } else { - Log.e("[In-app purchase] Error: responde code is not ok: " + response); + Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response)); } } @@ -222,7 +253,7 @@ public class InAppPurchaseHelper { }, purchaseData, signature); } } else { - Log.e("[In-app purchase] Error: responde code is not ok: " + response); + Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response)); } } } while (continuationToken != null); @@ -279,7 +310,7 @@ public class InAppPurchaseHelper { } }, purchaseData, signature); } else { - Log.e("[In-app purchase] Error: resultCode is " + resultCode + " and responseCode is " + responseCode); + Log.e("[In-app purchase] Error: resultCode is " + resultCode + " and responseCode is " + responseCodeToErrorMessage(responseCode)); } } } @@ -321,7 +352,7 @@ public class InAppPurchaseHelper { public void onError(long id, XMLRPCException error) { Log.e(error); } - }, "create_account_from_in_app_purchase", "android", purchasedData, signature); + }, "create_account_from_in_app_purchase", "sylvain@sip.linphone.org", "toto", purchasedData, signature, "google"); } } From 081d12a571d8947e3aa8940280e547471214092e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 22 Apr 2015 14:59:40 +0200 Subject: [PATCH 04/56] Server signature verification working --- res/raw/linphonerc_factory | 2 +- .../purchase/InAppPurchaseActivity.java | 26 +++++---- .../purchase/InAppPurchaseHelper.java | 54 +++++++++++++++---- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/res/raw/linphonerc_factory b/res/raw/linphonerc_factory index 3a1c800cb..5aec262ee 100644 --- a/res/raw/linphonerc_factory +++ b/res/raw/linphonerc_factory @@ -33,4 +33,4 @@ log_collection_upload_server_url=https://www.linphone.org:444/lft.php user_certificates_path=/data/data/org.linphone/files [in-app-purchase] -server_url=https://www.linphone.org/wizard2.php \ No newline at end of file +server_url=https://www.linphone.org/inapp.php \ No newline at end of file diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 28c323711..8df20f486 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -32,14 +32,14 @@ import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import android.widget.Toast; /** * @author Sylvain Berfini */ public class InAppPurchaseActivity extends Activity implements InAppPurchaseListener, OnClickListener { private InAppPurchaseHelper inAppPurchaseHelper; - private LinearLayout purchasableItems; + private LinearLayout purchasableItemsLayout; + private ArrayList purchasedItems; @Override protected void onCreate(Bundle savedInstanceState) { @@ -48,7 +48,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList inAppPurchaseHelper = new InAppPurchaseHelper(this, this); setContentView(R.layout.in_app_store); - purchasableItems = (LinearLayout) findViewById(R.id.purchasable_items); + purchasableItemsLayout = (LinearLayout) findViewById(R.id.purchasable_items); } @Override @@ -59,29 +59,35 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onServiceAvailableForQueries() { - inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); inAppPurchaseHelper.getPurchasedItemsAsync(); } @Override public void onAvailableItemsForPurchaseQueryFinished(ArrayList items) { - purchasableItems.removeAllViews(); + purchasableItemsLayout.removeAllViews(); for (Purchasable item : items) { - View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItems); + View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); TextView text = (TextView) layout.findViewById(R.id.text); - text.setText(item.getTitle() + " (" + item.getPrice() + ")"); + text.setText(item.getTitle() + " " + item.getPrice()); ImageView image = (ImageView) layout.findViewById(R.id.image); image.setTag(item); image.setOnClickListener(this); + + for (Purchasable purchasedItem : purchasedItems) { + Log.d("[In-app purchase] Found already bought item"); + if (purchasedItem.getId().equals(item.getId())) { + image.setEnabled(false); + text.setEnabled(false); + } + } } } @Override public void onPurchasedItemsQueryFinished(ArrayList items) { - for (Purchasable item : items) { - Log.d("[In-app] Item " + item.getTitle() + " is already bought"); - } + purchasedItems = items; + inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); } @Override diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 289a6977d..0f66b3cf1 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -245,12 +245,10 @@ public class InAppPurchaseHelper { String signature = signatureList.get(i); Log.d("[In-app purchase] Found purchase data: " + purchaseData); - verifySignatureAsync(new VerifiedSignatureListener() { - @Override - public void onParsedAndVerifiedSignatureQueryFinished(Purchasable item) { - items.add(item); - } - }, purchaseData, signature); + Purchasable item = verifySignature(purchaseData, signature); + if (item != null) { + items.add(item); + } } } else { Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response)); @@ -303,7 +301,7 @@ public class InAppPurchaseHelper { if (resultCode == Activity.RESULT_OK && responseCode == RESPONSE_RESULT_OK) { Log.d("[In-app purchase] response is OK"); - verifySignatureAsync(new VerifiedSignatureListener() { + verifySignatureAndCreateAccountAsync(new VerifiedSignatureListener() { @Override public void onParsedAndVerifiedSignatureQueryFinished(Purchasable item) { mListener.onPurchasedItemConfirmationQueryFinished(item); @@ -319,7 +317,7 @@ public class InAppPurchaseHelper { mContext.unbindService(mServiceConn); } - private void verifySignatureAsync(final VerifiedSignatureListener listener, String purchasedData, String signature) { + private Purchasable verifySignature(String purchasedData, String signature) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -327,30 +325,66 @@ public class InAppPurchaseHelper { Log.e(e); } + if (client != null) { + try { + Object result = client.call("check_signature", purchasedData, signature, "google"); + String object = (String)result; + JSONObject json = new JSONObject(object); + Log.d("[In-app purchase] JSON received is " + json); + String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); + Log.d("[In-app purchase] Purchasable verified by server: " + productId); + Purchasable item = new Purchasable(productId); + //TODO parse JSON result to get the purchasable in it + return item; + } catch (XMLRPCException e) { + Log.e(e); + } catch (JSONException e) { + Log.e(e); + } + } + + return null; + } + + private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, String purchasedData, String signature) { + XMLRPCClient client = null; + try { + client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); + } catch (MalformedURLException e) { + Log.e(e); + Log.e("[In-app purchase] Can't reach the server !"); + } + if (client != null) { client.callAsync(new XMLRPCCallback() { @Override public void onServerError(long id, XMLRPCServerException error) { Log.e(error); + Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } @Override public void onResponse(long id, Object result) { try { - JSONObject object = new JSONObject((String)result); - String productId = object.getString(PURCHASE_DETAILS_PRODUCT_ID); + String object = (String)result; + JSONObject json = new JSONObject(object); + Log.d("[In-app purchase] JSON received is " + json); + String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); Log.d("[In-app purchase] Purchasable verified by server: " + productId); Purchasable item = new Purchasable(productId); //TODO parse JSON result to get the purchasable in it listener.onParsedAndVerifiedSignatureQueryFinished(item); + return; } catch (JSONException e) { Log.e(e); } + Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } @Override public void onError(long id, XMLRPCException error) { Log.e(error); + Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } }, "create_account_from_in_app_purchase", "sylvain@sip.linphone.org", "toto", purchasedData, signature, "google"); } From 8d0e7c549b4008b8718394c04d045c207159ad9a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 23 Apr 2015 14:23:22 +0200 Subject: [PATCH 05/56] Changes in xmlrpc API to be able to retrieve the expiration date for subscriptions --- .../purchase/InAppPurchaseActivity.java | 2 +- .../purchase/InAppPurchaseHelper.java | 32 +++++++------------ src/org/linphone/purchase/Purchasable.java | 21 ++++++++++++ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 8df20f486..d77d37cd2 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -75,7 +75,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList image.setOnClickListener(this); for (Purchasable purchasedItem : purchasedItems) { - Log.d("[In-app purchase] Found already bought item"); + Log.d("[In-app purchase] Found already bought item, expires " + purchasedItem.getExpireDate()); if (purchasedItem.getId().equals(item.getId())) { image.setEnabled(false); text.setEnabled(false); diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 0f66b3cf1..d1f70d214 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -126,7 +126,6 @@ public class InAppPurchaseHelper { @Override public void onServiceDisconnected(ComponentName name) { mService = null; - Log.d("[In-app purchase] service disconnected"); } @Override @@ -138,7 +137,6 @@ public class InAppPurchaseHelper { if (response != RESPONSE_RESULT_OK) { Log.e("[In-app purchase] Error: Subscriptions aren't supported!"); } else { - Log.d("[In-app purchase] service connected and subsciptions are available"); mListener.onServiceAvailableForQueries(); } } catch (RemoteException e) { @@ -153,8 +151,6 @@ public class InAppPurchaseHelper { boolean ok = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); if (!ok) { Log.e("[In-app purchase] Error: Bind service failed"); - } else { - Log.d("[In-app purchase] service bound"); } } else { Log.e("[In-app purchase] Error: Billing service unavailable on device."); @@ -178,7 +174,6 @@ public class InAppPurchaseHelper { if (skuDetails != null) { int response = skuDetails.getInt(RESPONSE_CODE); if (response == RESPONSE_RESULT_OK) { - Log.d("[In-app purchase] response is OK"); ArrayList responseList = skuDetails.getStringArrayList(SKU_DETAILS_LIST); for (String thisResponse : responseList) { try { @@ -187,7 +182,6 @@ public class InAppPurchaseHelper { String price = object.getString(SKU_DETAILS_PRICE); String title = object.getString(SKU_DETAILS_TITLE); String desc = object.getString(SKU_DETAILS_DESC); - Log.d("[In-app purchase] found purchasable " + title + " (" + desc + ") for " + price + " with id " + id); Purchasable purchasable = new Purchasable(id).setTitle(title).setDescription(desc).setPrice(price); products.add(purchasable); @@ -235,7 +229,6 @@ public class InAppPurchaseHelper { if (purchasedItems != null) { int response = purchasedItems.getInt(RESPONSE_CODE); if (response == RESPONSE_RESULT_OK) { - Log.d("[In-app purchase] response is OK"); ArrayList purchaseDataList = purchasedItems.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST); ArrayList signatureList = purchasedItems.getStringArrayList(RESPONSE_INAPP_SIGNATURE_LIST); continuationToken = purchasedItems.getString(RESPONSE_INAPP_CONTINUATION_TOKEN); @@ -243,9 +236,9 @@ public class InAppPurchaseHelper { for (int i = 0; i < purchaseDataList.size(); ++i) { String purchaseData = purchaseDataList.get(i); String signature = signatureList.get(i); - Log.d("[In-app purchase] Found purchase data: " + purchaseData); + Log.d("[In-app purchase] " + purchaseData); - Purchasable item = verifySignature(purchaseData, signature); + Purchasable item = verifySignatureAndGetExpire(purchaseData, signature); if (item != null) { items.add(item); } @@ -300,7 +293,6 @@ public class InAppPurchaseHelper { String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE); if (resultCode == Activity.RESULT_OK && responseCode == RESPONSE_RESULT_OK) { - Log.d("[In-app purchase] response is OK"); verifySignatureAndCreateAccountAsync(new VerifiedSignatureListener() { @Override public void onParsedAndVerifiedSignatureQueryFinished(Purchasable item) { @@ -317,7 +309,7 @@ public class InAppPurchaseHelper { mContext.unbindService(mServiceConn); } - private Purchasable verifySignature(String purchasedData, String signature) { + private Purchasable verifySignatureAndGetExpire(String purchasedData, String signature) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -327,13 +319,12 @@ public class InAppPurchaseHelper { if (client != null) { try { - Object result = client.call("check_signature", purchasedData, signature, "google"); - String object = (String)result; - JSONObject json = new JSONObject(object); - Log.d("[In-app purchase] JSON received is " + json); + Object result = client.call("get_expiration_date", purchasedData, signature, "google"); + String expire = (String)result; + JSONObject json = new JSONObject(purchasedData); String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); - Log.d("[In-app purchase] Purchasable verified by server: " + productId); Purchasable item = new Purchasable(productId); + item.setExpire(Long.parseLong(expire)); //TODO parse JSON result to get the purchasable in it return item; } catch (XMLRPCException e) { @@ -346,7 +337,7 @@ public class InAppPurchaseHelper { return null; } - private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, String purchasedData, String signature) { + private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, String signature) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -366,12 +357,11 @@ public class InAppPurchaseHelper { @Override public void onResponse(long id, Object result) { try { - String object = (String)result; - JSONObject json = new JSONObject(object); - Log.d("[In-app purchase] JSON received is " + json); + String expire = (String)result; + JSONObject json = new JSONObject(purchasedData); String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); - Log.d("[In-app purchase] Purchasable verified by server: " + productId); Purchasable item = new Purchasable(productId); + item.setExpire(Long.parseLong(expire)); //TODO parse JSON result to get the purchasable in it listener.onParsedAndVerifiedSignatureQueryFinished(item); return; diff --git a/src/org/linphone/purchase/Purchasable.java b/src/org/linphone/purchase/Purchasable.java index 9d0b9c253..3228757d0 100644 --- a/src/org/linphone/purchase/Purchasable.java +++ b/src/org/linphone/purchase/Purchasable.java @@ -1,4 +1,9 @@ package org.linphone.purchase; + +import java.sql.Date; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Locale; /* Purchasable.java Copyright (C) 2015 Belledonne Communications, Grenoble, France @@ -23,6 +28,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ public class Purchasable { private String id, title, description, price; + private long expire; public Purchasable(String id) { this.id = id; @@ -58,4 +64,19 @@ public class Purchasable { this.price = price; return this; } + + public long getExpire() { + return expire; + } + + public String getExpireDate() { + DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()); + Date date = new Date(expire); + return dateFormat.format(date); + } + + public Purchasable setExpire(long expire) { + this.expire = expire; + return this; + } } From f4a6bfe59322ca4ef8fea003d3d9e57659b43ebd Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 24 Apr 2015 09:52:27 +0200 Subject: [PATCH 06/56] Added commentarys on callbacks --- .../linphone/purchase/InAppPurchaseListener.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseListener.java b/src/org/linphone/purchase/InAppPurchaseListener.java index c076a5905..4047fbe3c 100644 --- a/src/org/linphone/purchase/InAppPurchaseListener.java +++ b/src/org/linphone/purchase/InAppPurchaseListener.java @@ -24,23 +24,26 @@ import java.util.ArrayList; * @author Sylvain Berfini */ public interface InAppPurchaseListener { + /** + * Callback called when the in-app purchase listener is connected and available for queries + */ void onServiceAvailableForQueries(); /** - * - * @param items + * Callback called when the query for items available for purchase is done + * @param items the list of items that can be purchased (also contains the ones already bought) */ void onAvailableItemsForPurchaseQueryFinished(ArrayList items); /** - * - * @param items + * Callback called when the query for items bought by the user is done + * @param items the list of items already purchased by the user */ void onPurchasedItemsQueryFinished(ArrayList items); /** - * - * @param item + * Callback called when the purchase has been validated by our external server + * @param item the item the user just bought */ void onPurchasedItemConfirmationQueryFinished(Purchasable item); } From f747635c7b4041e074bd2a44644d756479aed923 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 28 Apr 2015 16:02:33 +0200 Subject: [PATCH 07/56] Changes to reflect new xmlrpc api --- .../purchase/InAppPurchaseActivity.java | 4 +- .../purchase/InAppPurchaseHelper.java | 46 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index d77d37cd2..fd6b086af 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -92,7 +92,9 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onPurchasedItemConfirmationQueryFinished(Purchasable item) { - + if (item != null) { + Log.d("[In-app purchase] Item bought, expires " + item.getExpireDate()); + } } @Override diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index d1f70d214..52e80807b 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -21,12 +21,15 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.regex.Pattern; import org.json.JSONException; import org.json.JSONObject; import org.linphone.LinphonePreferences; import org.linphone.mediastream.Log; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.Activity; import android.app.PendingIntent; import android.content.ComponentName; @@ -38,6 +41,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.util.Patterns; import com.android.vending.billing.IInAppBillingService; @@ -96,6 +100,7 @@ public class InAppPurchaseHelper { private IInAppBillingService mService; private ServiceConnection mServiceConn; private Handler mHandler = new Handler(); + private String mGmailAccount; private String responseCodeToErrorMessage(int responseCode) { switch (responseCode) { @@ -122,6 +127,8 @@ public class InAppPurchaseHelper { public InAppPurchaseHelper(Activity context, InAppPurchaseListener listener) { mContext = context; mListener = listener; + mGmailAccount = getGmailAccount(); + mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { @@ -134,7 +141,7 @@ public class InAppPurchaseHelper { String packageName = mContext.getPackageName(); try { int response = mService.isBillingSupported(API_VERSION, packageName, ITEM_TYPE_SUBS); - if (response != RESPONSE_RESULT_OK) { + if (response != RESPONSE_RESULT_OK || mGmailAccount == null) { Log.e("[In-app purchase] Error: Subscriptions aren't supported!"); } else { mListener.onServiceAvailableForQueries(); @@ -296,7 +303,9 @@ public class InAppPurchaseHelper { verifySignatureAndCreateAccountAsync(new VerifiedSignatureListener() { @Override public void onParsedAndVerifiedSignatureQueryFinished(Purchasable item) { - mListener.onPurchasedItemConfirmationQueryFinished(item); + if (item != null) { + mListener.onPurchasedItemConfirmationQueryFinished(item); + } } }, purchaseData, signature); } else { @@ -309,6 +318,24 @@ public class InAppPurchaseHelper { mContext.unbindService(mServiceConn); } + private boolean isEmailCorrect(String email) { + Pattern emailPattern = Patterns.EMAIL_ADDRESS; + return emailPattern.matcher(email).matches(); + } + + private String getGmailAccount() { + Account[] accounts = AccountManager.get(mContext).getAccountsByType("com.google"); + + for (Account account: accounts) { + if (isEmailCorrect(account.name)) { + String possibleEmail = account.name; + return possibleEmail; + } + } + + return null; + } + private Purchasable verifySignatureAndGetExpire(String purchasedData, String signature) { XMLRPCClient client = null; try { @@ -319,8 +346,13 @@ public class InAppPurchaseHelper { if (client != null) { try { - Object result = client.call("get_expiration_date", purchasedData, signature, "google"); + Object result = client.call("get_expiration_date", mGmailAccount, purchasedData, signature, "google"); String expire = (String)result; + if ("-1".equals(expire)) { + Log.e("[In-app purchase] Server failed to validate the payload !"); + return null; + } + JSONObject json = new JSONObject(purchasedData); String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); Purchasable item = new Purchasable(productId); @@ -358,6 +390,12 @@ public class InAppPurchaseHelper { public void onResponse(long id, Object result) { try { String expire = (String)result; + if ("-1".equals(expire)) { + Log.e("[In-app purchase] Server failed to validate the payload !"); + listener.onParsedAndVerifiedSignatureQueryFinished(null); + return; + } + JSONObject json = new JSONObject(purchasedData); String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); Purchasable item = new Purchasable(productId); @@ -376,7 +414,7 @@ public class InAppPurchaseHelper { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } - }, "create_account_from_in_app_purchase", "sylvain@sip.linphone.org", "toto", purchasedData, signature, "google"); + }, "create_account_from_in_app_purchase", mGmailAccount, "sylvain@sip.linphone.org", "toto", purchasedData, signature, "google"); } } From 5270bd8a5fec49e023b16fd4db5880b0c3f6ca63 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 28 Apr 2015 16:47:58 +0200 Subject: [PATCH 08/56] In-app view ask user for phone number and password --- res/layout/in_app_store.xml | 134 +++++++++- .../purchase/InAppPurchaseActivity.java | 231 +++++++++++++++++- .../purchase/InAppPurchaseHelper.java | 8 +- 3 files changed, 348 insertions(+), 25 deletions(-) diff --git a/res/layout/in_app_store.xml b/res/layout/in_app_store.xml index d6ec049d1..9ea941d5a 100644 --- a/res/layout/in_app_store.xml +++ b/res/layout/in_app_store.xml @@ -1,26 +1,140 @@ - + android:layout_height="match_parent" + android:background="@drawable/background" + android:orientation="vertical" > - + android:text="@string/setup_title_assistant"/> + + + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index fd6b086af..9a2b2d8c3 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -18,20 +18,34 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +import java.net.URL; import java.util.ArrayList; +import java.util.Locale; +import org.linphone.LinphoneManager; import org.linphone.R; +import org.linphone.core.LinphoneProxyConfig; import org.linphone.mediastream.Log; import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.os.Handler; +import android.text.Editable; +import android.text.InputFilter; +import android.text.Spanned; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; +import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import de.timroes.axmlrpc.XMLRPCCallback; +import de.timroes.axmlrpc.XMLRPCClient; +import de.timroes.axmlrpc.XMLRPCException; +import de.timroes.axmlrpc.XMLRPCServerException; /** * @author Sylvain Berfini @@ -40,6 +54,15 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList private InAppPurchaseHelper inAppPurchaseHelper; private LinearLayout purchasableItemsLayout; private ArrayList purchasedItems; + private ImageView buyItemButton; + + private EditText username, password, passwordConfirm; + private TextView errorMessage; + private Handler mHandler = new Handler(); + private char[] acceptedChars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' }; + private boolean usernameOk = false; + private boolean passwordOk = false; + private boolean confirmPasswordOk = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -49,6 +72,31 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList setContentView(R.layout.in_app_store); purchasableItemsLayout = (LinearLayout) findViewById(R.id.purchasable_items); + + username = (EditText) findViewById(R.id.setup_username); + ImageView usernameOkIV = (ImageView) findViewById(R.id.setup_username_ok); + addXMLRPCUsernameHandler(username, usernameOkIV); + InputFilter filter = new InputFilter(){ + @Override + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + if (end > start) { + for (int index = start; index < end; index++) { + if (!new String(acceptedChars).contains(String.valueOf(source.charAt(index)))) { + return ""; + } + } + } + return null; + } + }; + username.setFilters(new InputFilter[] { filter }); + password = (EditText) findViewById(R.id.setup_password); + passwordConfirm = (EditText) findViewById(R.id.setup_password_confirm); + ImageView passwordOkIV = (ImageView) findViewById(R.id.setup_password_ok); + addXMLRPCPasswordHandler(password, passwordOkIV); + ImageView passwordConfirmOkIV = (ImageView) findViewById(R.id.setup_confirm_password_ok); + addXMLRPCConfirmPasswordHandler(password, passwordConfirm, passwordConfirmOkIV); + errorMessage = (TextView) findViewById(R.id.setup_error); } @Override @@ -59,6 +107,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onServiceAvailableForQueries() { + inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); inAppPurchaseHelper.getPurchasedItemsAsync(); } @@ -69,25 +118,22 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList for (Purchasable item : items) { View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); TextView text = (TextView) layout.findViewById(R.id.text); - text.setText(item.getTitle() + " " + item.getPrice()); + text.setText("Buy account (" + item.getPrice() + ")"); ImageView image = (ImageView) layout.findViewById(R.id.image); image.setTag(item); image.setOnClickListener(this); - for (Purchasable purchasedItem : purchasedItems) { - Log.d("[In-app purchase] Found already bought item, expires " + purchasedItem.getExpireDate()); - if (purchasedItem.getId().equals(item.getId())) { - image.setEnabled(false); - text.setEnabled(false); - } - } + buyItemButton = image; + buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); } } @Override public void onPurchasedItemsQueryFinished(ArrayList items) { purchasedItems = items; - inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); + for (Purchasable purchasedItem : purchasedItems) { + Log.d("[In-app purchase] Found already bought item, expires " + purchasedItem.getExpireDate()); + } } @Override @@ -100,11 +146,174 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onClick(View v) { Purchasable item = (Purchasable) v.getTag(); - inAppPurchaseHelper.purchaseItemAsync(item.getId(), "sylvain@sip.linphone.org"); + inAppPurchaseHelper.purchaseItemAsync(item.getId(), getUsername()); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data); + inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data, getUsername(), password.getText().toString()); + } + + private String getUsername() { + String username = this.username.getText().toString(); + LinphoneProxyConfig lpc = LinphoneManager.getLc().createProxyConfig(); + username = lpc.normalizePhoneNumber(username); + return username.toLowerCase(Locale.getDefault()); + } + + private boolean isUsernameCorrect(String username) { + return username.matches("^(\\+)?(\\d-)?(\\d{3}-)?(\\d{3}-)?\\d{4,}$"); + } + + private void isUsernameRegistred(String username, final ImageView icon) { + final Runnable runNotReachable = new Runnable() { + public void run() { + errorMessage.setText(R.string.wizard_server_unavailable); + usernameOk = false; + icon.setImageResource(R.drawable.wizard_notok); + buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); + } + }; + + try { + XMLRPCClient client = new XMLRPCClient(new URL(getString(R.string.wizard_url))); + + XMLRPCCallback listener = new XMLRPCCallback() { + Runnable runNotOk = new Runnable() { + public void run() { + errorMessage.setText(R.string.wizard_username_unavailable); + usernameOk = false; + icon.setImageResource(R.drawable.wizard_notok); + buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); + } + }; + + Runnable runOk = new Runnable() { + public void run() { + errorMessage.setText(""); + usernameOk = true; + icon.setImageResource(R.drawable.wizard_ok); + buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); + } + }; + + public void onResponse(long id, Object result) { + int answer = (Integer) result; + if (answer != 0) { + mHandler.post(runNotOk); + } + else { + mHandler.post(runOk); + } + } + + public void onError(long id, XMLRPCException error) { + mHandler.post(runNotReachable); + } + + public void onServerError(long id, XMLRPCServerException error) { + mHandler.post(runNotReachable); + } + }; + + client.callAsync(listener, "check_account", username); + } + catch(Exception ex) { + mHandler.post(runNotReachable); + } + } + + private boolean isPasswordCorrect(String password) { + return password.length() >= 6; + } + + private void addXMLRPCUsernameHandler(final EditText field, final ImageView icon) { + field.addTextChangedListener(new TextWatcher() { + public void afterTextChanged(Editable s) { + + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + public void onTextChanged(CharSequence s, int start, int count, int after) { + usernameOk = false; + String username = field.getText().toString().toLowerCase(Locale.getDefault()); + if (isUsernameCorrect(username)) { + LinphoneProxyConfig lpc = LinphoneManager.getLc().createProxyConfig(); + username = lpc.normalizePhoneNumber(username); + isUsernameRegistred(username, icon); + } else { + errorMessage.setText(R.string.wizard_username_incorrect); + icon.setImageResource(R.drawable.wizard_notok); + } + } + }); + } + + private void addXMLRPCPasswordHandler(final EditText field1, final ImageView icon) { + TextWatcher passwordListener = new TextWatcher() { + public void afterTextChanged(Editable s) { + + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + public void onTextChanged(CharSequence s, int start, int count, int after) + { + passwordOk = false; + if (isPasswordCorrect(field1.getText().toString())) { + passwordOk = true; + icon.setImageResource(R.drawable.wizard_ok); + errorMessage.setText(""); + } + else { + errorMessage.setText(R.string.wizard_password_incorrect); + icon.setImageResource(R.drawable.wizard_notok); + } + buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); + } + }; + + field1.addTextChangedListener(passwordListener); + } + + private void addXMLRPCConfirmPasswordHandler(final EditText field1, final EditText field2, final ImageView icon) { + TextWatcher passwordListener = new TextWatcher() { + public void afterTextChanged(Editable s) { + + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + public void onTextChanged(CharSequence s, int start, int count, int after) + { + confirmPasswordOk = false; + if (field1.getText().toString().equals(field2.getText().toString())) { + confirmPasswordOk = true; + icon.setImageResource(R.drawable.wizard_ok); + + if (!isPasswordCorrect(field1.getText().toString())) { + errorMessage.setText(R.string.wizard_password_incorrect); + } + else { + errorMessage.setText(""); + } + } + else { + errorMessage.setText(R.string.wizard_passwords_unmatched); + icon.setImageResource(R.drawable.wizard_notok); + } + buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); + } + }; + + field1.addTextChangedListener(passwordListener); + field2.addTextChangedListener(passwordListener); } } diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 52e80807b..6309be55c 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -293,7 +293,7 @@ public class InAppPurchaseHelper { }).start(); } - public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data) { + public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data, String username, String password) { if (requestCode == ACTIVITY_RESULT_CODE_PURCHASE_ITEM) { int responseCode = data.getIntExtra(RESPONSE_CODE, 0); String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA); @@ -307,7 +307,7 @@ public class InAppPurchaseHelper { mListener.onPurchasedItemConfirmationQueryFinished(item); } } - }, purchaseData, signature); + }, purchaseData, signature, username, password); } else { Log.e("[In-app purchase] Error: resultCode is " + resultCode + " and responseCode is " + responseCodeToErrorMessage(responseCode)); } @@ -369,7 +369,7 @@ public class InAppPurchaseHelper { return null; } - private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, String signature) { + private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, String signature, String username, String password) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -414,7 +414,7 @@ public class InAppPurchaseHelper { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } - }, "create_account_from_in_app_purchase", mGmailAccount, "sylvain@sip.linphone.org", "toto", purchasedData, signature, "google"); + }, "create_account_from_in_app_purchase", mGmailAccount, username + "@sip.linphone.org", password, purchasedData, signature, "google"); } } From 15cb4a3919b9a5a3732792da5e7a1af3f3960645 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 30 Apr 2015 11:00:36 +0200 Subject: [PATCH 09/56] Use isPhoneNumber from proxyConfig instead --- src/org/linphone/purchase/InAppPurchaseActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 9a2b2d8c3..db095a623 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -162,7 +162,8 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList } private boolean isUsernameCorrect(String username) { - return username.matches("^(\\+)?(\\d-)?(\\d{3}-)?(\\d{3}-)?\\d{4,}$"); + LinphoneProxyConfig lpc = LinphoneManager.getLc().createProxyConfig(); + return lpc.isPhoneNumber(username); } private void isUsernameRegistred(String username, final ImageView icon) { From d7e2ba79e00cb311748281d39a34e57b600a80dc Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 11 May 2015 16:34:53 +0200 Subject: [PATCH 10/56] Added recover account, removed password fields and added email field --- AndroidManifest.xml | 2 +- res/layout/in_app_store.xml | 51 +--- .../purchase/InAppPurchaseActivity.java | 245 +++++------------- .../purchase/InAppPurchaseHelper.java | 79 ++++-- .../purchase/InAppPurchaseListener.java | 6 + 5 files changed, 139 insertions(+), 244 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 038571cd8..9b217e2de 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="2410" android:installLocation="auto"> diff --git a/res/layout/in_app_store.xml b/res/layout/in_app_store.xml index 9ea941d5a..9d5f3f6e3 100644 --- a/res/layout/in_app_store.xml +++ b/res/layout/in_app_store.xml @@ -42,7 +42,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/setup_username_hint" - android:inputType="textEmailAddress" + android:inputType="phone" android:paddingLeft="10dp" android:paddingRight="10dp" android:singleLine="true" @@ -62,65 +62,24 @@ - - - - - - - - - - - + purchasedItems; - private ImageView buyItemButton; - - private EditText username, password, passwordConfirm; - private TextView errorMessage; + private ImageView buyItemButton, recoverAccountButton; private Handler mHandler = new Handler(); - private char[] acceptedChars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' }; + + private EditText username, email; + private TextView errorMessage; private boolean usernameOk = false; - private boolean passwordOk = false; - private boolean confirmPasswordOk = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -74,28 +64,9 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList purchasableItemsLayout = (LinearLayout) findViewById(R.id.purchasable_items); username = (EditText) findViewById(R.id.setup_username); + email = (EditText) findViewById(R.id.setup_email); ImageView usernameOkIV = (ImageView) findViewById(R.id.setup_username_ok); - addXMLRPCUsernameHandler(username, usernameOkIV); - InputFilter filter = new InputFilter(){ - @Override - public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { - if (end > start) { - for (int index = start; index < end; index++) { - if (!new String(acceptedChars).contains(String.valueOf(source.charAt(index)))) { - return ""; - } - } - } - return null; - } - }; - username.setFilters(new InputFilter[] { filter }); - password = (EditText) findViewById(R.id.setup_password); - passwordConfirm = (EditText) findViewById(R.id.setup_password_confirm); - ImageView passwordOkIV = (ImageView) findViewById(R.id.setup_password_ok); - addXMLRPCPasswordHandler(password, passwordOkIV); - ImageView passwordConfirmOkIV = (ImageView) findViewById(R.id.setup_confirm_password_ok); - addXMLRPCConfirmPasswordHandler(password, passwordConfirm, passwordConfirmOkIV); + addUsernameHandler(username, usernameOkIV); errorMessage = (TextView) findViewById(R.id.setup_error); } @@ -107,7 +78,8 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onServiceAvailableForQueries() { - inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); + email.setText(inAppPurchaseHelper.getGmailAccount()); + email.setEnabled(false); inAppPurchaseHelper.getPurchasedItemsAsync(); } @@ -116,23 +88,21 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList purchasableItemsLayout.removeAllViews(); for (Purchasable item : items) { - View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); - TextView text = (TextView) layout.findViewById(R.id.text); - text.setText("Buy account (" + item.getPrice() + ")"); - ImageView image = (ImageView) layout.findViewById(R.id.image); - image.setTag(item); - image.setOnClickListener(this); - - buyItemButton = image; - buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); + displayBuySubscriptionButton(item); } } @Override public void onPurchasedItemsQueryFinished(ArrayList items) { purchasedItems = items; - for (Purchasable purchasedItem : purchasedItems) { - Log.d("[In-app purchase] Found already bought item, expires " + purchasedItem.getExpireDate()); + + if (items == null || items.size() == 0) { + inAppPurchaseHelper.getAvailableItemsForPurchaseAsync(); + } else { + for (Purchasable purchasedItem : purchasedItems) { + Log.d("[In-app purchase] Found already bought item, expires " + purchasedItem.getExpireDate()); + displayRecoverAccountButton(purchasedItem); + } } } @@ -146,12 +116,50 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override public void onClick(View v) { Purchasable item = (Purchasable) v.getTag(); - inAppPurchaseHelper.purchaseItemAsync(item.getId(), getUsername()); + if (v.equals(recoverAccountButton)) { + inAppPurchaseHelper.recoverAccount(item.getId(), getUsername()); + } else { + inAppPurchaseHelper.purchaseItemAsync(item.getId(), getUsername()); + } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data, getUsername(), password.getText().toString()); + inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data, getUsername()); + } + + @Override + public void onRecoverAccountSuccessful(boolean success) { + mHandler.post(new Runnable() { + @Override + public void run() { + recoverAccountButton.setEnabled(false); + } + }); + } + + private void displayBuySubscriptionButton(Purchasable item) { + View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); + TextView text = (TextView) layout.findViewById(R.id.text); + text.setText("Buy account (" + item.getPrice() + ")"); + ImageView image = (ImageView) layout.findViewById(R.id.image); + image.setTag(item); + image.setOnClickListener(this); + + buyItemButton = image; + buyItemButton.setEnabled(usernameOk); + } + + private void displayRecoverAccountButton(Purchasable item) { + View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); + TextView text = (TextView) layout.findViewById(R.id.text); + text.setText("Recover account"); + ImageView image = (ImageView) layout.findViewById(R.id.image); + image.setTag(item); + image.setOnClickListener(this); + + recoverAccountButton = image; + recoverAccountButton.setEnabled(usernameOk); } private String getUsername() { @@ -166,69 +174,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList return lpc.isPhoneNumber(username); } - private void isUsernameRegistred(String username, final ImageView icon) { - final Runnable runNotReachable = new Runnable() { - public void run() { - errorMessage.setText(R.string.wizard_server_unavailable); - usernameOk = false; - icon.setImageResource(R.drawable.wizard_notok); - buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); - } - }; - - try { - XMLRPCClient client = new XMLRPCClient(new URL(getString(R.string.wizard_url))); - - XMLRPCCallback listener = new XMLRPCCallback() { - Runnable runNotOk = new Runnable() { - public void run() { - errorMessage.setText(R.string.wizard_username_unavailable); - usernameOk = false; - icon.setImageResource(R.drawable.wizard_notok); - buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); - } - }; - - Runnable runOk = new Runnable() { - public void run() { - errorMessage.setText(""); - usernameOk = true; - icon.setImageResource(R.drawable.wizard_ok); - buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); - } - }; - - public void onResponse(long id, Object result) { - int answer = (Integer) result; - if (answer != 0) { - mHandler.post(runNotOk); - } - else { - mHandler.post(runOk); - } - } - - public void onError(long id, XMLRPCException error) { - mHandler.post(runNotReachable); - } - - public void onServerError(long id, XMLRPCServerException error) { - mHandler.post(runNotReachable); - } - }; - - client.callAsync(listener, "check_account", username); - } - catch(Exception ex) { - mHandler.post(runNotReachable); - } - } - - private boolean isPasswordCorrect(String password) { - return password.length() >= 6; - } - - private void addXMLRPCUsernameHandler(final EditText field, final ImageView icon) { + private void addUsernameHandler(final EditText field, final ImageView icon) { field.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { @@ -240,81 +186,18 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList public void onTextChanged(CharSequence s, int start, int count, int after) { usernameOk = false; - String username = field.getText().toString().toLowerCase(Locale.getDefault()); + String username = s.toString(); if (isUsernameCorrect(username)) { - LinphoneProxyConfig lpc = LinphoneManager.getLc().createProxyConfig(); - username = lpc.normalizePhoneNumber(username); - isUsernameRegistred(username, icon); + usernameOk = true; + icon.setImageResource(R.drawable.wizard_ok); + errorMessage.setText(""); } else { errorMessage.setText(R.string.wizard_username_incorrect); icon.setImageResource(R.drawable.wizard_notok); } + if (buyItemButton != null) buyItemButton.setEnabled(usernameOk); + if (recoverAccountButton != null) recoverAccountButton.setEnabled(usernameOk); } }); } - - private void addXMLRPCPasswordHandler(final EditText field1, final ImageView icon) { - TextWatcher passwordListener = new TextWatcher() { - public void afterTextChanged(Editable s) { - - } - - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - public void onTextChanged(CharSequence s, int start, int count, int after) - { - passwordOk = false; - if (isPasswordCorrect(field1.getText().toString())) { - passwordOk = true; - icon.setImageResource(R.drawable.wizard_ok); - errorMessage.setText(""); - } - else { - errorMessage.setText(R.string.wizard_password_incorrect); - icon.setImageResource(R.drawable.wizard_notok); - } - buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); - } - }; - - field1.addTextChangedListener(passwordListener); - } - - private void addXMLRPCConfirmPasswordHandler(final EditText field1, final EditText field2, final ImageView icon) { - TextWatcher passwordListener = new TextWatcher() { - public void afterTextChanged(Editable s) { - - } - - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - public void onTextChanged(CharSequence s, int start, int count, int after) - { - confirmPasswordOk = false; - if (field1.getText().toString().equals(field2.getText().toString())) { - confirmPasswordOk = true; - icon.setImageResource(R.drawable.wizard_ok); - - if (!isPasswordCorrect(field1.getText().toString())) { - errorMessage.setText(R.string.wizard_password_incorrect); - } - else { - errorMessage.setText(""); - } - } - else { - errorMessage.setText(R.string.wizard_passwords_unmatched); - icon.setImageResource(R.drawable.wizard_notok); - } - buyItemButton.setEnabled(usernameOk && passwordOk && confirmPasswordOk); - } - }; - - field1.addTextChangedListener(passwordListener); - field2.addTextChangedListener(passwordListener); - } } diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 6309be55c..29ec45e60 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -95,6 +95,13 @@ public class InAppPurchaseHelper { public static final String PURCHASE_DETAILS_PAYLOAD = "developerPayload"; public static final String PURCHASE_DETAILS_PURCHASE_TOKEN = "purchaseToken"; + public static final String SERVER_ERROR_INVALID_ACCOUNT = "ERROR_INVALID_ACCOUNT"; + public static final String SERVER_ERROR_PURCHASE_CANCELLED = "ERROR_PURCHASE_CANCELLED"; + public static final String SERVER_ERROR_RECEIPT_PARSING_FAILED = "ERROR_RECEIPT_PARSING_FAILED"; + public static final String SERVER_ERROR_UID_ALREADY_IN_USE = "ERROR_UID_ALREADY_IN_USE"; + public static final String SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED = "ERROR_SIGNATURE_VERIFICATION_FAILED"; + public static final String SERVER_ERROR_ACCOUNT_ALREADY_EXISTS = "ERROR_ACCOUNT_ALREADY_EXISTS"; + private Context mContext; private InAppPurchaseListener mListener; private IInAppBillingService mService; @@ -293,7 +300,7 @@ public class InAppPurchaseHelper { }).start(); } - public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data, String username, String password) { + public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data, String username) { if (requestCode == ACTIVITY_RESULT_CODE_PURCHASE_ITEM) { int responseCode = data.getIntExtra(RESPONSE_CODE, 0); String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA); @@ -307,23 +314,50 @@ public class InAppPurchaseHelper { mListener.onPurchasedItemConfirmationQueryFinished(item); } } - }, purchaseData, signature, username, password); + }, purchaseData, signature, username); } else { Log.e("[In-app purchase] Error: resultCode is " + resultCode + " and responseCode is " + responseCodeToErrorMessage(responseCode)); } } } + public void recoverAccount(String productId, String sipIdentity) { + XMLRPCClient client = null; + try { + client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); + } catch (MalformedURLException e) { + Log.e(e); + Log.e("[In-app purchase] Can't reach the server !"); + } + + if (client != null) { + client.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + } + + @Override + public void onResponse(long id, Object result) { + Log.d("[In-app purchase] Server result is " + result); + mListener.onRecoverAccountSuccessful(result.equals("OK")); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + } + }, "recover_account", mGmailAccount, sipIdentity + "@sip.linphone.org"); + } + } + public void destroy() { mContext.unbindService(mServiceConn); } - private boolean isEmailCorrect(String email) { - Pattern emailPattern = Patterns.EMAIL_ADDRESS; - return emailPattern.matcher(email).matches(); - } - - private String getGmailAccount() { + public String getGmailAccount() { Account[] accounts = AccountManager.get(mContext).getAccountsByType("com.google"); for (Account account: accounts) { @@ -336,6 +370,11 @@ public class InAppPurchaseHelper { return null; } + private boolean isEmailCorrect(String email) { + Pattern emailPattern = Patterns.EMAIL_ADDRESS; + return emailPattern.matcher(email).matches(); + } + private Purchasable verifySignatureAndGetExpire(String purchasedData, String signature) { XMLRPCClient client = null; try { @@ -347,16 +386,20 @@ public class InAppPurchaseHelper { if (client != null) { try { Object result = client.call("get_expiration_date", mGmailAccount, purchasedData, signature, "google"); + long longExpire = -1; String expire = (String)result; - if ("-1".equals(expire)) { - Log.e("[In-app purchase] Server failed to validate the payload !"); + + try { + longExpire = Long.parseLong(expire); + } catch (NumberFormatException nfe) { + Log.e("[In-app purchase] Server failure: " + result); return null; } JSONObject json = new JSONObject(purchasedData); String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); Purchasable item = new Purchasable(productId); - item.setExpire(Long.parseLong(expire)); + item.setExpire(longExpire); //TODO parse JSON result to get the purchasable in it return item; } catch (XMLRPCException e) { @@ -369,7 +412,7 @@ public class InAppPurchaseHelper { return null; } - private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, String signature, String username, String password) { + private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, String signature, String username) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -389,9 +432,13 @@ public class InAppPurchaseHelper { @Override public void onResponse(long id, Object result) { try { + long longExpire = -1; String expire = (String)result; - if ("-1".equals(expire)) { - Log.e("[In-app purchase] Server failed to validate the payload !"); + + try { + longExpire = Long.parseLong(expire); + } catch (NumberFormatException nfe) { + Log.e("[In-app purchase] Server failure: " + result); listener.onParsedAndVerifiedSignatureQueryFinished(null); return; } @@ -399,7 +446,7 @@ public class InAppPurchaseHelper { JSONObject json = new JSONObject(purchasedData); String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); Purchasable item = new Purchasable(productId); - item.setExpire(Long.parseLong(expire)); + item.setExpire(longExpire); //TODO parse JSON result to get the purchasable in it listener.onParsedAndVerifiedSignatureQueryFinished(item); return; @@ -414,7 +461,7 @@ public class InAppPurchaseHelper { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } - }, "create_account_from_in_app_purchase", mGmailAccount, username + "@sip.linphone.org", password, purchasedData, signature, "google"); + }, "create_account_from_in_app_purchase", mGmailAccount, username + "@sip.linphone.org", purchasedData, signature, "google"); } } diff --git a/src/org/linphone/purchase/InAppPurchaseListener.java b/src/org/linphone/purchase/InAppPurchaseListener.java index 4047fbe3c..6df279e1d 100644 --- a/src/org/linphone/purchase/InAppPurchaseListener.java +++ b/src/org/linphone/purchase/InAppPurchaseListener.java @@ -46,4 +46,10 @@ public interface InAppPurchaseListener { * @param item the item the user just bought */ void onPurchasedItemConfirmationQueryFinished(Purchasable item); + + /** + * Callback called when the account has been recovered (or not) + * @param true if the recover has been successful, false otherwise + */ + void onRecoverAccountSuccessful(boolean success); } From a7b63d1b9de34da8d2755241ed5307194ec15b22 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 12 May 2015 16:40:24 +0200 Subject: [PATCH 11/56] Added callback for errors for in app purchase manager --- .../purchase/InAppPurchaseActivity.java | 12 +++++++++++ .../purchase/InAppPurchaseHelper.java | 20 +++++++++++++++++++ .../purchase/InAppPurchaseListener.java | 6 ++++++ 3 files changed, 38 insertions(+) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 3e6f13036..862081da1 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -39,6 +39,7 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; /** * @author Sylvain Berfini @@ -137,6 +138,17 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList } }); } + + @Override + public void onError(final String error) { + Log.e(error); + mHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(InAppPurchaseActivity.this, error, Toast.LENGTH_LONG).show(); + } + }); + } private void displayBuySubscriptionButton(Purchasable item) { View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 29ec45e60..10a1fe035 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -101,6 +101,12 @@ public class InAppPurchaseHelper { public static final String SERVER_ERROR_UID_ALREADY_IN_USE = "ERROR_UID_ALREADY_IN_USE"; public static final String SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED = "ERROR_SIGNATURE_VERIFICATION_FAILED"; public static final String SERVER_ERROR_ACCOUNT_ALREADY_EXISTS = "ERROR_ACCOUNT_ALREADY_EXISTS"; + public static final String SERVER_ERROR_UNKNOWN_ERROR = "ERROR_UNKNOWN_ERROR"; + + public static final String CLIENT_ERROR_SUBSCRIPTION_PURCHASE_NOT_AVAILABLE = "SUBSCRIPTION_PURCHASE_NOT_AVAILABLE"; + public static final String CLIENT_ERROR_BIND_TO_BILLING_SERVICE_FAILED = "BIND_TO_BILLING_SERVICE_FAILED"; + public static final String CLIENT_ERROR_BILLING_SERVICE_UNAVAILABLE = "BILLING_SERVICE_UNAVAILABLE"; + public static final String CLIENT_ERROR_SERVER_NOT_REACHABLE = "SERVER_NOT_REACHABLE"; private Context mContext; private InAppPurchaseListener mListener; @@ -150,6 +156,7 @@ public class InAppPurchaseHelper { int response = mService.isBillingSupported(API_VERSION, packageName, ITEM_TYPE_SUBS); if (response != RESPONSE_RESULT_OK || mGmailAccount == null) { Log.e("[In-app purchase] Error: Subscriptions aren't supported!"); + mListener.onError(CLIENT_ERROR_SUBSCRIPTION_PURCHASE_NOT_AVAILABLE); } else { mListener.onServiceAvailableForQueries(); } @@ -165,9 +172,11 @@ public class InAppPurchaseHelper { boolean ok = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); if (!ok) { Log.e("[In-app purchase] Error: Bind service failed"); + mListener.onError(CLIENT_ERROR_BIND_TO_BILLING_SERVICE_FAILED); } } else { Log.e("[In-app purchase] Error: Billing service unavailable on device."); + mListener.onError(CLIENT_ERROR_BILLING_SERVICE_UNAVAILABLE); } } @@ -205,6 +214,7 @@ public class InAppPurchaseHelper { } } else { Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response)); + mListener.onError(responseCodeToErrorMessage(response)); } } @@ -259,6 +269,7 @@ public class InAppPurchaseHelper { } } else { Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response)); + mListener.onError(responseCodeToErrorMessage(response)); } } } while (continuationToken != null); @@ -317,6 +328,7 @@ public class InAppPurchaseHelper { }, purchaseData, signature, username); } else { Log.e("[In-app purchase] Error: resultCode is " + resultCode + " and responseCode is " + responseCodeToErrorMessage(responseCode)); + mListener.onError(responseCodeToErrorMessage(responseCode)); } } } @@ -328,6 +340,7 @@ public class InAppPurchaseHelper { } catch (MalformedURLException e) { Log.e(e); Log.e("[In-app purchase] Can't reach the server !"); + mListener.onError(CLIENT_ERROR_SERVER_NOT_REACHABLE); } if (client != null) { @@ -336,6 +349,7 @@ public class InAppPurchaseHelper { public void onServerError(long id, XMLRPCServerException error) { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); } @Override @@ -348,6 +362,7 @@ public class InAppPurchaseHelper { public void onError(long id, XMLRPCException error) { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); } }, "recover_account", mGmailAccount, sipIdentity + "@sip.linphone.org"); } @@ -393,6 +408,7 @@ public class InAppPurchaseHelper { longExpire = Long.parseLong(expire); } catch (NumberFormatException nfe) { Log.e("[In-app purchase] Server failure: " + result); + mListener.onError(SERVER_ERROR_UNKNOWN_ERROR); return null; } @@ -419,6 +435,7 @@ public class InAppPurchaseHelper { } catch (MalformedURLException e) { Log.e(e); Log.e("[In-app purchase] Can't reach the server !"); + mListener.onError(CLIENT_ERROR_SERVER_NOT_REACHABLE); } if (client != null) { @@ -427,6 +444,7 @@ public class InAppPurchaseHelper { public void onServerError(long id, XMLRPCServerException error) { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); } @Override @@ -440,6 +458,7 @@ public class InAppPurchaseHelper { } catch (NumberFormatException nfe) { Log.e("[In-app purchase] Server failure: " + result); listener.onParsedAndVerifiedSignatureQueryFinished(null); + mListener.onError(SERVER_ERROR_UNKNOWN_ERROR); return; } @@ -454,6 +473,7 @@ public class InAppPurchaseHelper { Log.e(e); } Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); } @Override diff --git a/src/org/linphone/purchase/InAppPurchaseListener.java b/src/org/linphone/purchase/InAppPurchaseListener.java index 6df279e1d..a65bcbb2b 100644 --- a/src/org/linphone/purchase/InAppPurchaseListener.java +++ b/src/org/linphone/purchase/InAppPurchaseListener.java @@ -52,4 +52,10 @@ public interface InAppPurchaseListener { * @param true if the recover has been successful, false otherwise */ void onRecoverAccountSuccessful(boolean success); + + /** + * Callback called when an error occurred. + * @param error the error that occurred + */ + void onError(String error); } From 6efd4477662fc58a7c9f7d5b1aeb9f57355e294a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 13 May 2015 12:11:55 +0200 Subject: [PATCH 12/56] Updated liblinphone --- submodules/linphone | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/linphone b/submodules/linphone index f1b42dc29..3b9a71708 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit f1b42dc2995fa4f934c4313e94aca0bc63d58c7a +Subproject commit 3b9a717089de8d5ff5412021b2ce194c66013443 From 036dce53a007775137c46c5c345bd5d33dbcee99 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 13 May 2015 17:19:02 +0200 Subject: [PATCH 13/56] Added email account parameter for in app purchase account creation --- src/org/linphone/purchase/InAppPurchaseHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 10a1fe035..35ecf04f8 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -481,7 +481,7 @@ public class InAppPurchaseHelper { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } - }, "create_account_from_in_app_purchase", mGmailAccount, username + "@sip.linphone.org", purchasedData, signature, "google"); + }, "create_account_from_in_app_purchase", mGmailAccount, username + "@sip.linphone.org", purchasedData, signature, "google", mGmailAccount); } } From 817cc18b12468d893842aa128237e6b52e2cf525 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 15 May 2015 15:01:34 +0200 Subject: [PATCH 14/56] Fix issue with upload image --- src/org/linphone/ChatFragment.java | 13 ++++++++----- submodules/linphone | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/org/linphone/ChatFragment.java b/src/org/linphone/ChatFragment.java index 8f7c9d192..05bbd0cd4 100644 --- a/src/org/linphone/ChatFragment.java +++ b/src/org/linphone/ChatFragment.java @@ -551,10 +551,9 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC protected byte[] doInBackground(Bitmap... params) { Bitmap bm = params[0]; - if (bm.getWidth() > bm.getHeight() && bm.getWidth() > SIZE_MAX) { + if (bm.getWidth() >= bm.getHeight() && bm.getWidth() > SIZE_MAX) { bm = Bitmap.createScaledBitmap(bm, SIZE_MAX, (SIZE_MAX * bm.getHeight()) / bm.getWidth(), false); - } else if (bm.getHeight() > bm.getWidth() && bm.getHeight() > SIZE_MAX) { - + } else if (bm.getHeight() >= bm.getWidth() && bm.getHeight() > SIZE_MAX) { bm = Bitmap.createScaledBitmap(bm, (SIZE_MAX * bm.getWidth()) / bm.getHeight(), SIZE_MAX, false); } @@ -741,8 +740,12 @@ public class ChatFragment extends Fragment implements OnClickListener, LinphoneC if (mUploadingImageStream != null && size > 0) { byte[] data = new byte[size]; int read = mUploadingImageStream.read(data, 0, size); - bufferToFill.setContent(data); - bufferToFill.setSize(read); + if (read > 0) { + bufferToFill.setContent(data); + bufferToFill.setSize(read); + } else { + Log.e("Error, upload task asking for more bytes(" + size + ") than available (" + mUploadingImageStream.available() + ")"); + } } } diff --git a/submodules/linphone b/submodules/linphone index 3b9a71708..8eb0f91a9 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit 3b9a717089de8d5ff5412021b2ce194c66013443 +Subproject commit 8eb0f91a9291964781db0658e6cd25a4b1f54a98 From 83281c4f795dc916a6e5007a7a02ec4040279d58 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 19 May 2015 16:04:53 +0200 Subject: [PATCH 15/56] Update to match few changes in app purchase API --- src/org/linphone/purchase/InAppPurchaseHelper.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 35ecf04f8..70bae9101 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -333,7 +333,7 @@ public class InAppPurchaseHelper { } } - public void recoverAccount(String productId, String sipIdentity) { + public void recoverAccount(String productId, String sipUsername) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -364,7 +364,7 @@ public class InAppPurchaseHelper { Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); } - }, "recover_account", mGmailAccount, sipIdentity + "@sip.linphone.org"); + }, "recover_account", mGmailAccount, sipUsername); } } @@ -481,7 +481,7 @@ public class InAppPurchaseHelper { Log.e(error); Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); } - }, "create_account_from_in_app_purchase", mGmailAccount, username + "@sip.linphone.org", purchasedData, signature, "google", mGmailAccount); + }, "create_account_from_in_app_purchase", mGmailAccount, username, purchasedData, signature, "google", mGmailAccount); } } From b61f0018e7bc4bc33f3cd28a8b7dda5f8b8a0686 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 20 May 2015 11:32:00 +0200 Subject: [PATCH 16/56] Added activate account API to in app purchase --- .../purchase/InAppPurchaseHelper.java | 43 +++++++++++++++++-- .../purchase/InAppPurchaseListener.java | 8 +++- src/org/linphone/purchase/Purchasable.java | 15 +++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 70bae9101..44623040e 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -333,7 +333,7 @@ public class InAppPurchaseHelper { } } - public void recoverAccount(String productId, String sipUsername) { + public void recoverAccount(String sipUsername) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -354,7 +354,7 @@ public class InAppPurchaseHelper { @Override public void onResponse(long id, Object result) { - Log.d("[In-app purchase] Server result is " + result); + Log.d("[In-app purchase] recoverAccount server result is " + result); mListener.onRecoverAccountSuccessful(result.equals("OK")); } @@ -368,6 +368,41 @@ public class InAppPurchaseHelper { } } + public void activateAccount(String sipUsername, String purchasedData, String signature) { + XMLRPCClient client = null; + try { + client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); + } catch (MalformedURLException e) { + Log.e(e); + Log.e("[In-app purchase] Can't reach the server !"); + mListener.onError(CLIENT_ERROR_SERVER_NOT_REACHABLE); + } + + if (client != null) { + client.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); + } + + @Override + public void onResponse(long id, Object result) { + Log.d("[In-app purchase] activateAccount server result is " + result); + mListener.onActivateAccountSuccessful(result.equals("OK")); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); + mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); + } + }, "activate_account", mGmailAccount, sipUsername, purchasedData, signature, "google"); + } + } + public void destroy() { mContext.unbindService(mServiceConn); } @@ -416,6 +451,7 @@ public class InAppPurchaseHelper { String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); Purchasable item = new Purchasable(productId); item.setExpire(longExpire); + item.setPayloadAndSignature(purchasedData, signature); //TODO parse JSON result to get the purchasable in it return item; } catch (XMLRPCException e) { @@ -428,7 +464,7 @@ public class InAppPurchaseHelper { return null; } - private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, String signature, String username) { + private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, final String signature, String username) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -466,6 +502,7 @@ public class InAppPurchaseHelper { String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); Purchasable item = new Purchasable(productId); item.setExpire(longExpire); + item.setPayloadAndSignature(purchasedData, signature); //TODO parse JSON result to get the purchasable in it listener.onParsedAndVerifiedSignatureQueryFinished(item); return; diff --git a/src/org/linphone/purchase/InAppPurchaseListener.java b/src/org/linphone/purchase/InAppPurchaseListener.java index a65bcbb2b..8f44ec751 100644 --- a/src/org/linphone/purchase/InAppPurchaseListener.java +++ b/src/org/linphone/purchase/InAppPurchaseListener.java @@ -49,10 +49,16 @@ public interface InAppPurchaseListener { /** * Callback called when the account has been recovered (or not) - * @param true if the recover has been successful, false otherwise + * @param success true if the recover has been successful, false otherwise */ void onRecoverAccountSuccessful(boolean success); + /** + * Callback called when the account has been activated (or not) + * @param success true if the activation has been successful, false otherwise + */ + void onActivateAccountSuccessful(boolean success); + /** * Callback called when an error occurred. * @param error the error that occurred diff --git a/src/org/linphone/purchase/Purchasable.java b/src/org/linphone/purchase/Purchasable.java index 3228757d0..30db76dae 100644 --- a/src/org/linphone/purchase/Purchasable.java +++ b/src/org/linphone/purchase/Purchasable.java @@ -29,6 +29,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. public class Purchasable { private String id, title, description, price; private long expire; + private String purchasePayload, purchasePayloadSignature; public Purchasable(String id) { this.id = id; @@ -79,4 +80,18 @@ public class Purchasable { this.expire = expire; return this; } + + public Purchasable setPayloadAndSignature(String payload, String signature) { + this.purchasePayload = payload; + this.purchasePayloadSignature = signature; + return this; + } + + public String getPayload() { + return this.purchasePayload; + } + + public String getPayloadSignature() { + return this.purchasePayloadSignature; + } } From ff64caaa8f51455b7a5d6043771d5aaf693f1c45 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 20 May 2015 13:54:27 +0200 Subject: [PATCH 17/56] Configure list of purchasable items in linphonerc --- res/raw/linphonerc_factory | 3 ++- src/org/linphone/LinphonePreferences.java | 15 +++++++++++++++ .../linphone/purchase/InAppPurchaseHelper.java | 4 +--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/res/raw/linphonerc_factory b/res/raw/linphonerc_factory index 5aec262ee..c7b88ec41 100644 --- a/res/raw/linphonerc_factory +++ b/res/raw/linphonerc_factory @@ -33,4 +33,5 @@ log_collection_upload_server_url=https://www.linphone.org:444/lft.php user_certificates_path=/data/data/org.linphone/files [in-app-purchase] -server_url=https://www.linphone.org/inapp.php \ No newline at end of file +server_url=https://www.linphone.org/inapp.php +purchasable_items_ids=test_account_subscription \ No newline at end of file diff --git a/src/org/linphone/LinphonePreferences.java b/src/org/linphone/LinphonePreferences.java index db0d0043b..cb5d4d29d 100644 --- a/src/org/linphone/LinphonePreferences.java +++ b/src/org/linphone/LinphonePreferences.java @@ -19,6 +19,8 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +import java.util.ArrayList; + import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneAddress.TransportType; import org.linphone.core.LinphoneAuthInfo; @@ -1155,6 +1157,19 @@ public class LinphonePreferences { public String getInAppPurchaseValidatingServerUrl() { return getConfig().getString("in-app-purchase", "server_url", null); } + + public ArrayList getInAppPurchasables() { + ArrayList purchasables = new ArrayList(); + String list = getConfig().getString("in-app-purchase", "purchasable_items_ids", null); + if (list != null) { + for(String purchasable : list.split(";")) { + if (purchasable.length() > 0) { + purchasables.add(purchasable); + } + } + } + return purchasables; + } public String getDebugPopupAddress(){ return getConfig().getString("app", "debug_popup_magic", null); diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 44623040e..47d72d9a9 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -55,7 +55,6 @@ import de.timroes.axmlrpc.XMLRPCServerException; */ public class InAppPurchaseHelper { public static final int API_VERSION = 3; - public static final String TEST_ITEM = "test_account_subscription"; public static final int ACTIVITY_RESULT_CODE_PURCHASE_ITEM = 11089; public static final String SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST"; @@ -182,8 +181,7 @@ public class InAppPurchaseHelper { private ArrayList getAvailableItemsForPurchase() { ArrayList products = new ArrayList(); - ArrayList skuList = new ArrayList (); - skuList.add(TEST_ITEM); + ArrayList skuList = LinphonePreferences.instance().getInAppPurchasables(); Bundle querySkus = new Bundle(); querySkus.putStringArrayList(SKU_DETAILS_ITEM_LIST, skuList); From 51eb29d9319b03ed73a7aa676e4918c77f98c980 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Wed, 20 May 2015 14:16:19 +0200 Subject: [PATCH 18/56] Fix inapp purchase methods in InAppActivity --- src/org/linphone/purchase/InAppPurchaseActivity.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 862081da1..5df79e2a6 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -118,7 +118,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList public void onClick(View v) { Purchasable item = (Purchasable) v.getTag(); if (v.equals(recoverAccountButton)) { - inAppPurchaseHelper.recoverAccount(item.getId(), getUsername()); + inAppPurchaseHelper.recoverAccount(getUsername()); } else { inAppPurchaseHelper.purchaseItemAsync(item.getId(), getUsername()); } @@ -149,6 +149,13 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList } }); } + + @Override + public void onActivateAccountSuccessful(boolean success) { + if (success) { + Log.d("[In-app purchase] Account activated"); + } + } private void displayBuySubscriptionButton(Purchasable item) { View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); From 82de18c77523c2cf2f4ee38c91f0d75fe59e9bf9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 20 May 2015 15:44:06 +0200 Subject: [PATCH 19/56] Changes to match new API --- src/org/linphone/purchase/InAppPurchaseActivity.java | 7 ++++++- src/org/linphone/purchase/InAppPurchaseHelper.java | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 5df79e2a6..d917e3805 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -118,7 +118,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList public void onClick(View v) { Purchasable item = (Purchasable) v.getTag(); if (v.equals(recoverAccountButton)) { - inAppPurchaseHelper.recoverAccount(getUsername()); + inAppPurchaseHelper.recoverAccount(getUsername(), item.getPayload(), item.getPayloadSignature()); } else { inAppPurchaseHelper.purchaseItemAsync(item.getId(), getUsername()); } @@ -139,6 +139,11 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList }); } + @Override + public void onActivateAccountSuccessful(boolean success) { + + } + @Override public void onError(final String error) { Log.e(error); diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 47d72d9a9..393fa59e7 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -331,7 +331,7 @@ public class InAppPurchaseHelper { } } - public void recoverAccount(String sipUsername) { + public void recoverAccount(String sipUsername, String purchasedData, String signature) { XMLRPCClient client = null; try { client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); @@ -362,7 +362,7 @@ public class InAppPurchaseHelper { Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); } - }, "recover_account", mGmailAccount, sipUsername); + }, "recover_account", mGmailAccount, sipUsername, purchasedData, signature, "google", mGmailAccount); } } From 2b8da0c053e974ffb3bfcd761771cff350e0f919 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 4 Jun 2015 14:56:48 +0200 Subject: [PATCH 20/56] Added a XmlRpc helper to separate xmlrpc calls from those of the in-app purchase helper --- .../purchase/InAppPurchaseActivity.java | 5 - .../purchase/InAppPurchaseHelper.java | 163 ++--- src/org/linphone/xmlrpc/XmlRpcHelper.java | 626 ++++++++++++++++++ src/org/linphone/xmlrpc/XmlRpcListener.java | 15 + .../linphone/xmlrpc/XmlRpcListenerBase.java | 70 ++ 5 files changed, 756 insertions(+), 123 deletions(-) create mode 100644 src/org/linphone/xmlrpc/XmlRpcHelper.java create mode 100644 src/org/linphone/xmlrpc/XmlRpcListener.java create mode 100644 src/org/linphone/xmlrpc/XmlRpcListenerBase.java diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index d917e3805..27db5dd46 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -139,11 +139,6 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList }); } - @Override - public void onActivateAccountSuccessful(boolean success) { - - } - @Override public void onError(final String error) { Log.e(error); diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 393fa59e7..6027281df 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -27,6 +27,8 @@ import org.json.JSONException; import org.json.JSONObject; import org.linphone.LinphonePreferences; import org.linphone.mediastream.Log; +import org.linphone.xmlrpc.XmlRpcHelper; +import org.linphone.xmlrpc.XmlRpcListenerBase; import android.accounts.Account; import android.accounts.AccountManager; @@ -45,10 +47,8 @@ import android.util.Patterns; import com.android.vending.billing.IInAppBillingService; -import de.timroes.axmlrpc.XMLRPCCallback; import de.timroes.axmlrpc.XMLRPCClient; import de.timroes.axmlrpc.XMLRPCException; -import de.timroes.axmlrpc.XMLRPCServerException; /** * @author Sylvain Berfini @@ -332,73 +332,23 @@ public class InAppPurchaseHelper { } public void recoverAccount(String sipUsername, String purchasedData, String signature) { - XMLRPCClient client = null; - try { - client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); - } catch (MalformedURLException e) { - Log.e(e); - Log.e("[In-app purchase] Can't reach the server !"); - mListener.onError(CLIENT_ERROR_SERVER_NOT_REACHABLE); - } - - if (client != null) { - client.callAsync(new XMLRPCCallback() { - @Override - public void onServerError(long id, XMLRPCServerException error) { - Log.e(error); - Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); - mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); - } - - @Override - public void onResponse(long id, Object result) { - Log.d("[In-app purchase] recoverAccount server result is " + result); - mListener.onRecoverAccountSuccessful(result.equals("OK")); - } - - @Override - public void onError(long id, XMLRPCException error) { - Log.e(error); - Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); - mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); - } - }, "recover_account", mGmailAccount, sipUsername, purchasedData, signature, "google", mGmailAccount); - } + XmlRpcHelper helper = new XmlRpcHelper(); + helper.createAccountAsync(new XmlRpcListenerBase() { + @Override + public void onAccountCreated(String result) { + mListener.onRecoverAccountSuccessful(true); + } + }, mGmailAccount, sipUsername, purchasedData, signature, mGmailAccount, null); } public void activateAccount(String sipUsername, String purchasedData, String signature) { - XMLRPCClient client = null; - try { - client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); - } catch (MalformedURLException e) { - Log.e(e); - Log.e("[In-app purchase] Can't reach the server !"); - mListener.onError(CLIENT_ERROR_SERVER_NOT_REACHABLE); - } - - if (client != null) { - client.callAsync(new XMLRPCCallback() { - @Override - public void onServerError(long id, XMLRPCServerException error) { - Log.e(error); - Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); - mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); - } - - @Override - public void onResponse(long id, Object result) { - Log.d("[In-app purchase] activateAccount server result is " + result); - mListener.onActivateAccountSuccessful(result.equals("OK")); - } - - @Override - public void onError(long id, XMLRPCException error) { - Log.e(error); - Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); - mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); - } - }, "activate_account", mGmailAccount, sipUsername, purchasedData, signature, "google"); - } + XmlRpcHelper helper = new XmlRpcHelper(); + helper.activateAccountAsync(new XmlRpcListenerBase() { + @Override + public void onAccountActivated(String result) { + mListener.onActivateAccountSuccessful(true); + } + }, mGmailAccount, sipUsername, purchasedData, signature); } public void destroy() { @@ -434,6 +384,8 @@ public class InAppPurchaseHelper { if (client != null) { try { Object result = client.call("get_expiration_date", mGmailAccount, purchasedData, signature, "google"); + Log.e(purchasedData); + Log.e(signature); long longExpire = -1; String expire = (String)result; @@ -463,61 +415,36 @@ public class InAppPurchaseHelper { } private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, final String signature, String username) { - XMLRPCClient client = null; - try { - client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); - } catch (MalformedURLException e) { - Log.e(e); - Log.e("[In-app purchase] Can't reach the server !"); - mListener.onError(CLIENT_ERROR_SERVER_NOT_REACHABLE); - } - - if (client != null) { - client.callAsync(new XMLRPCCallback() { - @Override - public void onServerError(long id, XMLRPCServerException error) { - Log.e(error); - Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); - mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); - } - - @Override - public void onResponse(long id, Object result) { + XmlRpcHelper helper = new XmlRpcHelper(); + helper.createAccountAsync(new XmlRpcListenerBase() { + @Override + public void onAccountCreated(String result) { + try { + long longExpire = -1; + String expire = (String)result; + try { - long longExpire = -1; - String expire = (String)result; - - try { - longExpire = Long.parseLong(expire); - } catch (NumberFormatException nfe) { - Log.e("[In-app purchase] Server failure: " + result); - listener.onParsedAndVerifiedSignatureQueryFinished(null); - mListener.onError(SERVER_ERROR_UNKNOWN_ERROR); - return; - } - - JSONObject json = new JSONObject(purchasedData); - String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); - Purchasable item = new Purchasable(productId); - item.setExpire(longExpire); - item.setPayloadAndSignature(purchasedData, signature); - //TODO parse JSON result to get the purchasable in it - listener.onParsedAndVerifiedSignatureQueryFinished(item); - return; - } catch (JSONException e) { - Log.e(e); + longExpire = Long.parseLong(expire); + } catch (NumberFormatException nfe) { + Log.e("[In-app purchase] Server failure: " + result); + listener.onParsedAndVerifiedSignatureQueryFinished(null); + mListener.onError(SERVER_ERROR_UNKNOWN_ERROR); + return; } - Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); - mListener.onError(SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED); + + JSONObject json = new JSONObject(purchasedData); + String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); + Purchasable item = new Purchasable(productId); + item.setExpire(longExpire); + item.setPayloadAndSignature(purchasedData, signature); + //TODO parse JSON result to get the purchasable in it + listener.onParsedAndVerifiedSignatureQueryFinished(item); + return; + } catch (JSONException e) { + Log.e(e); } - - @Override - public void onError(long id, XMLRPCException error) { - Log.e(error); - Log.e("[In-app purchase] Server can't validate the payload and it's signature !"); - } - }, "create_account_from_in_app_purchase", mGmailAccount, username, purchasedData, signature, "google", mGmailAccount); - } + } + }, mGmailAccount, username, purchasedData, signature, mGmailAccount, null); } interface VerifiedSignatureListener { diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java new file mode 100644 index 000000000..12c30ca77 --- /dev/null +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -0,0 +1,626 @@ +package org.linphone.xmlrpc; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.linphone.LinphonePreferences; +import org.linphone.mediastream.Log; + +import de.timroes.axmlrpc.XMLRPCCallback; +import de.timroes.axmlrpc.XMLRPCClient; +import de.timroes.axmlrpc.XMLRPCException; +import de.timroes.axmlrpc.XMLRPCServerException; + +public class XmlRpcHelper { + public static final String OS = "GOOGLE"; + public static final String SERVER_ERROR_INVALID_ACCOUNT = "ERROR_INVALID_ACCOUNT"; + public static final String SERVER_ERROR_PURCHASE_CANCELLED = "ERROR_PURCHASE_CANCELLED"; + public static final String SERVER_ERROR_RECEIPT_PARSING_FAILED = "ERROR_RECEIPT_PARSING_FAILED"; + public static final String SERVER_ERROR_UID_ALREADY_IN_USE = "ERROR_UID_ALREADY_IN_USE"; + public static final String SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED = "ERROR_SIGNATURE_VERIFICATION_FAILED"; + public static final String SERVER_ERROR_ACCOUNT_ALREADY_EXISTS = "ERROR_ACCOUNT_ALREADY_EXISTS"; + public static final String SERVER_ERROR_UNKNOWN_ERROR = "ERROR_UNKNOWN_ERROR"; + + public static final String CLIENT_ERROR_INVALID_SERVER_URL = "INVALID_SERVER_URL"; + public static final String CLIENT_ERROR_SERVER_NOT_REACHABLE = "SERVER_NOT_REACHABLE"; + + private XMLRPCClient mXmlRpcClient; + + public XmlRpcHelper() { + try { + mXmlRpcClient = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); + } catch (MalformedURLException e) { + Log.e(e); + } + } + + public void createAccountAsync(final XmlRpcListener listener, String gmailAccount, String username, String payload, String signature, String email, String password) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onAccountCreated(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "create_account_from_in_app_purchase", gmailAccount, username, payload, signature, OS, email, password == null ? "" : password); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String createAccount(String gmailAccount, String username, String payload, String signature, String email, String password) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("create_account_from_in_app_purchase", gmailAccount, username, payload, signature, OS, email, password == null ? "" : password); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void getAccountExpireAsync(final XmlRpcListener listener, String gmailAccount, String payload, String signature) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onAccountExpireFetched(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "get_expiration_date", gmailAccount, payload, signature, OS); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String getAccountExpire(String gmailAccount, String payload, String signature) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("get_expiration_date", gmailAccount, payload, signature, OS); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void getAccountExpireAsync(final XmlRpcListener listener, String username, String password) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onAccountExpireFetched(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "get_expiration_for_account", username, password, OS); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String getAccountExpire(String username, String password) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("get_expiration_for_account", username, password, OS); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void activateAccountAsync(final XmlRpcListener listener, String gmailAccount, String username, String payload, String signature) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onAccountActivated(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "activate_account", gmailAccount, username, payload, signature, OS); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String activateAccount(String gmailAccount, String username, String payload, String signature) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("activate_account", gmailAccount, username, payload, signature, OS); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void isAccountActivatedAsync(final XmlRpcListener listener, String username) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + if ("OK".equals(result)) { + listener.onAccountActivatedFetched(true); + } else if (!"ERROR_ACCOUNT_NOT_ACTIVATED".equals(result)) { + Log.e(result); + } + listener.onAccountActivatedFetched(false); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "check_account_activated", username); + } + } + + public boolean isAccountActivated(String username) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("check_account_activated", username); + String result = (String)object; + if ("OK".equals(result)) { + return true; + } else if (!"ERROR_ACCOUNT_NOT_ACTIVATED".equals(result)) { + Log.e(result); + } + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return false; + } + + public void isTrialAccountAsync(final XmlRpcListener listener, String username, String password) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + listener.onAccountFetched("OK".equals(result)); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "is_account_paid", username, password, OS); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public boolean isTrialAccount(String username, String password) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("is_account_paid", username, password, OS); + String result = (String)object; + return "OK".equals(result); + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return false; + } + + public void isAccountAsync(final XmlRpcListener listener, String username) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + if ("OK".equals(result)) { + listener.onAccountFetched(true); + } else if (!"ERROR_ACCOUNT_DOESNT_EXIST".equals(result)) { + Log.e(result); + } + listener.onAccountFetched(false); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "check_account_activated", username); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public boolean isAccount(String username) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("check_account_activated", username); + String result = (String)object; + if ("OK".equals(result)) { + return true; + } else if (!"ERROR_ACCOUNT_DOESNT_EXIST".equals(result)) { + Log.e(result); + } + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return false; + } + + public void changeAccountEmailAsync(final XmlRpcListener listener, String username, String password, String newEmail) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onAccountEmailChanged(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "change_email", username, password, newEmail, OS); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String changeAccountEmail(String username, String password, String newEmail) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("change_email", username, password, newEmail, OS); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void changeAccountPasswordAsync(final XmlRpcListener listener, String username, String oldPassword, String newPassword) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onAccountPasswordChanged(result); + return; + } + + listener.onAccountPasswordChanged(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "change_password", username, oldPassword, newPassword, OS); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String changeAccountPassword(String username, String oldPassword, String newPassword) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("change_password", username, oldPassword, newPassword, OS); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void sendRecoverPasswordLinkByEmailAsync(final XmlRpcListener listener, String usernameOrEmail) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onRecoverPasswordLinkSent(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "send_reset_account_password_email", usernameOrEmail); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String sendRecoverPasswordLinkByEmail(String usernameOrEmail) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("send_reset_account_password_email", usernameOrEmail); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void sendActivateAccountLinkByEmailAsync(final XmlRpcListener listener, String usernameOrEmail) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onActivateAccountLinkSent(result); + return; + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "resend_activation_email", usernameOrEmail); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String sendActivateAccountLinkByEmail(String usernameOrEmail) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("resend_activation_email", usernameOrEmail); + String result = (String)object; + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } +} diff --git a/src/org/linphone/xmlrpc/XmlRpcListener.java b/src/org/linphone/xmlrpc/XmlRpcListener.java new file mode 100644 index 000000000..8fbcd1a6a --- /dev/null +++ b/src/org/linphone/xmlrpc/XmlRpcListener.java @@ -0,0 +1,15 @@ +package org.linphone.xmlrpc; + +public interface XmlRpcListener { + public void onError(String error); + public void onAccountCreated(String result); + public void onAccountExpireFetched(String result); + public void onAccountActivated(String result); + public void onAccountActivatedFetched(boolean isActivated); + public void onTrialAccountFetched(boolean isTrial); + public void onAccountFetched(boolean isExisting); + public void onAccountEmailChanged(String result); + public void onAccountPasswordChanged(String result); + public void onRecoverPasswordLinkSent(String result); + public void onActivateAccountLinkSent(String result); +} diff --git a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java new file mode 100644 index 000000000..a824c61ce --- /dev/null +++ b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java @@ -0,0 +1,70 @@ +package org.linphone.xmlrpc; + +public class XmlRpcListenerBase implements XmlRpcListener { + @Override + public void onError(String error) { + // TODO Auto-generated method stub + + } + + @Override + public void onAccountCreated(String result) { + // TODO Auto-generated method stub + + } + + @Override + public void onAccountExpireFetched(String result) { + // TODO Auto-generated method stub + + } + + @Override + public void onAccountActivated(String result) { + // TODO Auto-generated method stub + + } + + @Override + public void onAccountActivatedFetched(boolean isActivated) { + // TODO Auto-generated method stub + + } + + @Override + public void onTrialAccountFetched(boolean isTrial) { + // TODO Auto-generated method stub + + } + + @Override + public void onAccountFetched(boolean isExisting) { + // TODO Auto-generated method stub + + } + + @Override + public void onAccountEmailChanged(String result) { + // TODO Auto-generated method stub + + } + + @Override + public void onAccountPasswordChanged(String result) { + // TODO Auto-generated method stub + + } + + @Override + public void onRecoverPasswordLinkSent(String result) { + // TODO Auto-generated method stub + + } + + @Override + public void onActivateAccountLinkSent(String result) { + // TODO Auto-generated method stub + + } + +} From 02e619c480d61826bd4f88411cb3cc244d2a59e9 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 4 Jun 2015 15:54:42 +0200 Subject: [PATCH 21/56] Forgot to replace a method in in app helper + fix wrong os constant --- res/raw/linphonerc_factory | 2 +- .../purchase/InAppPurchaseHelper.java | 71 ++++++------------- src/org/linphone/xmlrpc/XmlRpcHelper.java | 2 +- 3 files changed, 24 insertions(+), 51 deletions(-) diff --git a/res/raw/linphonerc_factory b/res/raw/linphonerc_factory index c7b88ec41..944a1cb50 100644 --- a/res/raw/linphonerc_factory +++ b/res/raw/linphonerc_factory @@ -33,5 +33,5 @@ log_collection_upload_server_url=https://www.linphone.org:444/lft.php user_certificates_path=/data/data/org.linphone/files [in-app-purchase] -server_url=https://www.linphone.org/inapp.php +server_url=https://www.linphone.org/xmlrpc.php purchasable_items_ids=test_account_subscription \ No newline at end of file diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 6027281df..25fbf2f63 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -18,8 +18,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -import java.net.MalformedURLException; -import java.net.URL; import java.util.ArrayList; import java.util.regex.Pattern; @@ -47,9 +45,6 @@ import android.util.Patterns; import com.android.vending.billing.IInAppBillingService; -import de.timroes.axmlrpc.XMLRPCClient; -import de.timroes.axmlrpc.XMLRPCException; - /** * @author Sylvain Berfini */ @@ -94,18 +89,9 @@ public class InAppPurchaseHelper { public static final String PURCHASE_DETAILS_PAYLOAD = "developerPayload"; public static final String PURCHASE_DETAILS_PURCHASE_TOKEN = "purchaseToken"; - public static final String SERVER_ERROR_INVALID_ACCOUNT = "ERROR_INVALID_ACCOUNT"; - public static final String SERVER_ERROR_PURCHASE_CANCELLED = "ERROR_PURCHASE_CANCELLED"; - public static final String SERVER_ERROR_RECEIPT_PARSING_FAILED = "ERROR_RECEIPT_PARSING_FAILED"; - public static final String SERVER_ERROR_UID_ALREADY_IN_USE = "ERROR_UID_ALREADY_IN_USE"; - public static final String SERVER_ERROR_SIGNATURE_VERIFICATION_FAILED = "ERROR_SIGNATURE_VERIFICATION_FAILED"; - public static final String SERVER_ERROR_ACCOUNT_ALREADY_EXISTS = "ERROR_ACCOUNT_ALREADY_EXISTS"; - public static final String SERVER_ERROR_UNKNOWN_ERROR = "ERROR_UNKNOWN_ERROR"; - public static final String CLIENT_ERROR_SUBSCRIPTION_PURCHASE_NOT_AVAILABLE = "SUBSCRIPTION_PURCHASE_NOT_AVAILABLE"; public static final String CLIENT_ERROR_BIND_TO_BILLING_SERVICE_FAILED = "BIND_TO_BILLING_SERVICE_FAILED"; public static final String CLIENT_ERROR_BILLING_SERVICE_UNAVAILABLE = "BILLING_SERVICE_UNAVAILABLE"; - public static final String CLIENT_ERROR_SERVER_NOT_REACHABLE = "SERVER_NOT_REACHABLE"; private Context mContext; private InAppPurchaseListener mListener; @@ -374,43 +360,30 @@ public class InAppPurchaseHelper { } private Purchasable verifySignatureAndGetExpire(String purchasedData, String signature) { - XMLRPCClient client = null; + XmlRpcHelper helper = new XmlRpcHelper(); + Object result = helper.getAccountExpire(mGmailAccount, purchasedData, signature); + long longExpire = -1; + String expire = (String)result; + try { - client = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl())); - } catch (MalformedURLException e) { + longExpire = Long.parseLong(expire); + } catch (NumberFormatException nfe) { + Log.e("[In-app purchase] Server failure: " + result); + mListener.onError(expire); + return null; + } + + try { + JSONObject json = new JSONObject(purchasedData); + String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); + Purchasable item = new Purchasable(productId); + item.setExpire(longExpire); + item.setPayloadAndSignature(purchasedData, signature); + //TODO parse JSON result to get the purchasable in it + return item; + } catch (JSONException e) { Log.e(e); } - - if (client != null) { - try { - Object result = client.call("get_expiration_date", mGmailAccount, purchasedData, signature, "google"); - Log.e(purchasedData); - Log.e(signature); - long longExpire = -1; - String expire = (String)result; - - try { - longExpire = Long.parseLong(expire); - } catch (NumberFormatException nfe) { - Log.e("[In-app purchase] Server failure: " + result); - mListener.onError(SERVER_ERROR_UNKNOWN_ERROR); - return null; - } - - JSONObject json = new JSONObject(purchasedData); - String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); - Purchasable item = new Purchasable(productId); - item.setExpire(longExpire); - item.setPayloadAndSignature(purchasedData, signature); - //TODO parse JSON result to get the purchasable in it - return item; - } catch (XMLRPCException e) { - Log.e(e); - } catch (JSONException e) { - Log.e(e); - } - } - return null; } @@ -428,7 +401,7 @@ public class InAppPurchaseHelper { } catch (NumberFormatException nfe) { Log.e("[In-app purchase] Server failure: " + result); listener.onParsedAndVerifiedSignatureQueryFinished(null); - mListener.onError(SERVER_ERROR_UNKNOWN_ERROR); + mListener.onError(result); return; } diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java index 12c30ca77..9e4879b13 100644 --- a/src/org/linphone/xmlrpc/XmlRpcHelper.java +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -12,7 +12,7 @@ import de.timroes.axmlrpc.XMLRPCException; import de.timroes.axmlrpc.XMLRPCServerException; public class XmlRpcHelper { - public static final String OS = "GOOGLE"; + public static final String OS = "google"; public static final String SERVER_ERROR_INVALID_ACCOUNT = "ERROR_INVALID_ACCOUNT"; public static final String SERVER_ERROR_PURCHASE_CANCELLED = "ERROR_PURCHASE_CANCELLED"; public static final String SERVER_ERROR_RECEIPT_PARSING_FAILED = "ERROR_RECEIPT_PARSING_FAILED"; From 74c9483b975a2367ac0a2c0d04e25df891bbbd23 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 8 Jun 2015 17:20:22 +0200 Subject: [PATCH 22/56] Added debug logs in XmlRpcHelper + fix isTrialAccount methods --- src/org/linphone/xmlrpc/XmlRpcHelper.java | 35 +++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java index 9e4879b13..cb2f9d45d 100644 --- a/src/org/linphone/xmlrpc/XmlRpcHelper.java +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -46,6 +46,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("createAccountAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -74,6 +75,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("create_account_from_in_app_purchase", gmailAccount, username, payload, signature, OS, email, password == null ? "" : password); String result = (String)object; + Log.d("createAccount: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -102,6 +104,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("getAccountExpireAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -130,6 +133,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("get_expiration_date", gmailAccount, payload, signature, OS); String result = (String)object; + Log.d("getAccountExpire: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -158,6 +162,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("getAccountExpireAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -186,6 +191,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("get_expiration_for_account", username, password, OS); String result = (String)object; + Log.d("getAccountExpire: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -214,6 +220,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("activateAccountAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -242,6 +249,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("activate_account", gmailAccount, username, payload, signature, OS); String result = (String)object; + Log.d("activateAccount: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -270,6 +278,8 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("isAccountActivatedAsync: " + result); + if ("OK".equals(result)) { listener.onAccountActivatedFetched(true); } else if (!"ERROR_ACCOUNT_NOT_ACTIVATED".equals(result)) { @@ -293,6 +303,8 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("check_account_activated", username); String result = (String)object; + Log.d("isAccountActivated: " + result); + if ("OK".equals(result)) { return true; } else if (!"ERROR_ACCOUNT_NOT_ACTIVATED".equals(result)) { @@ -319,7 +331,12 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; - listener.onAccountFetched("OK".equals(result)); + Log.d("isTrialAccountAsync: " + result); + + if (!"ERROR_TOKEN_NOT_FOUND".equals(result) && !"OK".equals(result)) { + listener.onError(result); + } + listener.onAccountFetched("ERROR_TOKEN_NOT_FOUND".equals(result)); } @Override @@ -339,7 +356,9 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("is_account_paid", username, password, OS); String result = (String)object; - return "OK".equals(result); + Log.d("isTrialAccount: " + result); + + return "ERROR_TOKEN_NOT_FOUND".equals(result); } catch (XMLRPCException e) { Log.e(e); } @@ -361,6 +380,8 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("isAccountAsync: " + result); + if ("OK".equals(result)) { listener.onAccountFetched(true); } else if (!"ERROR_ACCOUNT_DOESNT_EXIST".equals(result)) { @@ -386,6 +407,8 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("check_account_activated", username); String result = (String)object; + Log.d("isAccount: " + result); + if ("OK".equals(result)) { return true; } else if (!"ERROR_ACCOUNT_DOESNT_EXIST".equals(result)) { @@ -412,6 +435,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("changeAccountEmailAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -440,6 +464,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("change_email", username, password, newEmail, OS); String result = (String)object; + Log.d("changeAccountEmail: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -468,6 +493,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("changeAccountPasswordAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -496,6 +522,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("change_password", username, oldPassword, newPassword, OS); String result = (String)object; + Log.d("changeAccountPassword: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -524,6 +551,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("sendRecoverPasswordLinkByEmailAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -552,6 +580,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("send_reset_account_password_email", usernameOrEmail); String result = (String)object; + Log.d("sendRecoverPasswordLinkByEmail: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -580,6 +609,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; + Log.d("sendActivateAccountLinkByEmailAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -608,6 +638,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("resend_activation_email", usernameOrEmail); String result = (String)object; + Log.d("sendActivateAccountLinkByEmail: " + result); if (result.startsWith("ERROR_")) { Log.e(result); From 7828925e30c53bf92d1b26178c91d3333f381946 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 9 Jun 2015 16:21:52 +0200 Subject: [PATCH 23/56] Fix onError callback not being called --- src/org/linphone/xmlrpc/XmlRpcHelper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java index cb2f9d45d..e48cd913e 100644 --- a/src/org/linphone/xmlrpc/XmlRpcHelper.java +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -284,6 +284,7 @@ public class XmlRpcHelper { listener.onAccountActivatedFetched(true); } else if (!"ERROR_ACCOUNT_NOT_ACTIVATED".equals(result)) { Log.e(result); + listener.onError(result); } listener.onAccountActivatedFetched(false); return; @@ -386,6 +387,7 @@ public class XmlRpcHelper { listener.onAccountFetched(true); } else if (!"ERROR_ACCOUNT_DOESNT_EXIST".equals(result)) { Log.e(result); + listener.onError(result); } listener.onAccountFetched(false); } @@ -497,7 +499,7 @@ public class XmlRpcHelper { if (result.startsWith("ERROR_")) { Log.e(result); - listener.onAccountPasswordChanged(result); + listener.onError(result); return; } From 3633e9af9dbf7e9b660f3c6dbcfe9b52a4adb548 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 10 Jun 2015 17:01:32 +0200 Subject: [PATCH 24/56] Removed useless return + prevent callbacks to be called twice, one for each value --- src/org/linphone/xmlrpc/XmlRpcHelper.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java index e48cd913e..10e3bcfa6 100644 --- a/src/org/linphone/xmlrpc/XmlRpcHelper.java +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -55,7 +55,6 @@ public class XmlRpcHelper { } listener.onAccountCreated(result); - return; } @Override @@ -113,7 +112,6 @@ public class XmlRpcHelper { } listener.onAccountExpireFetched(result); - return; } @Override @@ -171,7 +169,6 @@ public class XmlRpcHelper { } listener.onAccountExpireFetched(result); - return; } @Override @@ -282,12 +279,12 @@ public class XmlRpcHelper { if ("OK".equals(result)) { listener.onAccountActivatedFetched(true); + return; } else if (!"ERROR_ACCOUNT_NOT_ACTIVATED".equals(result)) { Log.e(result); listener.onError(result); } listener.onAccountActivatedFetched(false); - return; } @Override @@ -385,6 +382,7 @@ public class XmlRpcHelper { if ("OK".equals(result)) { listener.onAccountFetched(true); + return; } else if (!"ERROR_ACCOUNT_DOESNT_EXIST".equals(result)) { Log.e(result); listener.onError(result); @@ -446,7 +444,6 @@ public class XmlRpcHelper { } listener.onAccountEmailChanged(result); - return; } @Override @@ -504,7 +501,6 @@ public class XmlRpcHelper { } listener.onAccountPasswordChanged(result); - return; } @Override @@ -562,7 +558,6 @@ public class XmlRpcHelper { } listener.onRecoverPasswordLinkSent(result); - return; } @Override @@ -620,7 +615,6 @@ public class XmlRpcHelper { } listener.onActivateAccountLinkSent(result); - return; } @Override From c73ebbb0c85e37315e62b4f4320bb35eaabac887 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 11 Jun 2015 10:39:23 +0200 Subject: [PATCH 25/56] Added methods & class to help with in app purchase --- src/org/linphone/LinphonePreferences.java | 21 +++++++++ .../purchase/InAppPurchaseListenerBase.java | 47 +++++++++++++++++++ src/org/linphone/purchase/Purchasable.java | 13 ++++- .../linphone/xmlrpc/XmlRpcListenerBase.java | 1 - 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/org/linphone/purchase/InAppPurchaseListenerBase.java diff --git a/src/org/linphone/LinphonePreferences.java b/src/org/linphone/LinphonePreferences.java index cb5d4d29d..8677bf181 100644 --- a/src/org/linphone/LinphonePreferences.java +++ b/src/org/linphone/LinphonePreferences.java @@ -35,6 +35,7 @@ import org.linphone.core.LinphoneProxyConfig; import org.linphone.core.LpConfig; import org.linphone.core.TunnelConfig; import org.linphone.mediastream.Log; +import org.linphone.purchase.Purchasable; import android.content.Context; @@ -1158,6 +1159,26 @@ public class LinphonePreferences { return getConfig().getString("in-app-purchase", "server_url", null); } + public Purchasable getInAppPurchasedItem() { + String id = getConfig().getString("in-app-purchase", "purchase_item_id", null); + String payload = getConfig().getString("in-app-purchase", "purchase_item_payload", null); + String signature = getConfig().getString("in-app-purchase", "purchase_item_signature", null); + String username = getConfig().getString("in-app-purchase", "purchase_item_username", null); + + Purchasable item = new Purchasable(id).setPayloadAndSignature(payload, signature).setUserData(username); + return item; + } + + public void setInAppPurchasedItem(Purchasable item) { + if (item == null) + return; + + getConfig().setString("in-app-purchase", "purchase_item_id", item.getId()); + getConfig().setString("in-app-purchase", "purchase_item_payload", item.getPayload()); + getConfig().setString("in-app-purchase", "purchase_item_signature", item.getPayloadSignature()); + getConfig().setString("in-app-purchase", "purchase_item_username", item.getUserData()); + } + public ArrayList getInAppPurchasables() { ArrayList purchasables = new ArrayList(); String list = getConfig().getString("in-app-purchase", "purchasable_items_ids", null); diff --git a/src/org/linphone/purchase/InAppPurchaseListenerBase.java b/src/org/linphone/purchase/InAppPurchaseListenerBase.java new file mode 100644 index 000000000..06b054f13 --- /dev/null +++ b/src/org/linphone/purchase/InAppPurchaseListenerBase.java @@ -0,0 +1,47 @@ +package org.linphone.purchase; + +import java.util.ArrayList; + +public class InAppPurchaseListenerBase implements InAppPurchaseListener { + @Override + public void onServiceAvailableForQueries() { + // TODO Auto-generated method stub + + } + + @Override + public void onAvailableItemsForPurchaseQueryFinished(ArrayList items) { + // TODO Auto-generated method stub + + } + + @Override + public void onPurchasedItemsQueryFinished(ArrayList items) { + // TODO Auto-generated method stub + + } + + @Override + public void onPurchasedItemConfirmationQueryFinished(Purchasable item) { + // TODO Auto-generated method stub + + } + + @Override + public void onRecoverAccountSuccessful(boolean success) { + // TODO Auto-generated method stub + + } + + @Override + public void onActivateAccountSuccessful(boolean success) { + // TODO Auto-generated method stub + + } + + @Override + public void onError(String error) { + // TODO Auto-generated method stub + + } +} diff --git a/src/org/linphone/purchase/Purchasable.java b/src/org/linphone/purchase/Purchasable.java index 30db76dae..22b8a84a8 100644 --- a/src/org/linphone/purchase/Purchasable.java +++ b/src/org/linphone/purchase/Purchasable.java @@ -4,6 +4,7 @@ import java.sql.Date; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Locale; + /* Purchasable.java Copyright (C) 2015 Belledonne Communications, Grenoble, France @@ -30,7 +31,8 @@ public class Purchasable { private String id, title, description, price; private long expire; private String purchasePayload, purchasePayloadSignature; - + private String userData; + public Purchasable(String id) { this.id = id; } @@ -94,4 +96,13 @@ public class Purchasable { public String getPayloadSignature() { return this.purchasePayloadSignature; } + + public Purchasable setUserData(String data) { + this.userData = data; + return this; + } + + public String getUserData() { + return this.userData; + } } diff --git a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java index a824c61ce..e1752d082 100644 --- a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java +++ b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java @@ -66,5 +66,4 @@ public class XmlRpcListenerBase implements XmlRpcListener { // TODO Auto-generated method stub } - } From 311855ba75705b40a3eeab86690e2a5a4353c53f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Jun 2015 16:40:57 +0200 Subject: [PATCH 26/56] Updated in app purchase helper and xmlrpc helper for new API --- .../purchase/InAppPurchaseActivity.java | 33 ++- .../purchase/InAppPurchaseHelper.java | 132 +++-------- .../purchase/InAppPurchaseListener.java | 4 +- .../purchase/InAppPurchaseListenerBase.java | 2 +- src/org/linphone/xmlrpc/XmlRpcHelper.java | 210 +++++++++++------- src/org/linphone/xmlrpc/XmlRpcListener.java | 2 + .../linphone/xmlrpc/XmlRpcListenerBase.java | 12 + 7 files changed, 209 insertions(+), 186 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index 27db5dd46..fd7bdfdd7 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -25,6 +25,8 @@ import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.core.LinphoneProxyConfig; import org.linphone.mediastream.Log; +import org.linphone.xmlrpc.XmlRpcHelper; +import org.linphone.xmlrpc.XmlRpcListenerBase; import android.app.Activity; import android.content.Intent; @@ -81,6 +83,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList public void onServiceAvailableForQueries() { email.setText(inAppPurchaseHelper.getGmailAccount()); email.setEnabled(false); + inAppPurchaseHelper.getPurchasedItemsAsync(); } @@ -108,9 +111,15 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList } @Override - public void onPurchasedItemConfirmationQueryFinished(Purchasable item) { - if (item != null) { - Log.d("[In-app purchase] Item bought, expires " + item.getExpireDate()); + public void onPurchasedItemConfirmationQueryFinished(boolean success) { + if (success) { + XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(); + xmlRpcHelper.createAccountAsync(new XmlRpcListenerBase() { + @Override + public void onAccountCreated(String result) { + //TODO + } + }, getUsername(), email.getText().toString(), null); } } @@ -118,7 +127,13 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList public void onClick(View v) { Purchasable item = (Purchasable) v.getTag(); if (v.equals(recoverAccountButton)) { - inAppPurchaseHelper.recoverAccount(getUsername(), item.getPayload(), item.getPayloadSignature()); + XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(); + xmlRpcHelper.createAccountAsync(new XmlRpcListenerBase() { + @Override + public void onAccountCreated(String result) { + //TODO + } + }, getUsername(), email.getText().toString(), null); } else { inAppPurchaseHelper.purchaseItemAsync(item.getId(), getUsername()); } @@ -126,7 +141,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data, getUsername()); + inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data, getUsername(), email.getText().toString()); } @Override @@ -163,7 +178,13 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList text.setText("Buy account (" + item.getPrice() + ")"); ImageView image = (ImageView) layout.findViewById(R.id.image); image.setTag(item); - image.setOnClickListener(this); + image.setOnClickListener(this);XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(); + xmlRpcHelper.createAccountAsync(new XmlRpcListenerBase() { + @Override + public void onAccountCreated(String result) { + //TODO + } + }, getUsername(), email.getText().toString(), null); buyItemButton = image; buyItemButton.setEnabled(usernameOk); diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 25fbf2f63..de4ad6c78 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -240,13 +240,13 @@ public class InAppPurchaseHelper { ArrayList purchaseDataList = purchasedItems.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST); ArrayList signatureList = purchasedItems.getStringArrayList(RESPONSE_INAPP_SIGNATURE_LIST); continuationToken = purchasedItems.getString(RESPONSE_INAPP_CONTINUATION_TOKEN); - + for (int i = 0; i < purchaseDataList.size(); ++i) { String purchaseData = purchaseDataList.get(i); String signature = signatureList.get(i); Log.d("[In-app purchase] " + purchaseData); - Purchasable item = verifySignatureAndGetExpire(purchaseData, signature); + Purchasable item = verifySignature(purchaseData, signature); if (item != null) { items.add(item); } @@ -269,6 +269,25 @@ public class InAppPurchaseHelper { }).start(); } + public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data, String username, String email) { + if (requestCode == ACTIVITY_RESULT_CODE_PURCHASE_ITEM) { + int responseCode = data.getIntExtra(RESPONSE_CODE, 0); + + if (resultCode == Activity.RESULT_OK && responseCode == RESPONSE_RESULT_OK) { + String payload = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA); + String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE); + + XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(); + xmlRpcHelper.verifySignatureAsync(new XmlRpcListenerBase() { + @Override + public void onSignatureVerified(boolean success) { + mListener.onPurchasedItemConfirmationQueryFinished(success); + } + }, payload, signature); + } + } + } + private void purchaseItem(String productId, String sipIdentity) { Bundle buyIntentBundle = null; try { @@ -295,48 +314,6 @@ public class InAppPurchaseHelper { }).start(); } - public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data, String username) { - if (requestCode == ACTIVITY_RESULT_CODE_PURCHASE_ITEM) { - int responseCode = data.getIntExtra(RESPONSE_CODE, 0); - String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA); - String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE); - - if (resultCode == Activity.RESULT_OK && responseCode == RESPONSE_RESULT_OK) { - verifySignatureAndCreateAccountAsync(new VerifiedSignatureListener() { - @Override - public void onParsedAndVerifiedSignatureQueryFinished(Purchasable item) { - if (item != null) { - mListener.onPurchasedItemConfirmationQueryFinished(item); - } - } - }, purchaseData, signature, username); - } else { - Log.e("[In-app purchase] Error: resultCode is " + resultCode + " and responseCode is " + responseCodeToErrorMessage(responseCode)); - mListener.onError(responseCodeToErrorMessage(responseCode)); - } - } - } - - public void recoverAccount(String sipUsername, String purchasedData, String signature) { - XmlRpcHelper helper = new XmlRpcHelper(); - helper.createAccountAsync(new XmlRpcListenerBase() { - @Override - public void onAccountCreated(String result) { - mListener.onRecoverAccountSuccessful(true); - } - }, mGmailAccount, sipUsername, purchasedData, signature, mGmailAccount, null); - } - - public void activateAccount(String sipUsername, String purchasedData, String signature) { - XmlRpcHelper helper = new XmlRpcHelper(); - helper.activateAccountAsync(new XmlRpcListenerBase() { - @Override - public void onAccountActivated(String result) { - mListener.onActivateAccountSuccessful(true); - } - }, mGmailAccount, sipUsername, purchasedData, signature); - } - public void destroy() { mContext.unbindService(mServiceConn); } @@ -359,67 +336,22 @@ public class InAppPurchaseHelper { return emailPattern.matcher(email).matches(); } - private Purchasable verifySignatureAndGetExpire(String purchasedData, String signature) { + private Purchasable verifySignature(String payload, String signature) { XmlRpcHelper helper = new XmlRpcHelper(); - Object result = helper.getAccountExpire(mGmailAccount, purchasedData, signature); - long longExpire = -1; - String expire = (String)result; - - try { - longExpire = Long.parseLong(expire); - } catch (NumberFormatException nfe) { - Log.e("[In-app purchase] Server failure: " + result); - mListener.onError(expire); - return null; - } - - try { - JSONObject json = new JSONObject(purchasedData); - String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); - Purchasable item = new Purchasable(productId); - item.setExpire(longExpire); - item.setPayloadAndSignature(purchasedData, signature); - //TODO parse JSON result to get the purchasable in it - return item; - } catch (JSONException e) { - Log.e(e); + if (helper.verifySignature(payload, signature)) { + try { + JSONObject json = new JSONObject(payload); + String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); + Purchasable item = new Purchasable(productId); + item.setPayloadAndSignature(payload, signature); + return item; + } catch (JSONException e) { + Log.e(e); + } } return null; } - private void verifySignatureAndCreateAccountAsync(final VerifiedSignatureListener listener, final String purchasedData, final String signature, String username) { - XmlRpcHelper helper = new XmlRpcHelper(); - helper.createAccountAsync(new XmlRpcListenerBase() { - @Override - public void onAccountCreated(String result) { - try { - long longExpire = -1; - String expire = (String)result; - - try { - longExpire = Long.parseLong(expire); - } catch (NumberFormatException nfe) { - Log.e("[In-app purchase] Server failure: " + result); - listener.onParsedAndVerifiedSignatureQueryFinished(null); - mListener.onError(result); - return; - } - - JSONObject json = new JSONObject(purchasedData); - String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID); - Purchasable item = new Purchasable(productId); - item.setExpire(longExpire); - item.setPayloadAndSignature(purchasedData, signature); - //TODO parse JSON result to get the purchasable in it - listener.onParsedAndVerifiedSignatureQueryFinished(item); - return; - } catch (JSONException e) { - Log.e(e); - } - } - }, mGmailAccount, username, purchasedData, signature, mGmailAccount, null); - } - interface VerifiedSignatureListener { void onParsedAndVerifiedSignatureQueryFinished(Purchasable item); } diff --git a/src/org/linphone/purchase/InAppPurchaseListener.java b/src/org/linphone/purchase/InAppPurchaseListener.java index 8f44ec751..a0f17878a 100644 --- a/src/org/linphone/purchase/InAppPurchaseListener.java +++ b/src/org/linphone/purchase/InAppPurchaseListener.java @@ -43,9 +43,9 @@ public interface InAppPurchaseListener { /** * Callback called when the purchase has been validated by our external server - * @param item the item the user just bought + * @param success true if ok, false otherwise */ - void onPurchasedItemConfirmationQueryFinished(Purchasable item); + void onPurchasedItemConfirmationQueryFinished(boolean success); /** * Callback called when the account has been recovered (or not) diff --git a/src/org/linphone/purchase/InAppPurchaseListenerBase.java b/src/org/linphone/purchase/InAppPurchaseListenerBase.java index 06b054f13..b99a97ddc 100644 --- a/src/org/linphone/purchase/InAppPurchaseListenerBase.java +++ b/src/org/linphone/purchase/InAppPurchaseListenerBase.java @@ -22,7 +22,7 @@ public class InAppPurchaseListenerBase implements InAppPurchaseListener { } @Override - public void onPurchasedItemConfirmationQueryFinished(Purchasable item) { + public void onPurchasedItemConfirmationQueryFinished(boolean success) { // TODO Auto-generated method stub } diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java index 10e3bcfa6..2cd684323 100644 --- a/src/org/linphone/xmlrpc/XmlRpcHelper.java +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -12,7 +12,6 @@ import de.timroes.axmlrpc.XMLRPCException; import de.timroes.axmlrpc.XMLRPCServerException; public class XmlRpcHelper { - public static final String OS = "google"; public static final String SERVER_ERROR_INVALID_ACCOUNT = "ERROR_INVALID_ACCOUNT"; public static final String SERVER_ERROR_PURCHASE_CANCELLED = "ERROR_PURCHASE_CANCELLED"; public static final String SERVER_ERROR_RECEIPT_PARSING_FAILED = "ERROR_RECEIPT_PARSING_FAILED"; @@ -34,7 +33,7 @@ public class XmlRpcHelper { } } - public void createAccountAsync(final XmlRpcListener listener, String gmailAccount, String username, String payload, String signature, String email, String password) { + public void createAccountAsync(final XmlRpcListener listener, String username, String email, String password) { if (mXmlRpcClient != null) { mXmlRpcClient.callAsync(new XMLRPCCallback() { @Override @@ -62,17 +61,17 @@ public class XmlRpcHelper { Log.e(error); listener.onError(error.toString()); } - }, "create_account_from_in_app_purchase", gmailAccount, username, payload, signature, OS, email, password == null ? "" : password); + }, "create_account", username, email, password == null ? "" : password); } else { Log.e(CLIENT_ERROR_INVALID_SERVER_URL); listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); } } - public String createAccount(String gmailAccount, String username, String payload, String signature, String email, String password) { + public String createAccount(String username, String email, String password) { if (mXmlRpcClient != null) { try { - Object object = mXmlRpcClient.call("create_account_from_in_app_purchase", gmailAccount, username, payload, signature, OS, email, password == null ? "" : password); + Object object = mXmlRpcClient.call("create_account", username, email, password == null ? "" : password); String result = (String)object; Log.d("createAccount: " + result); @@ -91,63 +90,6 @@ public class XmlRpcHelper { return null; } - public void getAccountExpireAsync(final XmlRpcListener listener, String gmailAccount, String payload, String signature) { - if (mXmlRpcClient != null) { - mXmlRpcClient.callAsync(new XMLRPCCallback() { - @Override - public void onServerError(long id, XMLRPCServerException error) { - Log.e(error); - listener.onError(error.toString()); - } - - @Override - public void onResponse(long id, Object object) { - String result = (String)object; - Log.d("getAccountExpireAsync: " + result); - - if (result.startsWith("ERROR_")) { - Log.e(result); - listener.onError(result); - return; - } - - listener.onAccountExpireFetched(result); - } - - @Override - public void onError(long id, XMLRPCException error) { - Log.e(error); - listener.onError(error.toString()); - } - }, "get_expiration_date", gmailAccount, payload, signature, OS); - } else { - Log.e(CLIENT_ERROR_INVALID_SERVER_URL); - listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); - } - } - - public String getAccountExpire(String gmailAccount, String payload, String signature) { - if (mXmlRpcClient != null) { - try { - Object object = mXmlRpcClient.call("get_expiration_date", gmailAccount, payload, signature, OS); - String result = (String)object; - Log.d("getAccountExpire: " + result); - - if (result.startsWith("ERROR_")) { - Log.e(result); - return null; - } - return result; - - } catch (XMLRPCException e) { - Log.e(e); - } - } else { - Log.e(CLIENT_ERROR_INVALID_SERVER_URL); - } - return null; - } - public void getAccountExpireAsync(final XmlRpcListener listener, String username, String password) { if (mXmlRpcClient != null) { mXmlRpcClient.callAsync(new XMLRPCCallback() { @@ -176,7 +118,7 @@ public class XmlRpcHelper { Log.e(error); listener.onError(error.toString()); } - }, "get_expiration_for_account", username, password, OS); + }, "get_expiration_for_account", username, password); } else { Log.e(CLIENT_ERROR_INVALID_SERVER_URL); listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); @@ -186,7 +128,7 @@ public class XmlRpcHelper { public String getAccountExpire(String username, String password) { if (mXmlRpcClient != null) { try { - Object object = mXmlRpcClient.call("get_expiration_for_account", username, password, OS); + Object object = mXmlRpcClient.call("get_expiration_for_account", username, password); String result = (String)object; Log.d("getAccountExpire: " + result); @@ -205,7 +147,64 @@ public class XmlRpcHelper { return null; } - public void activateAccountAsync(final XmlRpcListener listener, String gmailAccount, String username, String payload, String signature) { + public void updateAccountExpireAsync(final XmlRpcListener listener, String username, String password, String payload, String signature) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + Log.d("updateAccountExpireAsync: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onAccountExpireUpdated(result); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "update_expiration_date", username, password, payload, signature); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String updateAccountExpire(String username, String password, String payload, String signature) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("update_expiration_date", username, password, payload, signature); + String result = (String)object; + Log.d("updateAccountExpire: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + + public void activateAccountAsync(final XmlRpcListener listener, String username, String password) { if (mXmlRpcClient != null) { mXmlRpcClient.callAsync(new XMLRPCCallback() { @Override @@ -234,17 +233,17 @@ public class XmlRpcHelper { Log.e(error); listener.onError(error.toString()); } - }, "activate_account", gmailAccount, username, payload, signature, OS); + }, "activate_account", username, password); } else { Log.e(CLIENT_ERROR_INVALID_SERVER_URL); listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); } } - public String activateAccount(String gmailAccount, String username, String payload, String signature) { + public String activateAccount(String gmailAccount, String username, String password) { if (mXmlRpcClient != null) { try { - Object object = mXmlRpcClient.call("activate_account", gmailAccount, username, payload, signature, OS); + Object object = mXmlRpcClient.call("activate_account", username, password); String result = (String)object; Log.d("activateAccount: " + result); @@ -331,10 +330,10 @@ public class XmlRpcHelper { String result = (String)object; Log.d("isTrialAccountAsync: " + result); - if (!"ERROR_TOKEN_NOT_FOUND".equals(result) && !"OK".equals(result)) { + if (!"NOK".equals(result) && !"OK".equals(result)) { listener.onError(result); } - listener.onAccountFetched("ERROR_TOKEN_NOT_FOUND".equals(result)); + listener.onAccountFetched("OK".equals(result)); } @Override @@ -342,7 +341,7 @@ public class XmlRpcHelper { Log.e(error); listener.onError(error.toString()); } - }, "is_account_paid", username, password, OS); + }, "check_account_trial", username, password); } else { Log.e(CLIENT_ERROR_INVALID_SERVER_URL); listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); @@ -352,11 +351,11 @@ public class XmlRpcHelper { public boolean isTrialAccount(String username, String password) { if (mXmlRpcClient != null) { try { - Object object = mXmlRpcClient.call("is_account_paid", username, password, OS); + Object object = mXmlRpcClient.call("check_account_trial", username, password); String result = (String)object; Log.d("isTrialAccount: " + result); - return "ERROR_TOKEN_NOT_FOUND".equals(result); + return "OK".equals(result); } catch (XMLRPCException e) { Log.e(e); } @@ -451,7 +450,7 @@ public class XmlRpcHelper { Log.e(error); listener.onError(error.toString()); } - }, "change_email", username, password, newEmail, OS); + }, "change_email", username, password, newEmail); } else { Log.e(CLIENT_ERROR_INVALID_SERVER_URL); listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); @@ -461,7 +460,7 @@ public class XmlRpcHelper { public String changeAccountEmail(String username, String password, String newEmail) { if (mXmlRpcClient != null) { try { - Object object = mXmlRpcClient.call("change_email", username, password, newEmail, OS); + Object object = mXmlRpcClient.call("change_email", username, password, newEmail); String result = (String)object; Log.d("changeAccountEmail: " + result); @@ -508,7 +507,7 @@ public class XmlRpcHelper { Log.e(error); listener.onError(error.toString()); } - }, "change_password", username, oldPassword, newPassword, OS); + }, "change_password", username, oldPassword, newPassword); } else { Log.e(CLIENT_ERROR_INVALID_SERVER_URL); listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); @@ -518,7 +517,7 @@ public class XmlRpcHelper { public String changeAccountPassword(String username, String oldPassword, String newPassword) { if (mXmlRpcClient != null) { try { - Object object = mXmlRpcClient.call("change_password", username, oldPassword, newPassword, OS); + Object object = mXmlRpcClient.call("change_password", username, oldPassword, newPassword); String result = (String)object; Log.d("changeAccountPassword: " + result); @@ -650,4 +649,61 @@ public class XmlRpcHelper { } return null; } + + public void verifySignatureAsync(final XmlRpcListener listener, String payload, String signature) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + Log.d("verifySignatureAsync: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onSignatureVerified("OK".equals(result)); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "verify_payload_signature", payload, signature); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public boolean verifySignature(String payload, String signature) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("verify_payload_signature", payload, signature); + String result = (String)object; + Log.d("verifySignature: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + return false; + } + return "OK".equals(result); + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return false; + } } diff --git a/src/org/linphone/xmlrpc/XmlRpcListener.java b/src/org/linphone/xmlrpc/XmlRpcListener.java index 8fbcd1a6a..f44543dcb 100644 --- a/src/org/linphone/xmlrpc/XmlRpcListener.java +++ b/src/org/linphone/xmlrpc/XmlRpcListener.java @@ -4,6 +4,7 @@ public interface XmlRpcListener { public void onError(String error); public void onAccountCreated(String result); public void onAccountExpireFetched(String result); + public void onAccountExpireUpdated(String result); public void onAccountActivated(String result); public void onAccountActivatedFetched(boolean isActivated); public void onTrialAccountFetched(boolean isTrial); @@ -12,4 +13,5 @@ public interface XmlRpcListener { public void onAccountPasswordChanged(String result); public void onRecoverPasswordLinkSent(String result); public void onActivateAccountLinkSent(String result); + public void onSignatureVerified(boolean success); } diff --git a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java index e1752d082..94001cea7 100644 --- a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java +++ b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java @@ -66,4 +66,16 @@ public class XmlRpcListenerBase implements XmlRpcListener { // TODO Auto-generated method stub } + + @Override + public void onAccountExpireUpdated(String result) { + // TODO Auto-generated method stub + + } + + @Override + public void onSignatureVerified(boolean success) { + // TODO Auto-generated method stub + + } } From e7459a6e20bf03e57d81ff2c4091d605a6049505 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Jun 2015 16:49:54 +0200 Subject: [PATCH 27/56] Fix activity result checking for buy result needing username and email --- src/org/linphone/purchase/InAppPurchaseActivity.java | 2 +- src/org/linphone/purchase/InAppPurchaseHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java index fd7bdfdd7..4f9e5e7fb 100644 --- a/src/org/linphone/purchase/InAppPurchaseActivity.java +++ b/src/org/linphone/purchase/InAppPurchaseActivity.java @@ -141,7 +141,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data, getUsername(), email.getText().toString()); + inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data); } @Override diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index de4ad6c78..7e1a7dcba 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -269,7 +269,7 @@ public class InAppPurchaseHelper { }).start(); } - public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data, String username, String email) { + public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data) { if (requestCode == ACTIVITY_RESULT_CODE_PURCHASE_ITEM) { int responseCode = data.getIntExtra(RESPONSE_CODE, 0); From f5dd5cf4fd878b3b34a3b0c3eb7d3a3f37aa5a63 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 18 Jun 2015 11:54:29 +0200 Subject: [PATCH 28/56] Fix compilation with latest android sdk tools --- build.xml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/build.xml b/build.xml index 4588f2c03..c23990f28 100644 --- a/build.xml +++ b/build.xml @@ -88,7 +88,22 @@ In all cases you must update the value of version-tag below to read 'custom' instead of an integer, in order to avoid having your file be overridden by tools such as "android update project" --> - - + + + + + + + + + + + + + + + + + From 3c289f4fc5e194f0e171badcfd6f0093a3e7763e Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 23 Jun 2015 16:15:31 +0200 Subject: [PATCH 29/56] Fix crash on Android M preview --- Makefile | 1 + bsed.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 bsed.sh diff --git a/Makefile b/Makefile index eb69db45e..08ed199c8 100644 --- a/Makefile +++ b/Makefile @@ -433,6 +433,7 @@ MEDIASTREAMER2_OPTIONS = $(GENERATE_OPTIONS) BUILD_MEDIASTREAMER2_SDK=1 generate-libs: prepare-sources javah $(NDK_PATH)/ndk-build $(LIBLINPHONE_OPTIONS) -j$(NUMCPUS) TARGET_PLATFORM=$(NDKBUILD_TARGET) + ./bsed.sh # Fix path to libffmpeg library in linphone.so because of Android M Preview issue: https://code.google.com/p/android-developer-preview/issues/detail?id=2239 generate-mediastreamer2-libs: prepare-sources @cd $(TOPDIR)/submodules/linphone/mediastreamer2/java && \ diff --git a/bsed.sh b/bsed.sh new file mode 100755 index 000000000..ea7a242fe --- /dev/null +++ b/bsed.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# Script found at http://everydaywithlinux.blogspot.fr/2012/11/patch-strings-in-binary-files-with-sed.html + +function patch_strings_in_file() { + local FILE="$1" + local PATTERN="$2" + local REPLACEMENT="$3" + + # Find all unique strings in FILE that contain the pattern + STRINGS=$(strings ${FILE} | grep ${PATTERN} | sort -u -r) + + if [ "${STRINGS}" != "" ] ; then + echo "File '${FILE}' contain strings with '${PATTERN}' in them:" + + for OLD_STRING in ${STRINGS} ; do + # Create the new string with a simple bash-replacement + NEW_STRING=${OLD_STRING//${PATTERN}/${REPLACEMENT}} + + # Create null terminated ASCII HEX representations of the strings + OLD_STRING_HEX="$(echo -n ${OLD_STRING} | xxd -g 0 -u -ps -c 256)00" + NEW_STRING_HEX="$(echo -n ${NEW_STRING} | xxd -g 0 -u -ps -c 256)00" + + if [ ${#NEW_STRING_HEX} -le ${#OLD_STRING_HEX} ] ; then + # Pad the replacement string with null terminations so the + # length matches the original string + while [ ${#NEW_STRING_HEX} -lt ${#OLD_STRING_HEX} ] ; do + NEW_STRING_HEX="${NEW_STRING_HEX}00" + done + + # Now, replace every occurrence of OLD_STRING with NEW_STRING + echo -n "Replacing ${OLD_STRING} with ${NEW_STRING}... " + hexdump -ve '1/1 "%.2X"' ${FILE} | \ + sed "s/${OLD_STRING_HEX}/${NEW_STRING_HEX}/g" | \ + xxd -r -p > ${FILE}.tmp + chmod --reference ${FILE} ${FILE}.tmp + mv ${FILE}.tmp ${FILE} + echo "Done!" + else + echo "New string '${NEW_STRING}' is longer than old" \ + "string '${OLD_STRING}'. Skipping." + fi + done + fi +} + +patch_strings_in_file libs/armeabi-v7a/liblinphone-armeabi-v7a.so "./obj/local/armeabi-v7a/libffmpeg-linphone-arm.so" "libffmpeg-linphone-arm.so" +patch_strings_in_file libs/armeabi-x86/liblinphone-armeabi-x86.so "./obj/local/armeabi-x86/libffmpeg-linphone-x86.so" "libffmpeg-linphone-x86.so" From d8913ef63abbd250ed6ea281d26520c1908a6f6d Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 21 Jul 2015 15:29:30 +0200 Subject: [PATCH 30/56] Fix crash if purchase service not available --- src/org/linphone/purchase/InAppPurchaseHelper.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java index 7e1a7dcba..729aabb25 100644 --- a/src/org/linphone/purchase/InAppPurchaseHelper.java +++ b/src/org/linphone/purchase/InAppPurchaseHelper.java @@ -298,10 +298,12 @@ public class InAppPurchaseHelper { if (buyIntentBundle != null) { PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT); - try { - ((Activity) mContext).startIntentSenderForResult(pendingIntent.getIntentSender(), ACTIVITY_RESULT_CODE_PURCHASE_ITEM, new Intent(), 0, 0, 0); - } catch (SendIntentException e) { - Log.e(e); + if (pendingIntent != null) { + try { + ((Activity) mContext).startIntentSenderForResult(pendingIntent.getIntentSender(), ACTIVITY_RESULT_CODE_PURCHASE_ITEM, new Intent(), 0, 0, 0); + } catch (SendIntentException e) { + Log.e(e); + } } } } From d6a570644b7c38ae4751e1b32d684128d0d770e8 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 31 Aug 2015 12:10:37 +0200 Subject: [PATCH 31/56] Fix compilation when using SDK 23 --- .../linphone/compatibility/ApiFivePlus.java | 63 +++++++++---------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/src/org/linphone/compatibility/ApiFivePlus.java b/src/org/linphone/compatibility/ApiFivePlus.java index b0c66f825..82a95be4f 100644 --- a/src/org/linphone/compatibility/ApiFivePlus.java +++ b/src/org/linphone/compatibility/ApiFivePlus.java @@ -32,6 +32,7 @@ import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; +import android.support.v4.app.NotificationCompat; import android.text.ClipboardManager; import android.text.TextUtils; import android.view.ViewTreeObserver; @@ -291,32 +292,28 @@ public class ApiFivePlus { } public static Notification createMessageNotification(Context context, String title, String msg, PendingIntent intent) { - Notification notif = new Notification(); - notif.icon = R.drawable.chat_icon_over; - notif.iconLevel = 0; - notif.when = System.currentTimeMillis(); - notif.flags &= Notification.FLAG_ONGOING_EVENT; - + NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.chat_icon_over) + .setContentTitle(title) + .setContentText(msg) + .setContentIntent(intent); + + Notification notif = notifBuilder.build(); notif.defaults |= Notification.DEFAULT_VIBRATE; notif.defaults |= Notification.DEFAULT_SOUND; notif.defaults |= Notification.DEFAULT_LIGHTS; - notif.setLatestEventInfo(context, title, msg, intent); - return notif; } - public static Notification createInCallNotification(Context context, - String title, String msg, int iconID, PendingIntent intent) { - Notification notif = new Notification(); - notif.icon = iconID; - notif.iconLevel = 0; - notif.when = System.currentTimeMillis(); - notif.flags &= Notification.FLAG_ONGOING_EVENT; - - notif.setLatestEventInfo(context, title, msg, intent); - - return notif; + public static Notification createInCallNotification(Context context, String title, String msg, int iconID, PendingIntent intent) { + NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(iconID) + .setContentTitle(title) + .setContentText(msg) + .setContentIntent(intent); + + return notifBuilder.build(); } public static void setPreferenceChecked(Preference preference, boolean checked) { @@ -390,31 +387,27 @@ public class ApiFivePlus { } public static Notification createNotification(Context context, String title, String message, int icon, int level, PendingIntent intent, boolean isOngoingEvent) { - Notification notif = new Notification(); - notif.icon = icon; - notif.iconLevel = level; - notif.when = System.currentTimeMillis(); - if (isOngoingEvent) { - notif.flags |= Notification.FLAG_ONGOING_EVENT; - } - notif.setLatestEventInfo(context, title, message, intent); + NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(icon, level) + .setContentTitle(title) + .setContentText(message) + .setContentIntent(intent); - return notif; + return notifBuilder.build(); } public static Notification createSimpleNotification(Context context, String title, String text, PendingIntent intent) { - Notification notif = new Notification(); - notif.icon = R.drawable.logo_linphone_57x57; - notif.iconLevel = 0; - notif.when = System.currentTimeMillis(); - notif.flags &= Notification.FLAG_ONGOING_EVENT; + NotificationCompat.Builder notifBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.logo_linphone_57x57) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(intent); + Notification notif = notifBuilder.build(); notif.defaults |= Notification.DEFAULT_VIBRATE; notif.defaults |= Notification.DEFAULT_SOUND; notif.defaults |= Notification.DEFAULT_LIGHTS; - notif.setLatestEventInfo(context, title, text, intent); - return notif; } } From 41014b153a73e17f63c25500390789396038cf75 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Fri, 28 Aug 2015 16:33:53 +0200 Subject: [PATCH 32/56] Fix echo cancellation preference --- .../linphone/setup/EchoCancellerCalibrationFragment.java | 6 ------ src/org/linphone/setup/SetupActivity.java | 3 --- 2 files changed, 9 deletions(-) diff --git a/src/org/linphone/setup/EchoCancellerCalibrationFragment.java b/src/org/linphone/setup/EchoCancellerCalibrationFragment.java index a347a5f3e..b69eda7bf 100644 --- a/src/org/linphone/setup/EchoCancellerCalibrationFragment.java +++ b/src/org/linphone/setup/EchoCancellerCalibrationFragment.java @@ -59,12 +59,6 @@ public class EchoCancellerCalibrationFragment extends Fragment { @Override public void ecCalibrationStatus(LinphoneCore lc,LinphoneCore.EcCalibratorStatus status, int delay_ms, Object data) { LinphoneManager.getInstance().routeAudioToReceiver(); - - if (status == EcCalibratorStatus.DoneNoEcho) { - LinphonePreferences.instance().setEchoCancellation(false); - } else if ((status == EcCalibratorStatus.Done) || (status == EcCalibratorStatus.Failed)) { - LinphonePreferences.instance().setEchoCancellation(true); - } if (mSendEcCalibrationResult) { sendEcCalibrationResult(status, delay_ms); } else { diff --git a/src/org/linphone/setup/SetupActivity.java b/src/org/linphone/setup/SetupActivity.java index bba6e8292..e5d5cbd72 100644 --- a/src/org/linphone/setup/SetupActivity.java +++ b/src/org/linphone/setup/SetupActivity.java @@ -222,9 +222,6 @@ public class SetupActivity extends FragmentActivity implements OnClickListener { next.setEnabled(false); cancel.setEnabled(false); } else { - if (mPrefs.isFirstLaunch()) { - mPrefs.setEchoCancellation(LinphoneManager.getLc().needsEchoCanceler()); - } success(); } } From 2025fb3b1b18a1de43fb5d7c986fed0154023db5 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 9 Oct 2015 14:53:47 +0200 Subject: [PATCH 33/56] Added change hash password in xmlrpc helper --- src/org/linphone/xmlrpc/XmlRpcHelper.java | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java index 2cd684323..d37d6a0b8 100644 --- a/src/org/linphone/xmlrpc/XmlRpcHelper.java +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -536,6 +536,63 @@ public class XmlRpcHelper { return null; } + public void changeAccountHashPasswordAsync(final XmlRpcListener listener, String username, String oldPassword, String newPassword) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + Log.d("changeAccountPasswordAsync: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onAccountPasswordChanged(result); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "change_hash", username, oldPassword, newPassword); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String changeAccountHashPassword(String username, String oldPassword, String newPassword) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("change_hash", username, oldPassword, newPassword); + String result = (String)object; + Log.d("changeAccountPassword: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + public void sendRecoverPasswordLinkByEmailAsync(final XmlRpcListener listener, String usernameOrEmail) { if (mXmlRpcClient != null) { mXmlRpcClient.callAsync(new XMLRPCCallback() { From 826cdc7e27397c91c2c550f36ddf8f21013fac4c Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Fri, 9 Oct 2015 15:09:33 +0200 Subject: [PATCH 34/56] Added xmlrpc method to recover username associated to email --- src/org/linphone/xmlrpc/XmlRpcHelper.java | 61 ++++++++++++++++++- src/org/linphone/xmlrpc/XmlRpcListener.java | 1 + .../linphone/xmlrpc/XmlRpcListenerBase.java | 6 ++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java index d37d6a0b8..bed90e850 100644 --- a/src/org/linphone/xmlrpc/XmlRpcHelper.java +++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java @@ -548,7 +548,7 @@ public class XmlRpcHelper { @Override public void onResponse(long id, Object object) { String result = (String)object; - Log.d("changeAccountPasswordAsync: " + result); + Log.d("changeAccountHashPasswordAsync: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -576,7 +576,7 @@ public class XmlRpcHelper { try { Object object = mXmlRpcClient.call("change_hash", username, oldPassword, newPassword); String result = (String)object; - Log.d("changeAccountPassword: " + result); + Log.d("changeAccountHashPassword: " + result); if (result.startsWith("ERROR_")) { Log.e(result); @@ -707,6 +707,63 @@ public class XmlRpcHelper { return null; } + public void sendUsernameByEmailAsync(final XmlRpcListener listener, String email) { + if (mXmlRpcClient != null) { + mXmlRpcClient.callAsync(new XMLRPCCallback() { + @Override + public void onServerError(long id, XMLRPCServerException error) { + Log.e(error); + listener.onError(error.toString()); + } + + @Override + public void onResponse(long id, Object object) { + String result = (String)object; + Log.d("sendUsernameByEmailAsync: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + listener.onError(result); + return; + } + + listener.onUsernameSent(result); + } + + @Override + public void onError(long id, XMLRPCException error) { + Log.e(error); + listener.onError(error.toString()); + } + }, "recover_username_from_email", email); + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + listener.onError(CLIENT_ERROR_INVALID_SERVER_URL); + } + } + + public String sendUsernameByEmail(String email) { + if (mXmlRpcClient != null) { + try { + Object object = mXmlRpcClient.call("recover_username_from_email", email); + String result = (String)object; + Log.d("sendUsernameByEmail: " + result); + + if (result.startsWith("ERROR_")) { + Log.e(result); + return null; + } + return result; + + } catch (XMLRPCException e) { + Log.e(e); + } + } else { + Log.e(CLIENT_ERROR_INVALID_SERVER_URL); + } + return null; + } + public void verifySignatureAsync(final XmlRpcListener listener, String payload, String signature) { if (mXmlRpcClient != null) { mXmlRpcClient.callAsync(new XMLRPCCallback() { diff --git a/src/org/linphone/xmlrpc/XmlRpcListener.java b/src/org/linphone/xmlrpc/XmlRpcListener.java index f44543dcb..6a7fb5a99 100644 --- a/src/org/linphone/xmlrpc/XmlRpcListener.java +++ b/src/org/linphone/xmlrpc/XmlRpcListener.java @@ -14,4 +14,5 @@ public interface XmlRpcListener { public void onRecoverPasswordLinkSent(String result); public void onActivateAccountLinkSent(String result); public void onSignatureVerified(boolean success); + public void onUsernameSent(String result); } diff --git a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java index 94001cea7..5ef93bffd 100644 --- a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java +++ b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java @@ -78,4 +78,10 @@ public class XmlRpcListenerBase implements XmlRpcListener { // TODO Auto-generated method stub } + + @Override + public void onUsernameSent(String result) { + // TODO Auto-generated method stub + + } } From d4c340a512f76c5f263006e6fef1c248d71e785f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 17 Feb 2016 14:42:59 +0100 Subject: [PATCH 35/56] Updated libupnp --- submodules/externals/libupnp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/externals/libupnp b/submodules/externals/libupnp index d0b16d056..3fc0f9ad1 160000 --- a/submodules/externals/libupnp +++ b/submodules/externals/libupnp @@ -1 +1 @@ -Subproject commit d0b16d056e0f681a2bc6bd70859303b4bba521dc +Subproject commit 3fc0f9ad1dc124bdd7fc2aa5aeb6a684f79191f2 From 6948613dec6a7aa9b5e711d539c5e86023c6d9f9 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 24 Jun 2016 14:44:18 +0200 Subject: [PATCH 36/56] Update belcard submodule. --- submodules/belcard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/belcard b/submodules/belcard index da7030bb3..0967e5fbe 160000 --- a/submodules/belcard +++ b/submodules/belcard @@ -1 +1 @@ -Subproject commit da7030bb322f4cd74638e6710007b1470c1b602e +Subproject commit 0967e5fbec5c8f5a130dc84fb48c21b57153ae8c From 6a4cbe00f1186d1c5ac078d7f86a49c399e65106 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 24 Jun 2016 15:42:32 +0200 Subject: [PATCH 37/56] Update belr and belcard submodules. --- submodules/belcard | 2 +- submodules/belr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/belcard b/submodules/belcard index 0967e5fbe..3207a12e8 160000 --- a/submodules/belcard +++ b/submodules/belcard @@ -1 +1 @@ -Subproject commit 0967e5fbec5c8f5a130dc84fb48c21b57153ae8c +Subproject commit 3207a12e894cce6d4d22ea9551963efbdd2df339 diff --git a/submodules/belr b/submodules/belr index c30e93b8b..8e1921592 160000 --- a/submodules/belr +++ b/submodules/belr @@ -1 +1 @@ -Subproject commit c30e93b8bb16a36e2bd8bcf5b08af61aa8416aa9 +Subproject commit 8e1921592df1abd7a6a00504410d776822c2b46f From 68ab1ab4b1ad6c3768000efcc3bf699a9f781846 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 24 Jun 2016 16:07:30 +0200 Subject: [PATCH 38/56] Fix NDK version detection for NDK 12. --- prepare.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prepare.py b/prepare.py index f90dc1416..59200b4c4 100755 --- a/prepare.py +++ b/prepare.py @@ -138,8 +138,8 @@ class AndroidPreparator(prepare.Preparator): python_config_files.append(os.path.join(root, filename)) if len(python_config_files) > 0: version = open(python_config_files[0]).readlines()[0] - res = re.match('^.*/aosp-ndk-r(\d+).*$', version) - version = int(res.group(1)) + res = re.match('^.*/(aosp-)?ndk-r(\d+).*$', version) + version = int(res.group(2)) retval = False else: error("Could not get Android NDK version!") From b36d9103aa42256bae33989ed90cf4fa1ad3e7e7 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Fri, 24 Jun 2016 16:31:46 +0200 Subject: [PATCH 39/56] Support NDK r12. --- prepare.py | 2 +- submodules/cmake-builder | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/prepare.py b/prepare.py index 59200b4c4..1e2316165 100755 --- a/prepare.py +++ b/prepare.py @@ -84,7 +84,7 @@ class AndroidPreparator(prepare.Preparator): def __init__(self, targets=android_targets): prepare.Preparator.__init__(self, targets) self.min_supported_ndk = 10 - self.max_supported_ndk = 11 + self.max_supported_ndk = 12 self.unsupported_ndk_version = None self.release_with_debug_info = True self.veryclean = True diff --git a/submodules/cmake-builder b/submodules/cmake-builder index a4fbcbee4..d9ec1a69d 160000 --- a/submodules/cmake-builder +++ b/submodules/cmake-builder @@ -1 +1 @@ -Subproject commit a4fbcbee40e7c9b9ef3ec3f7b0e95e3440906d89 +Subproject commit d9ec1a69de814102d7d0bc6a044595fd98e288b8 From c7b47c68a1059067f917946339423d69614d595f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 27 Jun 2016 13:06:14 +0200 Subject: [PATCH 40/56] Do the setName after the setAddress when creationg LinphoneFriend to prevent warning in logs + prevent crash if fullname is null --- src/org/linphone/ContactsManager.java | 12 ++++++++---- src/org/linphone/LinphoneContact.java | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/org/linphone/ContactsManager.java b/src/org/linphone/ContactsManager.java index 59611a8f3..78b7ef8b5 100644 --- a/src/org/linphone/ContactsManager.java +++ b/src/org/linphone/ContactsManager.java @@ -126,8 +126,10 @@ public class ContactsManager extends ContentObserver { search = search.toLowerCase(Locale.getDefault()); List searchContacts = new ArrayList(); for (LinphoneContact contact : contacts) { - if (contact.getFullName().toLowerCase(Locale.getDefault()).contains(search)) { - searchContacts.add(contact); + if (contact.getFullName() != null) { + if (contact.getFullName().toLowerCase(Locale.getDefault()).contains(search)) { + searchContacts.add(contact); + } } } return searchContacts; @@ -137,8 +139,10 @@ public class ContactsManager extends ContentObserver { search = search.toLowerCase(Locale.getDefault()); List searchContacts = new ArrayList(); for (LinphoneContact contact : sipContacts) { - if (contact.getFullName().toLowerCase(Locale.getDefault()).contains(search)) { - searchContacts.add(contact); + if (contact.getFullName() != null) { + if (contact.getFullName().toLowerCase(Locale.getDefault()).contains(search)) { + searchContacts.add(contact); + } } } return searchContacts; diff --git a/src/org/linphone/LinphoneContact.java b/src/org/linphone/LinphoneContact.java index 66a2920f5..dbe7bd0d0 100644 --- a/src/org/linphone/LinphoneContact.java +++ b/src/org/linphone/LinphoneContact.java @@ -317,7 +317,6 @@ public class LinphoneContact implements Serializable, Comparable Date: Mon, 27 Jun 2016 13:38:35 +0200 Subject: [PATCH 41/56] Only set LinphoneFriend's name if it has at least an address + use the multiple SIP addresses in one LinphoneFriend feature --- src/org/linphone/LinphoneContact.java | 30 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/org/linphone/LinphoneContact.java b/src/org/linphone/LinphoneContact.java index dbe7bd0d0..7233462bf 100644 --- a/src/org/linphone/LinphoneContact.java +++ b/src/org/linphone/LinphoneContact.java @@ -313,24 +313,30 @@ public class LinphoneContact implements Serializable, Comparable Date: Mon, 27 Jun 2016 13:40:08 +0200 Subject: [PATCH 42/56] Allow to display a LinphoneFriend without a display name (crash fix) --- src/org/linphone/ContactDetailsFragment.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/org/linphone/ContactDetailsFragment.java b/src/org/linphone/ContactDetailsFragment.java index 7909e0f1e..1c2c8d11c 100644 --- a/src/org/linphone/ContactDetailsFragment.java +++ b/src/org/linphone/ContactDetailsFragment.java @@ -213,13 +213,8 @@ public class ContactDetailsFragment extends Fragment implements OnClickListener LinphoneActivity.instance().selectMenu(FragmentsAvailable.CONTACT_DETAIL); LinphoneActivity.instance().hideTabBar(false); } - if (contact.getFullName() == null || contact.getFullName().equals("")) { - //Contact has been deleted, return - LinphoneActivity.instance().displayContacts(false); - } else { - contact.refresh(); - displayContact(inflater, view); - } + contact.refresh(); + displayContact(inflater, view); } @Override From 6d1f06fe7f7deead7c06fb35f10b12f36b8bf406 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 27 Jun 2016 14:32:58 +0200 Subject: [PATCH 43/56] Store all phone numbers and SIP addresses in LinphoneFriend + some improvements in the way we handle it --- src/org/linphone/ContactEditorFragment.java | 32 +++++++++---------- src/org/linphone/LinphoneContact.java | 35 ++++++++++++++------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/org/linphone/ContactEditorFragment.java b/src/org/linphone/ContactEditorFragment.java index 8544e3056..e94aefdc0 100644 --- a/src/org/linphone/ContactEditorFragment.java +++ b/src/org/linphone/ContactEditorFragment.java @@ -385,7 +385,7 @@ public class ContactEditorFragment extends Fragment { if (contact != null) { for (LinphoneNumberOrAddress numberOrAddress : contact.getNumbersOrAddresses()) { if (!numberOrAddress.isSIPAddress()) { - View view = displayNumberOrAddress(controls, numberOrAddress.getValue()); + View view = displayNumberOrAddress(controls, numberOrAddress.getValue(), false); if (view != null) controls.addView(view); } @@ -395,7 +395,7 @@ public class ContactEditorFragment extends Fragment { if (newSipOrNumberToAdd != null) { boolean isSip = LinphoneUtils.isStrictSipAddress(newSipOrNumberToAdd) || !LinphoneUtils.isNumberAddress(newSipOrNumberToAdd); if(!isSip) { - View view = displayNumberOrAddress(controls, newSipOrNumberToAdd); + View view = displayNumberOrAddress(controls, newSipOrNumberToAdd, false); if (view != null) controls.addView(view); } @@ -415,7 +415,7 @@ public class ContactEditorFragment extends Fragment { if (contact != null) { for (LinphoneNumberOrAddress numberOrAddress : contact.getNumbersOrAddresses()) { if (numberOrAddress.isSIPAddress()) { - View view = displayNumberOrAddress(controls, numberOrAddress.getValue()); + View view = displayNumberOrAddress(controls, numberOrAddress.getValue(), true); if (view != null) controls.addView(view); } @@ -425,7 +425,7 @@ public class ContactEditorFragment extends Fragment { if (newSipOrNumberToAdd != null) { boolean isSip = LinphoneUtils.isStrictSipAddress(newSipOrNumberToAdd) || !LinphoneUtils.isNumberAddress(newSipOrNumberToAdd); if (isSip) { - View view = displayNumberOrAddress(controls, newSipOrNumberToAdd); + View view = displayNumberOrAddress(controls, newSipOrNumberToAdd, true); if (view != null) controls.addView(view); } @@ -438,35 +438,33 @@ public class ContactEditorFragment extends Fragment { return controls; } - private View displayNumberOrAddress(final LinearLayout controls, String numberOrAddress) { - return displayNumberOrAddress(controls, numberOrAddress, false); + private View displayNumberOrAddress(final LinearLayout controls, String numberOrAddress, boolean isSIP) { + return displayNumberOrAddress(controls, numberOrAddress, isSIP, false); } @SuppressLint("InflateParams") - private View displayNumberOrAddress(final LinearLayout controls, String numberOrAddress, boolean forceAddNumber) { - boolean isSip = LinphoneUtils.isStrictSipAddress(numberOrAddress) || !LinphoneUtils.isNumberAddress(numberOrAddress); - - if (isSip) { + private View displayNumberOrAddress(final LinearLayout controls, String numberOrAddress, boolean isSIP, boolean forceAddNumber) { + if (isSIP) { if (firstSipAddressIndex == -1) { firstSipAddressIndex = controls.getChildCount(); } numberOrAddress = numberOrAddress.replace("sip:", ""); } - if ((getResources().getBoolean(R.bool.hide_phone_numbers_in_editor) && !isSip) || (getResources().getBoolean(R.bool.hide_sip_addresses_in_editor) && isSip)) { + if ((getResources().getBoolean(R.bool.hide_phone_numbers_in_editor) && !isSIP) || (getResources().getBoolean(R.bool.hide_sip_addresses_in_editor) && isSIP)) { if (forceAddNumber) - isSip = !isSip; // If number can't be displayed because we hide a sort of number, change that category + isSIP = !isSIP; // If number can't be displayed because we hide a sort of number, change that category else return null; } LinphoneNumberOrAddress tempNounoa; if (forceAddNumber) { - tempNounoa = new LinphoneNumberOrAddress(null, isSip); + tempNounoa = new LinphoneNumberOrAddress(null, isSIP); } else { if(isNewContact || newSipOrNumberToAdd != null) { - tempNounoa = new LinphoneNumberOrAddress(numberOrAddress, isSip); + tempNounoa = new LinphoneNumberOrAddress(numberOrAddress, isSIP); } else { - tempNounoa = new LinphoneNumberOrAddress(null, isSip, numberOrAddress); + tempNounoa = new LinphoneNumberOrAddress(null, isSIP, numberOrAddress); } } final LinphoneNumberOrAddress nounoa = tempNounoa; @@ -475,7 +473,7 @@ public class ContactEditorFragment extends Fragment { final View view = inflater.inflate(R.layout.contact_edit_row, null); final EditText noa = (EditText) view.findViewById(R.id.numoraddr); - noa.setInputType(isSip ? InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS : InputType.TYPE_CLASS_PHONE); + noa.setInputType(isSIP ? InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS : InputType.TYPE_CLASS_PHONE); noa.setText(numberOrAddress); noa.addTextChangedListener(new TextWatcher() { @Override @@ -496,7 +494,7 @@ public class ContactEditorFragment extends Fragment { } ImageView delete = (ImageView) view.findViewById(R.id.delete_field); - if ((getResources().getBoolean(R.bool.allow_only_one_phone_number) && !isSip) || (getResources().getBoolean(R.bool.allow_only_one_sip_address) && isSip)) { + if ((getResources().getBoolean(R.bool.allow_only_one_phone_number) && !isSIP) || (getResources().getBoolean(R.bool.allow_only_one_sip_address) && isSIP)) { delete.setVisibility(View.GONE); } delete.setOnClickListener(new OnClickListener() { diff --git a/src/org/linphone/LinphoneContact.java b/src/org/linphone/LinphoneContact.java index 7233462bf..cc73283ec 100644 --- a/src/org/linphone/LinphoneContact.java +++ b/src/org/linphone/LinphoneContact.java @@ -203,12 +203,14 @@ public class LinphoneContact implements Serializable, Comparable Date: Mon, 27 Jun 2016 15:35:41 +0200 Subject: [PATCH 44/56] Update cmake-builder submodule. --- submodules/cmake-builder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/cmake-builder b/submodules/cmake-builder index d9ec1a69d..eb1bc5f1b 160000 --- a/submodules/cmake-builder +++ b/submodules/cmake-builder @@ -1 +1 @@ -Subproject commit d9ec1a69de814102d7d0bc6a044595fd98e288b8 +Subproject commit eb1bc5f1b9392d9c83e4df56a49849b57afbd25c From e892b12530335806f0b992011a61150a1975edad Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 27 Jun 2016 16:58:47 +0200 Subject: [PATCH 45/56] Display minimalist progress dialog when uploading logs --- src/org/linphone/AboutFragment.java | 76 +++++++++++++++++++++++++- src/org/linphone/LinphoneActivity.java | 16 ------ src/org/linphone/LinphoneManager.java | 4 -- 3 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/org/linphone/AboutFragment.java b/src/org/linphone/AboutFragment.java index 77a5f516f..86502d948 100644 --- a/src/org/linphone/AboutFragment.java +++ b/src/org/linphone/AboutFragment.java @@ -19,13 +19,22 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ import org.linphone.core.LinphoneCore; +import org.linphone.core.LinphoneCore.LogCollectionUploadState; +import org.linphone.core.LinphoneCoreListenerBase; import org.linphone.mediastream.Log; +import android.app.Dialog; import android.app.Fragment; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; @@ -38,6 +47,9 @@ public class AboutFragment extends Fragment implements OnClickListener { View sendLogButton = null; View resetLogButton = null; ImageView cancel; + LinphoneCoreListenerBase mListener; + private ProgressDialog progress; + private boolean uploadInProgress; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -62,22 +74,82 @@ public class AboutFragment extends Fragment implements OnClickListener { resetLogButton = view.findViewById(R.id.reset_log); resetLogButton.setOnClickListener(this); resetLogButton.setVisibility(org.linphone.LinphonePreferences.instance().isDebugEnabled() ? View.VISIBLE : View.GONE); + + mListener = new LinphoneCoreListenerBase() { + @Override + public void uploadProgressIndication(LinphoneCore lc, int offset, int total) { + } + + @Override + public void uploadStateChanged(LinphoneCore lc, LogCollectionUploadState state, String info) { + if (state == LogCollectionUploadState.LogCollectionUploadStateInProgress) { + displayUploadLogsInProgress(); + } else if (state == LogCollectionUploadState.LogCollectionUploadStateDelivered || state == LogCollectionUploadState.LogCollectionUploadStateNotDelivered) { + uploadInProgress = false; + if (progress != null) progress.dismiss(); + if (state == LogCollectionUploadState.LogCollectionUploadStateDelivered) { + sendLogs(LinphoneService.instance().getApplicationContext(), info); + } + } + } + }; return view; } + + private void displayUploadLogsInProgress() { + if (uploadInProgress) { + return; + } + uploadInProgress = true; + + progress = ProgressDialog.show(LinphoneActivity.instance(), null, null); + Drawable d = new ColorDrawable(getResources().getColor(R.color.colorE)); + d.setAlpha(200); + progress.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + progress.getWindow().setBackgroundDrawable(d); + progress.setContentView(R.layout.progress_dialog); + progress.show(); + } + + private void sendLogs(Context context, String info){ + final String appName = context.getString(R.string.app_name); + + Intent i = new Intent(Intent.ACTION_SEND); + i.putExtra(Intent.EXTRA_EMAIL, new String[]{ context.getString(R.string.about_bugreport_email) }); + i.putExtra(Intent.EXTRA_SUBJECT, appName + " Logs"); + i.putExtra(Intent.EXTRA_TEXT, info); + i.setType("application/zip"); + + try { + startActivity(Intent.createChooser(i, "Send mail...")); + } catch (android.content.ActivityNotFoundException ex) { + Log.e(ex); + } + } @Override public void onPause() { + LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); + if (lc != null) { + lc.removeListener(mListener); + } + super.onPause(); } @Override public void onResume() { - super.onResume(); - + LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); + if (lc != null) { + lc.addListener(mListener); + } + if (org.linphone.LinphoneActivity.isInstanciated()) { LinphoneActivity.instance().selectMenu(FragmentsAvailable.ABOUT); } + + super.onResume(); } @Override diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index 0fcfc05a8..d77460da7 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -970,22 +970,6 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta startActivityForResult(intent, CALL_ACTIVITY); } - public void sendLogs(Context context, String info){ - final String appName = context.getString(R.string.app_name); - - Intent i = new Intent(Intent.ACTION_SEND); - i.putExtra(Intent.EXTRA_EMAIL, new String[]{ context.getString(R.string.about_bugreport_email) }); - i.putExtra(Intent.EXTRA_SUBJECT, appName + " Logs"); - i.putExtra(Intent.EXTRA_TEXT, info); - i.setType("application/zip"); - - try { - startActivity(Intent.createChooser(i, "Send mail...")); - } catch (android.content.ActivityNotFoundException ex) { - Log.e(ex); - } - } - /** * Register a sensor to track phoneOrientation changes */ diff --git a/src/org/linphone/LinphoneManager.java b/src/org/linphone/LinphoneManager.java index f94fe313e..281426dc6 100644 --- a/src/org/linphone/LinphoneManager.java +++ b/src/org/linphone/LinphoneManager.java @@ -1467,10 +1467,6 @@ public class LinphoneManager implements LinphoneCoreListener, LinphoneChatMessag @Override public void uploadStateChanged(LinphoneCore linphoneCore, LogCollectionUploadState state, String info) { Log.d("Log upload state: " + state.toString() + ", info = " + info); - - if (state == LogCollectionUploadState.LogCollectionUploadStateDelivered) { - LinphoneActivity.instance().sendLogs(LinphoneService.instance().getApplicationContext(),info); - } } @Override From f5e77447ad2ec3ba7fc96853158acf654bc77ce5 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 27 Jun 2016 17:34:17 +0200 Subject: [PATCH 46/56] Display unread chat count in call view --- res/layout-land/call.xml | 31 +++++++++++++++++------ res/layout-sw533dp-land/call.xml | 31 +++++++++++++++++------ res/layout/call.xml | 33 +++++++++++++++++++------ src/org/linphone/CallActivity.java | 34 +++++++++++++++++++++++++- src/org/linphone/LinphoneActivity.java | 2 +- 5 files changed, 107 insertions(+), 24 deletions(-) diff --git a/res/layout-land/call.xml b/res/layout-land/call.xml index 9c556801b..f88ab92ac 100644 --- a/res/layout-land/call.xml +++ b/res/layout-land/call.xml @@ -241,15 +241,32 @@ android:layout_weight="0.5" android:padding="12dp"/> - + android:layout_weight="1"> + + + + + diff --git a/res/layout-sw533dp-land/call.xml b/res/layout-sw533dp-land/call.xml index 12862af3b..5de923dec 100644 --- a/res/layout-sw533dp-land/call.xml +++ b/res/layout-sw533dp-land/call.xml @@ -241,15 +241,32 @@ android:padding="12dp" android:contentDescription="@string/content_description_hang_up"/> - + android:layout_weight="1"> + + + + + diff --git a/res/layout/call.xml b/res/layout/call.xml index 280bdb653..06c66b396 100644 --- a/res/layout/call.xml +++ b/res/layout/call.xml @@ -294,16 +294,33 @@ android:layout_height="wrap_content" android:layout_weight="0.4" android:padding="12dp"/> - - + android:layout_weight="0.8"> + + + + + diff --git a/src/org/linphone/CallActivity.java b/src/org/linphone/CallActivity.java index 635adba9f..afdbd8b60 100644 --- a/src/org/linphone/CallActivity.java +++ b/src/org/linphone/CallActivity.java @@ -24,6 +24,8 @@ import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCallParams; +import org.linphone.core.LinphoneChatMessage; +import org.linphone.core.LinphoneChatRoom; import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCoreException; import org.linphone.core.LinphoneCoreListenerBase; @@ -91,6 +93,7 @@ public class CallActivity extends Activity implements OnClickListener, SensorEve private Handler mControlsHandler = new Handler(); private Runnable mControls; private ImageView switchCamera; + private TextView missedChats; private RelativeLayout mActiveCallHeader, sideMenuContent, avatar_layout; private ImageView pause, hangUp, dialer, video, micro, speaker, options, addCall, transfer, conference, conferenceStatus, contactPicture; private ImageView audioRoute, routeSpeaker, routeEarpiece, routeBluetooth, menu, chat; @@ -161,7 +164,12 @@ public class CallActivity extends Activity implements OnClickListener, SensorEve mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); mProximity = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - mListener = new LinphoneCoreListenerBase(){ + mListener = new LinphoneCoreListenerBase() { + @Override + public void messageReceived(LinphoneCore lc, LinphoneChatRoom cr, LinphoneChatMessage message) { + displayMissedChats(); + } + @Override public void callState(LinphoneCore lc, final LinphoneCall call, LinphoneCall.State state, String message) { if (LinphoneManager.getLc().getCallsNb() == 0) { @@ -362,6 +370,7 @@ public class CallActivity extends Activity implements OnClickListener, SensorEve chat = (ImageView) findViewById(R.id.chat); chat.setOnClickListener(this); + missedChats = (TextView) findViewById(R.id.missed_chats); //Others @@ -500,6 +509,7 @@ public class CallActivity extends Activity implements OnClickListener, SensorEve refreshInCallActions(); refreshCallList(getResources()); enableAndRefreshInCallActions(); + displayMissedChats(); } private void refreshInCallActions() { @@ -1806,4 +1816,26 @@ public class CallActivity extends Activity implements OnClickListener, SensorEve public void onAccuracyChanged(Sensor sensor, int accuracy) { } + + private void displayMissedChats() { + int count = 0; + LinphoneChatRoom[] chats = LinphoneManager.getLc().getChatRooms(); + for (LinphoneChatRoom chatroom : chats) { + count += chatroom.getUnreadMessagesCount(); + } + + if (count > 0) { + missedChats.setText(count + ""); + missedChats.setVisibility(View.VISIBLE); + if (!isAnimationDisabled) { + missedChats.startAnimation(AnimationUtils.loadAnimation(this, R.anim.bounce)); + } + if(count > 99){ + //TODO + } + } else { + missedChats.clearAnimation(); + missedChats.setVisibility(View.GONE); + } + } } diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index d77460da7..44421ca98 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -1248,7 +1248,7 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta LinphoneManager.getInstance().changeStatusToOnline(); - if (getIntent().getIntExtra("PreviousActivity", 0) != CALL_ACTIVITY){ + if (getIntent().getIntExtra("PreviousActivity", 0) != CALL_ACTIVITY) { if (LinphoneManager.getLc().getCalls().length > 0) { LinphoneCall call = LinphoneManager.getLc().getCalls()[0]; LinphoneCall.State callState = call.getState(); From a01a6a60f81e574ecea33c499ff46cfb6051d837 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 28 Jun 2016 09:31:22 +0200 Subject: [PATCH 47/56] Fixed message notification touch not going to chat but staying on call view while in call --- src/org/linphone/LinphoneActivity.java | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index 44421ca98..048892aac 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -126,6 +126,7 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta private ListView accountsList, sideMenuItemList; private ImageView menu; private boolean fetchedContactsOnce = false; + private boolean doNotGoToCallActivity = false; static final boolean isInstanciated() { return instance != null; @@ -1248,7 +1249,7 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta LinphoneManager.getInstance().changeStatusToOnline(); - if (getIntent().getIntExtra("PreviousActivity", 0) != CALL_ACTIVITY) { + if (getIntent().getIntExtra("PreviousActivity", 0) != CALL_ACTIVITY && !doNotGoToCallActivity) { if (LinphoneManager.getLc().getCalls().length > 0) { LinphoneCall call = LinphoneManager.getLc().getCalls()[0]; LinphoneCall.State callState = call.getState(); @@ -1273,6 +1274,7 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta } } } + doNotGoToCallActivity = false; } @Override @@ -1309,6 +1311,7 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta if (extras != null && extras.getBoolean("GoToChat", false)) { LinphoneService.instance().removeMessageNotification(); String sipUri = extras.getString("ChatContactSipUri"); + doNotGoToCallActivity = true; displayChat(sipUri); } else if (extras != null && extras.getBoolean("Notification", false)) { if (LinphoneManager.getLc().getCallsNb() > 0) { @@ -1333,19 +1336,6 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta } } if (LinphoneManager.getLc().getCalls().length > 0) { - LinphoneCall calls[] = LinphoneManager.getLc().getCalls(); - if (calls.length > 0) { - LinphoneCall call = calls[0]; - - if (call != null && call.getState() != LinphoneCall.State.IncomingReceived) { - if (call.getCurrentParamsCopy().getVideoEnabled()) { - //startVideoActivity(call); - } else { - //startIncallActivity(call); - } - } - } - // If a call is ringing, start incomingcallactivity Collection incoming = new ArrayList(); incoming.add(LinphoneCall.State.IncomingReceived); From 9143a61ad0d32fb9a3fa3447484111f3ddc668ff Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Tue, 28 Jun 2016 16:08:25 +0200 Subject: [PATCH 48/56] Update in_app ui [--force] --- res/layout/in_app_purchasable.xml | 29 ++-- res/layout/in_app_store.xml | 143 ++++++++++-------- res/values/non_localizable_custom.xml | 1 + res/values/non_localizable_strings.xml | 1 + res/values/strings.xml | 1 + res/xml/preferences.xml | 4 +- src/org/linphone/SettingsFragment.java | 7 +- .../purchase/InAppPurchaseActivity.java | 39 +++-- 8 files changed, 118 insertions(+), 107 deletions(-) diff --git a/res/layout/in_app_purchasable.xml b/res/layout/in_app_purchasable.xml index 1af59b944..768c58572 100644 --- a/res/layout/in_app_purchasable.xml +++ b/res/layout/in_app_purchasable.xml @@ -5,26 +5,15 @@ android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="vertical" > - - - - - - - - + android:paddingRight="10dp" + android:layout_width="match_parent" + android:layout_height="40dp"/> \ No newline at end of file diff --git a/res/layout/in_app_store.xml b/res/layout/in_app_store.xml index 2e390ebba..ba4341aed 100644 --- a/res/layout/in_app_store.xml +++ b/res/layout/in_app_store.xml @@ -1,78 +1,95 @@ - - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + false false false + false https://www.linphone.org/wizard.php diff --git a/res/values/non_localizable_strings.xml b/res/values/non_localizable_strings.xml index 2a3a3b75b..0c20b9bd4 100644 --- a/res/values/non_localizable_strings.xml +++ b/res/values/non_localizable_strings.xml @@ -34,6 +34,7 @@ pref_sipaccounts_key setup_key pref_add_account_key + pref_in_app_store_key pref_video_key pref_video_codecs_key diff --git a/res/values/strings.xml b/res/values/strings.xml index 04e309487..dbae8f581 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -219,6 +219,7 @@ SIP Accounts Default account Add account + In-app Store Tunnel Hostname Port diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 83ef8c15d..8eab25ca6 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -10,8 +10,8 @@ android:key="@string/pref_add_account_key"/> + android:title="@string/pref_in_app_store" + android:key="@string/pref_in_app_store_key"/> purchasedItems; - private ImageView buyItemButton, recoverAccountButton; + private Button buyItemButton, recoverAccountButton; private Handler mHandler = new Handler(); private EditText username, email; @@ -66,11 +67,10 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList setContentView(R.layout.in_app_store); purchasableItemsLayout = (LinearLayout) findViewById(R.id.purchasable_items); - username = (EditText) findViewById(R.id.setup_username); - email = (EditText) findViewById(R.id.setup_email); - ImageView usernameOkIV = (ImageView) findViewById(R.id.setup_username_ok); - addUsernameHandler(username, usernameOkIV); - errorMessage = (TextView) findViewById(R.id.setup_error); + username = (EditText) findViewById(R.id.username); + email = (EditText) findViewById(R.id.email); + errorMessage = (TextView) findViewById(R.id.username_error); + addUsernameHandler(username, errorMessage); } @Override @@ -174,11 +174,11 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList private void displayBuySubscriptionButton(Purchasable item) { View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); - TextView text = (TextView) layout.findViewById(R.id.text); - text.setText("Buy account (" + item.getPrice() + ")"); - ImageView image = (ImageView) layout.findViewById(R.id.image); - image.setTag(item); - image.setOnClickListener(this);XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(); + Button button = (Button) layout.findViewById(R.id.inapp_button); + button.setText("Buy account (" + item.getPrice() + ")"); + button.setTag(item); + button.setOnClickListener(this); + XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(); xmlRpcHelper.createAccountAsync(new XmlRpcListenerBase() { @Override public void onAccountCreated(String result) { @@ -186,19 +186,18 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList } }, getUsername(), email.getText().toString(), null); - buyItemButton = image; + buyItemButton = button; buyItemButton.setEnabled(usernameOk); } private void displayRecoverAccountButton(Purchasable item) { View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout); - TextView text = (TextView) layout.findViewById(R.id.text); - text.setText("Recover account"); - ImageView image = (ImageView) layout.findViewById(R.id.image); - image.setTag(item); - image.setOnClickListener(this); + Button button = (Button) layout.findViewById(R.id.inapp_button); + button.setText("Recover account"); + button.setTag(item); + button.setOnClickListener(this); - recoverAccountButton = image; + recoverAccountButton = button; recoverAccountButton.setEnabled(usernameOk); } @@ -214,7 +213,7 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList return lpc.isPhoneNumber(username); } - private void addUsernameHandler(final EditText field, final ImageView icon) { + private void addUsernameHandler(final EditText field, final TextView errorMessage) { field.addTextChangedListener(new TextWatcher() { public void afterTextChanged(Editable s) { @@ -229,11 +228,9 @@ public class InAppPurchaseActivity extends Activity implements InAppPurchaseList String username = s.toString(); if (isUsernameCorrect(username)) { usernameOk = true; - //icon.setImageResource(R.drawable.wizard_ok); errorMessage.setText(""); } else { errorMessage.setText(R.string.wizard_username_incorrect); - //icon.setImageResource(R.drawable.wizard_notok); } if (buyItemButton != null) buyItemButton.setEnabled(usernameOk); if (recoverAccountButton != null) recoverAccountButton.setEnabled(usernameOk); From 516b37bc8da4b4e6dee8da887fff8e98cb26cf44 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Tue, 21 Jun 2016 10:38:11 +0200 Subject: [PATCH 49/56] Add recover password in assistant --- res/layout/assistant_linphone_login.xml | 9 ++++++++- res/values/strings.xml | 1 + src/org/linphone/assistant/LinphoneLoginFragment.java | 9 +++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/res/layout/assistant_linphone_login.xml b/res/layout/assistant_linphone_login.xml index 692b89a08..a1a0dc3aa 100644 --- a/res/layout/assistant_linphone_login.xml +++ b/res/layout/assistant_linphone_login.xml @@ -68,11 +68,18 @@ android:layout_height="40dp" android:singleLine="true"/> + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 04e309487..61bd98f75 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -87,6 +87,7 @@ Passwords do not match. Your username will be %s.\r\n\r\nIt may differ from your input to match requirements.\r\nDo you accept? Please enter your login and password + Forgot password ? Enter a number or an address diff --git a/src/org/linphone/assistant/LinphoneLoginFragment.java b/src/org/linphone/assistant/LinphoneLoginFragment.java index be0d610d3..bf4dcbd8d 100644 --- a/src/org/linphone/assistant/LinphoneLoginFragment.java +++ b/src/org/linphone/assistant/LinphoneLoginFragment.java @@ -22,13 +22,16 @@ import org.linphone.R; import android.app.Fragment; import android.os.Bundle; import android.text.Editable; +import android.text.Html; import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; /** * @author Sylvain Berfini @@ -36,16 +39,22 @@ import android.widget.Toast; public class LinphoneLoginFragment extends Fragment implements OnClickListener, TextWatcher { private EditText login, password, displayName; private Button apply; + private TextView forgotPassword; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.assistant_linphone_login, container, false); + + String url = "http://linphone.org/free-sip-service.html&action=recover"; login = (EditText) view.findViewById(R.id.assistant_username); login.addTextChangedListener(this); password = (EditText) view.findViewById(R.id.assistant_password); password.addTextChangedListener(this); + forgotPassword = (TextView) view.findViewById(R.id.forgot_password); + forgotPassword.setText(Html.fromHtml(""+ getString(R.string.forgot_password) + "")); + forgotPassword.setMovementMethod(LinkMovementMethod.getInstance()); displayName = (EditText) view.findViewById(R.id.assistant_display_name); apply = (Button) view.findViewById(R.id.assistant_apply); apply.setEnabled(false); From 4ade655473e9ba282a96c0a37273a1186cd2d482 Mon Sep 17 00:00:00 2001 From: Margaux Clerc Date: Fri, 24 Jun 2016 14:57:16 +0200 Subject: [PATCH 50/56] Add friendlist subscription --- res/layout/contact_cell.xml | 13 +++++++ res/raw/linphonerc_default | 2 ++ res/values/non_localizable_strings.xml | 1 + res/values/strings.xml | 1 + res/xml/account_preferences.xml | 4 +++ .../linphone/AccountPreferencesFragment.java | 16 ++++++++- src/org/linphone/ContactsListFragment.java | 9 ++++- src/org/linphone/LinphoneActivity.java | 34 +++++++++++++++++-- src/org/linphone/LinphoneContact.java | 5 +++ src/org/linphone/LinphoneManager.java | 15 ++++++-- src/org/linphone/LinphonePreferences.java | 9 +++++ 11 files changed, 102 insertions(+), 7 deletions(-) diff --git a/res/layout/contact_cell.xml b/res/layout/contact_cell.xml index cf233a2ce..0d5615ff8 100644 --- a/res/layout/contact_cell.xml +++ b/res/layout/contact_cell.xml @@ -95,6 +95,19 @@ android:scaleType="centerInside" android:src="@drawable/led_connected" /> + + + \ No newline at end of file diff --git a/res/raw/linphonerc_default b/res/raw/linphonerc_default index 61e812811..6ced4c1b6 100644 --- a/res/raw/linphonerc_default +++ b/res/raw/linphonerc_default @@ -7,6 +7,8 @@ contact="Linphone Android" use_info=0 use_ipv6=0 keepalive_period=30000 +rls_uri=sip:rls@sip1.linphone.org +use_rls_presence=1 [video] size=qvga diff --git a/res/values/non_localizable_strings.xml b/res/values/non_localizable_strings.xml index 2a3a3b75b..b7b638830 100644 --- a/res/values/non_localizable_strings.xml +++ b/res/values/non_localizable_strings.xml @@ -71,6 +71,7 @@ pref_bandwidth_limit_key pref_animation_enable_key pref_escape_plus_key + pref_friendlist_subscribe_key pref_echo_cancellation_key pref_autostart_key Outbound proxy diff --git a/res/values/strings.xml b/res/values/strings.xml index 61bd98f75..5c3ac39e7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -204,6 +204,7 @@ AVPF AVPF regular RTCP interval in seconds (between 1 and 5) Replace + by 00 + Friendlist subscribe Auth userid Enter authentication userid (optional) Display name diff --git a/res/xml/account_preferences.xml b/res/xml/account_preferences.xml index cd70b14fd..2e46704d7 100644 --- a/res/xml/account_preferences.xml +++ b/res/xml/account_preferences.xml @@ -72,6 +72,10 @@ + + diff --git a/src/org/linphone/AccountPreferencesFragment.java b/src/org/linphone/AccountPreferencesFragment.java index a1b217840..2b8b78538 100644 --- a/src/org/linphone/AccountPreferencesFragment.java +++ b/src/org/linphone/AccountPreferencesFragment.java @@ -176,7 +176,7 @@ public class AccountPreferencesFragment extends PreferencesListFragment { } preference.setSummary(newValue.toString()); return true; - } + } }; OnPreferenceChangeListener prefixChangedListener = new OnPreferenceChangeListener() { @Override @@ -234,6 +234,14 @@ public class AccountPreferencesFragment extends PreferencesListFragment { return true; } }; + OnPreferenceChangeListener friendlistSubscribeListener = new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean value = (Boolean) newValue; + LinphoneManager.getInstance().subscribeFriendList(value); + return true; + } + }; OnPreferenceChangeListener disableChangedListener = new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -365,6 +373,12 @@ public class AccountPreferencesFragment extends PreferencesListFragment { if(!isNewAccount){ escape.setChecked(mPrefs.getReplacePlusByZeroZero(n)); } + + CheckBoxPreference friendlistSubscribe = (CheckBoxPreference) advanced.getPreference(8); + friendlistSubscribe.setOnPreferenceChangeListener(friendlistSubscribeListener); + if(!isNewAccount){ + escape.setChecked(mPrefs.getReplacePlusByZeroZero(n)); + } PreferenceCategory manage = (PreferenceCategory) getPreferenceScreen().findPreference(getString(R.string.pref_manage_key)); final CheckBoxPreference disable = (CheckBoxPreference) manage.getPreference(0); diff --git a/src/org/linphone/ContactsListFragment.java b/src/org/linphone/ContactsListFragment.java index f81e59b00..6c2ffa021 100644 --- a/src/org/linphone/ContactsListFragment.java +++ b/src/org/linphone/ContactsListFragment.java @@ -459,6 +459,7 @@ public class ContactsListFragment extends Fragment implements OnClickListener, O } CheckBox delete = (CheckBox) view.findViewById(R.id.delete); + ImageView linphoneFriend = (ImageView) view.findViewById(R.id.friendLinphone); TextView name = (TextView) view.findViewById(R.id.name); name.setText(contact.getFullName()); @@ -474,7 +475,13 @@ public class ContactsListFragment extends Fragment implements OnClickListener, O separatorText.setText(String.valueOf(fullName.charAt(0))); } } - + + if(contact.isInLinphoneFriendList()){ + linphoneFriend.setVisibility(View.VISIBLE); + } else { + linphoneFriend.setVisibility(View.GONE); + } + ImageView icon = (ImageView) view.findViewById(R.id.contact_picture); if (contact.hasPhoto()) { LinphoneUtils.setImagePictureFromUri(getActivity(), icon, contact.getPhotoUri(), contact.getThumbnailUri()); diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index 048892aac..4af290067 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -50,13 +50,16 @@ import org.linphone.ui.AddressText; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.ActivityManager; import android.app.Dialog; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.drawable.ColorDrawable; @@ -1122,6 +1125,9 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta protected void onPause() { getIntent().putExtra("PreviousActivity", 0); + if(LinphonePreferences.instance().isFriendlistsubscriptionEnabled()){ + LinphoneManager.getInstance().subscribeFriendList(!isApplicationBroughtToBackground(this)); + } LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); if (lc != null) { lc.removeListener(mListener); @@ -1129,7 +1135,28 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta super.onPause(); } - + + public static boolean isApplicationBroughtToBackground(final Activity activity) { + ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE); + List tasks = activityManager.getRunningTasks(1); + + // Check the top Activity against the list of Activities contained in the Application's package. + if (!tasks.isEmpty()) { + ComponentName topActivity = tasks.get(0).topActivity; + try { + PackageInfo pi = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES); + for (ActivityInfo activityInfo : pi.activities) { + if(topActivity.getClassName().equals(activityInfo.name)) { + return false; + } + } + } catch( PackageManager.NameNotFoundException e) { + return false; // Never happens. + } + } + return true; + } + public void checkAndRequestExternalStoragePermission() { if (LinphonePreferences.instance().writeExternalStoragePermAsked()) { return; @@ -1171,7 +1198,7 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta public void checkAndRequestPermission(String permission, int result) { if (getPackageManager().checkPermission(permission, getPackageName()) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, new String[]{ permission }, result); + ActivityCompat.requestPermissions(this, new String[]{permission}, result); } } @@ -1244,6 +1271,9 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta refreshAccounts(); updateMissedChatCount(); + if(LinphonePreferences.instance().isFriendlistsubscriptionEnabled()){ + LinphoneManager.getInstance().subscribeFriendList(true); + } displayMissedCalls(LinphoneManager.getLc().getMissedCallsCount()); diff --git a/src/org/linphone/LinphoneContact.java b/src/org/linphone/LinphoneContact.java index cc73283ec..ba76ea319 100644 --- a/src/org/linphone/LinphoneContact.java +++ b/src/org/linphone/LinphoneContact.java @@ -29,6 +29,7 @@ import org.linphone.core.LinphoneCoreException; import org.linphone.core.LinphoneCoreFactory; import org.linphone.core.LinphoneFriend; import org.linphone.core.LinphoneFriend.SubscribePolicy; +import org.linphone.core.PresenceBasicStatus; import org.linphone.mediastream.Log; import android.content.ContentProviderOperation; @@ -446,6 +447,10 @@ public class LinphoneContact implements Serializable, Comparable= 0 && accountIndex < prxCfgs.length) From 8a1ee54523ce864da73233d77feaa24577b55a4f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 28 Jun 2016 17:01:40 +0200 Subject: [PATCH 51/56] [Switch submodule branch] Updated submodules --- submodules/bctoolbox | 2 +- submodules/belcard | 2 +- submodules/belle-sip | 2 +- submodules/cmake-builder | 2 +- submodules/linphone | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/submodules/bctoolbox b/submodules/bctoolbox index 9c8a905c2..ee545d1af 160000 --- a/submodules/bctoolbox +++ b/submodules/bctoolbox @@ -1 +1 @@ -Subproject commit 9c8a905c202e6ee26ec66a9b1658fe11151aa3c9 +Subproject commit ee545d1af6080cb765f02da0b396685e416e6b4a diff --git a/submodules/belcard b/submodules/belcard index 3207a12e8..4cbcda6d1 160000 --- a/submodules/belcard +++ b/submodules/belcard @@ -1 +1 @@ -Subproject commit 3207a12e894cce6d4d22ea9551963efbdd2df339 +Subproject commit 4cbcda6d13fc8dbf71ffeefc51af8d73952766a0 diff --git a/submodules/belle-sip b/submodules/belle-sip index a46c73939..00dd3c003 160000 --- a/submodules/belle-sip +++ b/submodules/belle-sip @@ -1 +1 @@ -Subproject commit a46c73939d7bb495e0604fe4493a55ff17a5aaf3 +Subproject commit 00dd3c003ed62cdd85f5f46261d5c050a943171e diff --git a/submodules/cmake-builder b/submodules/cmake-builder index eb1bc5f1b..7fc2446bb 160000 --- a/submodules/cmake-builder +++ b/submodules/cmake-builder @@ -1 +1 @@ -Subproject commit eb1bc5f1b9392d9c83e4df56a49849b57afbd25c +Subproject commit 7fc2446bbdfed9977648ffbfc9d86f5ce052ca49 diff --git a/submodules/linphone b/submodules/linphone index e81293ab4..2e086ee41 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit e81293ab4af60aa37c74d720b3f2da8f83d421e5 +Subproject commit 2e086ee4151c419916ac2e4da545ddada249651f From 3d71b55b32cbd23ab83783230fd4b475c1ea5038 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 28 Jun 2016 17:19:21 +0200 Subject: [PATCH 52/56] Updated belle-sip --- submodules/belle-sip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/belle-sip b/submodules/belle-sip index 00dd3c003..a58f79864 160000 --- a/submodules/belle-sip +++ b/submodules/belle-sip @@ -1 +1 @@ -Subproject commit 00dd3c003ed62cdd85f5f46261d5c050a943171e +Subproject commit a58f79864adc0a2ec402742226df01be6c9a4ae4 From e5b5c777bdbea9ed0e2e99f7e20a9eef87fccf86 Mon Sep 17 00:00:00 2001 From: Ghislain MARY Date: Wed, 29 Jun 2016 13:21:37 +0200 Subject: [PATCH 53/56] Update bctoolbox and cmake-builder submodules. --- submodules/bctoolbox | 2 +- submodules/cmake-builder | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/bctoolbox b/submodules/bctoolbox index ee545d1af..9352d7ca9 160000 --- a/submodules/bctoolbox +++ b/submodules/bctoolbox @@ -1 +1 @@ -Subproject commit ee545d1af6080cb765f02da0b396685e416e6b4a +Subproject commit 9352d7ca9e85004842ad1cf02c94138791e55c1c diff --git a/submodules/cmake-builder b/submodules/cmake-builder index 7fc2446bb..2b682aa2a 160000 --- a/submodules/cmake-builder +++ b/submodules/cmake-builder @@ -1 +1 @@ -Subproject commit 7fc2446bbdfed9977648ffbfc9d86f5ce052ca49 +Subproject commit 2b682aa2a26f8fb2ad6318d3e82cf238e3625c7b From ad0faea633ea3b7251d595a62c6af9154af465eb Mon Sep 17 00:00:00 2001 From: Gautier Pelloux-Prayer Date: Wed, 29 Jun 2016 16:41:05 +0200 Subject: [PATCH 54/56] Move from CUnit to BCUnit --- .gitmodules | 6 +++--- submodules/bctoolbox | 2 +- submodules/bcunit | 1 + submodules/belcard | 2 +- submodules/belle-sip | 2 +- submodules/bzrtp | 2 +- submodules/cmake-builder | 2 +- submodules/externals/cunit | 1 - submodules/linphone | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) create mode 160000 submodules/bcunit delete mode 160000 submodules/externals/cunit diff --git a/.gitmodules b/.gitmodules index 263d8ce9b..fa0235b31 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,9 +58,6 @@ [submodule "submodules/externals/libupnp"] path = submodules/externals/libupnp url = git://git.linphone.org/libupnp.git -[submodule "submodules/externals/cunit"] - path = submodules/externals/cunit - url = git://git.linphone.org/cunit.git [submodule "submodules/externals/axmlrpc"] path = submodules/externals/axmlrpc url = git://git.linphone.org/axmlrpc.git @@ -104,3 +101,6 @@ [submodule "submodules/belcard"] path = submodules/belcard url = git://git.linphone.org/belcard.git +[submodule "submodules/bcunit"] + path = submodules/bcunit + url = git://git.linphone.org/bcunit.git diff --git a/submodules/bctoolbox b/submodules/bctoolbox index 9352d7ca9..381df0104 160000 --- a/submodules/bctoolbox +++ b/submodules/bctoolbox @@ -1 +1 @@ -Subproject commit 9352d7ca9e85004842ad1cf02c94138791e55c1c +Subproject commit 381df01040121f427c5d1a533cee017be033487a diff --git a/submodules/bcunit b/submodules/bcunit new file mode 160000 index 000000000..53b8f5fed --- /dev/null +++ b/submodules/bcunit @@ -0,0 +1 @@ +Subproject commit 53b8f5fed434857af9934b377f21adfc6c334bd6 diff --git a/submodules/belcard b/submodules/belcard index 4cbcda6d1..8be40ede7 160000 --- a/submodules/belcard +++ b/submodules/belcard @@ -1 +1 @@ -Subproject commit 4cbcda6d13fc8dbf71ffeefc51af8d73952766a0 +Subproject commit 8be40ede7d484ba459e3836cf1d944f9694d8fe0 diff --git a/submodules/belle-sip b/submodules/belle-sip index a58f79864..374a1ae8b 160000 --- a/submodules/belle-sip +++ b/submodules/belle-sip @@ -1 +1 @@ -Subproject commit a58f79864adc0a2ec402742226df01be6c9a4ae4 +Subproject commit 374a1ae8be6eea497df54e90041b11d81bb32cdf diff --git a/submodules/bzrtp b/submodules/bzrtp index b8033c8dc..883d1cd5b 160000 --- a/submodules/bzrtp +++ b/submodules/bzrtp @@ -1 +1 @@ -Subproject commit b8033c8dc960b8fd446e3e68d37749828cf7f808 +Subproject commit 883d1cd5b00ff8bdebdb65dc420d0c60e7edef1c diff --git a/submodules/cmake-builder b/submodules/cmake-builder index 2b682aa2a..a76e657cb 160000 --- a/submodules/cmake-builder +++ b/submodules/cmake-builder @@ -1 +1 @@ -Subproject commit 2b682aa2a26f8fb2ad6318d3e82cf238e3625c7b +Subproject commit a76e657cb8848094d6c5fc27666d1f7439de0b89 diff --git a/submodules/externals/cunit b/submodules/externals/cunit deleted file mode 160000 index efca65998..000000000 --- a/submodules/externals/cunit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit efca6599807bd800d740ecc02c2e0b72b912fc69 diff --git a/submodules/linphone b/submodules/linphone index 2e086ee41..099a7daf0 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit 2e086ee4151c419916ac2e4da545ddada249651f +Subproject commit 099a7daf09f5abff212aae5ca1ffb311c74b54e1 From 1537715830e3f2b5e10904591d5e581aa7d8ecf2 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Wed, 29 Jun 2016 17:01:57 +0200 Subject: [PATCH 55/56] Updated submodules --- submodules/belr | 2 +- submodules/linphone | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/belr b/submodules/belr index 8e1921592..6de5b422e 160000 --- a/submodules/belr +++ b/submodules/belr @@ -1 +1 @@ -Subproject commit 8e1921592df1abd7a6a00504410d776822c2b46f +Subproject commit 6de5b422e6933a5c9c281ad3b666d63be7045a77 diff --git a/submodules/linphone b/submodules/linphone index 099a7daf0..ae708bdd8 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit 099a7daf09f5abff212aae5ca1ffb311c74b54e1 +Subproject commit ae708bdd8cabe0efa5afc102ee7a06e324594847 From f7c1e67b2e4b750985a7eab6b327bca5917e600a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 30 Jun 2016 11:28:43 +0200 Subject: [PATCH 56/56] Updated belcard --- submodules/belcard | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/belcard b/submodules/belcard index 8be40ede7..7ba5c18d4 160000 --- a/submodules/belcard +++ b/submodules/belcard @@ -1 +1 @@ -Subproject commit 8be40ede7d484ba459e3836cf1d944f9694d8fe0 +Subproject commit 7ba5c18d4e8d12baafb33d2146006d573245ff65