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); }