diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 309e28424..7d99c6672 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -41,6 +41,8 @@
+
+
@@ -133,6 +135,14 @@
+
+
+
+
+
+
diff --git a/res/layout/in_app_purchasable.xml b/res/layout/in_app_purchasable.xml
new file mode 100644
index 000000000..768c58572
--- /dev/null
+++ b/res/layout/in_app_purchasable.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ 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..ba4341aed
--- /dev/null
+++ b/res/layout/in_app_store.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/raw/linphonerc_factory b/res/raw/linphonerc_factory
index e13273368..648a0ab83 100644
--- a/res/raw/linphonerc_factory
+++ b/res/raw/linphonerc_factory
@@ -31,4 +31,8 @@ dtmf_player_amp=0.1
ec_calibrator_cool_tones=1
[misc]
-max_calls=10
\ No newline at end of file
+max_calls=10
+
+[in-app-purchase]
+server_url=https://www.linphone.org/xmlrpc.php
+purchasable_items_ids=test_account_subscription
diff --git a/res/values/non_localizable_custom.xml b/res/values/non_localizable_custom.xml
index 9557622ca..4266988c7 100644
--- a/res/values/non_localizable_custom.xml
+++ b/res/values/non_localizable_custom.xml
@@ -1,5 +1,6 @@
+
sip.linphone.orgstun.linphone.org
@@ -13,6 +14,7 @@
falsefalsefalse
+ falsehttps://www.linphone.org/wizard.php
diff --git a/res/values/non_localizable_strings.xml b/res/values/non_localizable_strings.xml
index b7b638830..f204815ee 100644
--- a/res/values/non_localizable_strings.xml
+++ b/res/values/non_localizable_strings.xml
@@ -34,6 +34,7 @@
pref_sipaccounts_keysetup_keypref_add_account_key
+ pref_in_app_store_keypref_video_keypref_video_codecs_key
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5c3ac39e7..943878304 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -221,6 +221,7 @@
SIP AccountsDefault accountAdd account
+ In-app StoreTunnelHostnamePort
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 544eb39af..8eab25ca6 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -4,11 +4,15 @@
-
+
+
+
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 804e49e6b..8c8395074 100644
--- a/src/org/linphone/LinphonePreferences.java
+++ b/src/org/linphone/LinphonePreferences.java
@@ -19,6 +19,7 @@ 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 java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
@@ -39,6 +40,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;
@@ -1237,7 +1239,44 @@ public class LinphonePreferences {
}
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);
+ }
+
+ 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);
+ if (list != null) {
+ for(String purchasable : list.split(";")) {
+ if (purchasable.length() > 0) {
+ purchasables.add(purchasable);
+ }
+ }
+ }
+ return purchasables;
}
public String getXmlRpcServerUrl() {
diff --git a/src/org/linphone/SettingsFragment.java b/src/org/linphone/SettingsFragment.java
index b82ae71e1..3aa3195ef 100644
--- a/src/org/linphone/SettingsFragment.java
+++ b/src/org/linphone/SettingsFragment.java
@@ -34,8 +34,10 @@ 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.ui.LedPreference;
import org.linphone.ui.PreferencesListFragment;
+import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
@@ -52,6 +54,8 @@ import android.preference.PreferenceScreen;
* @author Sylvain Berfini
*/
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;
@@ -112,6 +116,14 @@ public class SettingsFragment extends PreferencesListFragment {
return true;
}
});
+ findPreference(getString(R.string.pref_in_app_store_key)).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
@@ -134,6 +146,11 @@ public class SettingsFragment extends PreferencesListFragment {
hidePreference(R.string.pref_add_account_key);
}
+ if(!getResources().getBoolean(R.bool.in_app_purchase_in_settings)){
+ hidePreference(R.string.pref_in_app_store_key);
+ }
+
+
if (getResources().getBoolean(R.bool.disable_animations)) {
uncheckAndHidePreference(R.string.pref_animation_enable_key);
}
diff --git a/src/org/linphone/assistant/RemoteProvisioningLoginActivity.java b/src/org/linphone/assistant/RemoteProvisioningLoginActivity.java
index 4987aeaf9..1d5a90afe 100644
--- a/src/org/linphone/assistant/RemoteProvisioningLoginActivity.java
+++ b/src/org/linphone/assistant/RemoteProvisioningLoginActivity.java
@@ -88,7 +88,7 @@ public class RemoteProvisioningLoginActivity extends Activity implements OnClick
private boolean storeAccount(String username, String password, String domain) {
LinphoneCore lc = LinphoneManager.getLc();
- XmlRpcHelper xmlRpcHelper = new XmlRpcHelper(null);
+ XmlRpcHelper xmlRpcHelper = new XmlRpcHelper();
xmlRpcHelper.getRemoteProvisioningFilenameAsync(new XmlRpcListenerBase() {
@Override
public void onRemoteProvisioningFilenameSent(String result) {
diff --git a/src/org/linphone/purchase/InAppPurchaseActivity.java b/src/org/linphone/purchase/InAppPurchaseActivity.java
new file mode 100644
index 000000000..ecc993a0b
--- /dev/null
+++ b/src/org/linphone/purchase/InAppPurchaseActivity.java
@@ -0,0 +1,240 @@
+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 java.util.Locale;
+
+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;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.EditText;
+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 purchasableItemsLayout;
+ private ArrayList purchasedItems;
+ private Button buyItemButton, recoverAccountButton;
+ private Handler mHandler = new Handler();
+
+ private EditText username, email;
+ private TextView errorMessage;
+ private boolean usernameOk = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ inAppPurchaseHelper = new InAppPurchaseHelper(this, this);
+
+ setContentView(R.layout.in_app_store);
+ purchasableItemsLayout = (LinearLayout) findViewById(R.id.purchasable_items);
+
+ username = (EditText) findViewById(R.id.username);
+ email = (EditText) findViewById(R.id.email);
+ errorMessage = (TextView) findViewById(R.id.username_error);
+ addUsernameHandler(username, errorMessage);
+ }
+
+ @Override
+ protected void onDestroy() {
+ inAppPurchaseHelper.destroy();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onServiceAvailableForQueries() {
+ email.setText(inAppPurchaseHelper.getGmailAccount());
+ email.setEnabled(false);
+
+ inAppPurchaseHelper.getPurchasedItemsAsync();
+ }
+
+ @Override
+ public void onAvailableItemsForPurchaseQueryFinished(ArrayList items) {
+ purchasableItemsLayout.removeAllViews();
+
+ for (Purchasable item : items) {
+ displayBuySubscriptionButton(item);
+ }
+ }
+
+ @Override
+ public void onPurchasedItemsQueryFinished(ArrayList items) {
+ purchasedItems = items;
+
+ 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);
+ }
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ Purchasable item = (Purchasable) v.getTag();
+ if (v.equals(recoverAccountButton)) {
+ 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());
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ inAppPurchaseHelper.parseAndVerifyPurchaseItemResultAsync(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onRecoverAccountSuccessful(boolean success) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ recoverAccountButton.setEnabled(false);
+ }
+ });
+ }
+
+ @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();
+ }
+ });
+ }
+
+ @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);
+ 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) {
+ //TODO
+ }
+ }, getUsername(), email.getText().toString(), null);
+
+ buyItemButton = button;
+ buyItemButton.setEnabled(usernameOk);
+ }
+
+ private void displayRecoverAccountButton(Purchasable item) {
+ View layout = LayoutInflater.from(this).inflate(R.layout.in_app_purchasable, purchasableItemsLayout);
+ Button button = (Button) layout.findViewById(R.id.inapp_button);
+ button.setText("Recover account");
+ button.setTag(item);
+ button.setOnClickListener(this);
+
+ recoverAccountButton = button;
+ recoverAccountButton.setEnabled(usernameOk);
+ }
+
+ 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) {
+ LinphoneProxyConfig lpc = LinphoneManager.getLc().createProxyConfig();
+ return lpc.isPhoneNumber(username);
+ }
+
+ private void addUsernameHandler(final EditText field, final TextView errorMessage) {
+ 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 = s.toString();
+ if (isUsernameCorrect(username)) {
+ usernameOk = true;
+ errorMessage.setText("");
+ } else {
+ errorMessage.setText(R.string.wizard_username_incorrect);
+ }
+ if (buyItemButton != null) buyItemButton.setEnabled(usernameOk);
+ if (recoverAccountButton != null) recoverAccountButton.setEnabled(usernameOk);
+ }
+ });
+ }
+}
diff --git a/src/org/linphone/purchase/InAppPurchaseHelper.java b/src/org/linphone/purchase/InAppPurchaseHelper.java
new file mode 100644
index 000000000..729aabb25
--- /dev/null
+++ b/src/org/linphone/purchase/InAppPurchaseHelper.java
@@ -0,0 +1,360 @@
+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.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 org.linphone.xmlrpc.XmlRpcHelper;
+import org.linphone.xmlrpc.XmlRpcListenerBase;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+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 android.util.Patterns;
+
+import com.android.vending.billing.IInAppBillingService;
+
+/**
+ * @author Sylvain Berfini
+ */
+public class InAppPurchaseHelper {
+ public static final int API_VERSION = 3;
+ 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 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";
+ 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";
+
+ 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";
+
+ private Context mContext;
+ private InAppPurchaseListener mListener;
+ private IInAppBillingService mService;
+ private ServiceConnection mServiceConn;
+ private Handler mHandler = new Handler();
+ private String mGmailAccount;
+
+ 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;
+ mGmailAccount = getGmailAccount();
+
+ mServiceConn = new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+
+ @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 || mGmailAccount == null) {
+ Log.e("[In-app purchase] Error: Subscriptions aren't supported!");
+ mListener.onError(CLIENT_ERROR_SUBSCRIPTION_PURCHASE_NOT_AVAILABLE);
+ } else {
+ 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");
+ 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);
+ }
+ }
+
+ private ArrayList getAvailableItemsForPurchase() {
+ ArrayList products = new ArrayList();
+ ArrayList skuList = LinphonePreferences.instance().getInAppPurchasables();
+ 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) {
+ 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);
+
+ 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: " + responseCodeToErrorMessage(response));
+ mListener.onError(responseCodeToErrorMessage(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) {
+ 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 = verifySignature(purchaseData, signature);
+ if (item != null) {
+ items.add(item);
+ }
+ }
+ } else {
+ Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response));
+ mListener.onError(responseCodeToErrorMessage(response));
+ }
+ }
+ } while (continuationToken != null);
+
+ if (mHandler != null && mListener != null) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mListener.onPurchasedItemsQueryFinished(items);
+ }
+ });
+ }
+ }
+ }).start();
+ }
+
+ public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data) {
+ 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 {
+ 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);
+ 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);
+ }
+ }
+ }
+ }
+
+ public void purchaseItemAsync(final String productId, final String sipIdentity) {
+ new Thread(new Runnable() {
+ public void run() {
+ purchaseItem(productId, sipIdentity);
+ }
+ }).start();
+ }
+
+ public void destroy() {
+ mContext.unbindService(mServiceConn);
+ }
+
+ public 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 boolean isEmailCorrect(String email) {
+ Pattern emailPattern = Patterns.EMAIL_ADDRESS;
+ return emailPattern.matcher(email).matches();
+ }
+
+ private Purchasable verifySignature(String payload, String signature) {
+ XmlRpcHelper helper = new XmlRpcHelper();
+ 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;
+ }
+
+ 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..a0f17878a
--- /dev/null
+++ b/src/org/linphone/purchase/InAppPurchaseListener.java
@@ -0,0 +1,67 @@
+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 {
+ /**
+ * Callback called when the in-app purchase listener is connected and available for queries
+ */
+ void onServiceAvailableForQueries();
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+ /**
+ * Callback called when the purchase has been validated by our external server
+ * @param success true if ok, false otherwise
+ */
+ void onPurchasedItemConfirmationQueryFinished(boolean success);
+
+ /**
+ * Callback called when the account has been recovered (or not)
+ * @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
+ */
+ void onError(String error);
+}
diff --git a/src/org/linphone/purchase/InAppPurchaseListenerBase.java b/src/org/linphone/purchase/InAppPurchaseListenerBase.java
new file mode 100644
index 000000000..b99a97ddc
--- /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(boolean success) {
+ // 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
new file mode 100644
index 000000000..22b8a84a8
--- /dev/null
+++ b/src/org/linphone/purchase/Purchasable.java
@@ -0,0 +1,108 @@
+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
+
+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;
+ private long expire;
+ private String purchasePayload, purchasePayloadSignature;
+ private String userData;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ public Purchasable setUserData(String data) {
+ this.userData = data;
+ return this;
+ }
+
+ public String getUserData() {
+ return this.userData;
+ }
+}
diff --git a/src/org/linphone/xmlrpc/XmlRpcHelper.java b/src/org/linphone/xmlrpc/XmlRpcHelper.java
index ed66dfabc..25d543b8b 100644
--- a/src/org/linphone/xmlrpc/XmlRpcHelper.java
+++ b/src/org/linphone/xmlrpc/XmlRpcHelper.java
@@ -24,14 +24,10 @@ public class XmlRpcHelper {
public static final String CLIENT_ERROR_SERVER_NOT_REACHABLE = "SERVER_NOT_REACHABLE";
private XMLRPCClient mXmlRpcClient;
-
- public XmlRpcHelper(String serverUrl) {
+
+ public XmlRpcHelper() {
try {
- if(serverUrl != null) {
- mXmlRpcClient = new XMLRPCClient(new URL(serverUrl));
- } else {
- mXmlRpcClient = new XMLRPCClient(new URL(LinphonePreferences.instance().getXmlRpcServerUrl()));
- }
+ mXmlRpcClient = new XMLRPCClient(new URL(LinphonePreferences.instance().getInAppPurchaseValidatingServerUrl()));
} catch (MalformedURLException e) {
Log.e(e);
}
@@ -274,12 +270,11 @@ public class XmlRpcHelper {
Log.e(error);
listener.onError(error.toString());
}
-
+
@Override
public void onResponse(long id, Object object) {
- String result = (String) object;
+ String result = (String)object;
Log.d("isAccountActivatedAsync: " + result);
-
if ("OK".equals(result)) {
listener.onAccountActivatedFetched(true);
return;
@@ -289,7 +284,6 @@ public class XmlRpcHelper {
}
listener.onAccountActivatedFetched(false);
}
-
@Override
public void onError(long id, XMLRPCException error) {
Log.e(error);
@@ -859,26 +853,4 @@ public class XmlRpcHelper {
listener.onError(CLIENT_ERROR_INVALID_SERVER_URL);
}
}
-
- public String getRemoteProvisioningFilename(String username, String domain, String password){
- if (mXmlRpcClient != null) {
- try {
- Object object = mXmlRpcClient.call("get_remote_provisioning_filename", username, domain, password);
- String result = (String)object;
- Log.d("getRemoteProvisioningFilename:: " + result);
-
- if (result.startsWith("ERROR_")) {
- Log.e(result);
- return result;
- }
- 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/XmlRpcListenerBase.java b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java
index 9f096c021..d849f40b5 100644
--- a/src/org/linphone/xmlrpc/XmlRpcListenerBase.java
+++ b/src/org/linphone/xmlrpc/XmlRpcListenerBase.java
@@ -84,7 +84,7 @@ public class XmlRpcListenerBase implements XmlRpcListener {
// TODO Auto-generated method stub
}
-
+
@Override
public void onRemoteProvisioningFilenameSent(String result) {
// TODO Auto-generated method stub