summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2012-08-13 16:00:27 +0400
committerAndrew Dolgov <[email protected]>2012-08-13 16:00:27 +0400
commit77352728e7085bf9883c2c7d94501bce1ab8e4cf (patch)
tree8502f62777c5e12c35863f50b90b1c41e13bbfdc
parente68c90cbceab13193597f203ad9f15a6d919af14 (diff)
remove in-app billing
add spinner-like triangles for headlines fragment context menu bump version
-rw-r--r--AndroidManifest.xml7
-rw-r--r--res/drawable-hdpi/ic_mailbox_collapsed_holo_light.pngbin0 -> 350 bytes
-rw-r--r--res/layout-port/headlines_row.xml12
-rw-r--r--res/layout-port/headlines_row_selected.xml10
-rw-r--r--res/layout-port/headlines_row_unread.xml9
-rw-r--r--res/layout-xlarge/headlines_row.xml8
-rw-r--r--res/layout-xlarge/headlines_row_selected.xml9
-rw-r--r--res/layout-xlarge/headlines_row_unread.xml9
-rw-r--r--res/layout/headlines_row.xml9
-rw-r--r--res/layout/headlines_row_selected.xml8
-rw-r--r--res/layout/headlines_row_unread.xml9
-rw-r--r--res/menu/main_menu.xml5
-rw-r--r--src/com/android/vending/billing/IMarketBillingService.aidl24
-rw-r--r--src/org/fox/ttrss/HeadlinesFragment.java13
-rw-r--r--src/org/fox/ttrss/MainActivity.java45
-rw-r--r--src/org/fox/ttrss/billing/BillingConstants.java63
-rw-r--r--src/org/fox/ttrss/billing/BillingHelper.java268
-rw-r--r--src/org/fox/ttrss/billing/BillingReceiver.java57
-rw-r--r--src/org/fox/ttrss/billing/BillingSecurity.java258
-rw-r--r--src/org/fox/ttrss/billing/BillingService.java60
-rw-r--r--src/org/fox/ttrss/offline/OfflineHeadlinesFragment.java13
21 files changed, 108 insertions, 788 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 10a13213..a2aba21c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,15 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.fox.ttrss"
- android:versionCode="86"
- android:versionName="0.6.10" >
+ android:versionCode="87"
+ android:versionName="0.6.11" >
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="11" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- <uses-permission android:name="com.android.vending.BILLING" />
<application
android:allowBackup="true"
@@ -48,8 +47,6 @@
<service
android:enabled="true"
android:name=".util.ImageCacheService" />
-
- <service android:name=".billing.BillingService" />
<meta-data
android:name="com.google.android.backup.api_key"
diff --git a/res/drawable-hdpi/ic_mailbox_collapsed_holo_light.png b/res/drawable-hdpi/ic_mailbox_collapsed_holo_light.png
new file mode 100644
index 00000000..2c395b80
--- /dev/null
+++ b/res/drawable-hdpi/ic_mailbox_collapsed_holo_light.png
Binary files differ
diff --git a/res/layout-port/headlines_row.xml b/res/layout-port/headlines_row.xml
index 106c659e..e3f4b705 100644
--- a/res/layout-port/headlines_row.xml
+++ b/res/layout-port/headlines_row.xml
@@ -69,8 +69,8 @@
android:layout_weight="1"
android:text="Jan 01, 00:00"
android:textColor="?headlineExcerptTextColor"
- android:textSize="11sp" />
-
+ android:textSize="11sp" />
+
<TextView
android:id="@+id/feed_title"
android:layout_width="wrap_content"
@@ -136,4 +136,12 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout-port/headlines_row_selected.xml b/res/layout-port/headlines_row_selected.xml
index 7f669957..f348cc8e 100644
--- a/res/layout-port/headlines_row_selected.xml
+++ b/res/layout-port/headlines_row_selected.xml
@@ -69,7 +69,7 @@
android:layout_weight="1"
android:text="Jan 01, 00:00"
android:textColor="?headlineSelectedExcerptTextColor"
- android:textSize="11sp" />
+ android:textSize="11sp" />
<TextView
android:id="@+id/feed_title"
@@ -137,4 +137,12 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout-port/headlines_row_unread.xml b/res/layout-port/headlines_row_unread.xml
index 98dc6248..7770bcc0 100644
--- a/res/layout-port/headlines_row_unread.xml
+++ b/res/layout-port/headlines_row_unread.xml
@@ -136,4 +136,13 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout-xlarge/headlines_row.xml b/res/layout-xlarge/headlines_row.xml
index 6b46d81d..bb673f9f 100644
--- a/res/layout-xlarge/headlines_row.xml
+++ b/res/layout-xlarge/headlines_row.xml
@@ -130,4 +130,12 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout-xlarge/headlines_row_selected.xml b/res/layout-xlarge/headlines_row_selected.xml
index 66343703..5df99772 100644
--- a/res/layout-xlarge/headlines_row_selected.xml
+++ b/res/layout-xlarge/headlines_row_selected.xml
@@ -129,4 +129,13 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout-xlarge/headlines_row_unread.xml b/res/layout-xlarge/headlines_row_unread.xml
index 3e66e6ed..5ccac557 100644
--- a/res/layout-xlarge/headlines_row_unread.xml
+++ b/res/layout-xlarge/headlines_row_unread.xml
@@ -129,4 +129,13 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/headlines_row.xml b/res/layout/headlines_row.xml
index b028eeb0..a339b18e 100644
--- a/res/layout/headlines_row.xml
+++ b/res/layout/headlines_row.xml
@@ -143,4 +143,13 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/headlines_row_selected.xml b/res/layout/headlines_row_selected.xml
index e18f866e..edffdd2c 100644
--- a/res/layout/headlines_row_selected.xml
+++ b/res/layout/headlines_row_selected.xml
@@ -142,4 +142,12 @@
android:text="@string/attachment_copy" />
</LinearLayout>
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/headlines_row_unread.xml b/res/layout/headlines_row_unread.xml
index 33d50dfa..96fa7bd1 100644
--- a/res/layout/headlines_row_unread.xml
+++ b/res/layout/headlines_row_unread.xml
@@ -141,4 +141,13 @@
android:layout_weight="0"
android:text="@string/attachment_copy" />
</LinearLayout>
+
+ <ImageButton
+ android:id="@+id/article_menu_button"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_mailbox_collapsed_holo_light" />
+
</LinearLayout> \ No newline at end of file
diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml
index 5b6f34a8..c6e49511 100644
--- a/res/menu/main_menu.xml
+++ b/res/menu/main_menu.xml
@@ -155,11 +155,6 @@
android:showAsAction=""
android:title="@string/preferences"/>
- <item
- android:id="@+id/donate"
- android:showAsAction=""
- android:title="@string/donate"/>
-
<group android:id="@+id/menu_group_logged_out" >
<item
diff --git a/src/com/android/vending/billing/IMarketBillingService.aidl b/src/com/android/vending/billing/IMarketBillingService.aidl
deleted file mode 100644
index 6884b41f..00000000
--- a/src/com/android/vending/billing/IMarketBillingService.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-interface IMarketBillingService {
- /** Given the arguments in bundle form, returns a bundle for results. */
- Bundle sendBillingRequest(in Bundle bundle);
-}
diff --git a/src/org/fox/ttrss/HeadlinesFragment.java b/src/org/fox/ttrss/HeadlinesFragment.java
index d4adf744..6e24aa64 100644
--- a/src/org/fox/ttrss/HeadlinesFragment.java
+++ b/src/org/fox/ttrss/HeadlinesFragment.java
@@ -45,6 +45,7 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
@@ -596,6 +597,18 @@ public class HeadlinesFragment extends Fragment implements OnItemClickListener,
});
}
+ ImageButton ib = (ImageButton) v.findViewById(R.id.article_menu_button);
+
+ if (ib != null) {
+ ib.setVisibility(android.os.Build.VERSION.SDK_INT >= 10 ? View.VISIBLE : View.GONE);
+ ib.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getActivity().openContextMenu(v);
+ }
+ });
+ }
+
return v;
}
}
diff --git a/src/org/fox/ttrss/MainActivity.java b/src/org/fox/ttrss/MainActivity.java
index c3a7ea21..5cb281ef 100644
--- a/src/org/fox/ttrss/MainActivity.java
+++ b/src/org/fox/ttrss/MainActivity.java
@@ -8,8 +8,6 @@ import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
-import org.fox.ttrss.billing.BillingHelper;
-import org.fox.ttrss.billing.BillingService;
import org.fox.ttrss.offline.OfflineActivity;
import org.fox.ttrss.offline.OfflineDownloadService;
import org.fox.ttrss.offline.OfflineUploadService;
@@ -1037,45 +1035,6 @@ public class MainActivity extends CommonActivity implements OnlineServices {
case R.id.close_article:
closeArticle();
return true;
- case R.id.donate:
- if (true) {
- CharSequence[] items = { "Silver Donation ($2)", "Gold Donation ($5)", "Platinum Donation ($10)" };
-
- Dialog dialog = new Dialog(MainActivity.this);
- AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this)
- .setTitle(R.string.donate_select)
- .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- m_selectedProduct = which;
- }
- }).setNegativeButton(R.string.dialog_close, new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- }).setPositiveButton(R.string.donate_do, new OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (m_selectedProduct != -1 && m_selectedProduct < 3) {
- CharSequence[] products = { "donation_silver", "donation_gold", "donation_platinum2" };
-
- Log.d(TAG, "Selected product: " + products[m_selectedProduct]);
-
- BillingHelper.requestPurchase(MainActivity.this, (String) products[m_selectedProduct]);
-
- dialog.dismiss();
- }
- }
- });
-
- dialog = builder.create();
- dialog.show();
- }
- return true;
case android.R.id.home:
goBack(false);
return true;
@@ -1598,8 +1557,6 @@ public class MainActivity extends CommonActivity implements OnlineServices {
m_menu.findItem(R.id.set_labels).setEnabled(m_apiLevel >= 1);
m_menu.findItem(R.id.article_set_note).setEnabled(m_apiLevel >= 1);
-
- m_menu.findItem(R.id.donate).setVisible(BillingHelper.isBillingSupported());
} else {
m_menu.setGroupVisible(R.id.menu_group_logged_in, false);
@@ -1650,8 +1607,6 @@ public class MainActivity extends CommonActivity implements OnlineServices {
setProgressBarIndeterminateVisibility(false);
m_isOffline = false;
-
- startService(new Intent(MainActivity.this, BillingService.class));
initMainMenu();
diff --git a/src/org/fox/ttrss/billing/BillingConstants.java b/src/org/fox/ttrss/billing/BillingConstants.java
deleted file mode 100644
index eb440219..00000000
--- a/src/org/fox/ttrss/billing/BillingConstants.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.fox.ttrss.billing;
-
-
-public class BillingConstants {
-
- // The response codes for a request, defined by Android Market.
- public enum ResponseCode {
- RESULT_OK,
- RESULT_USER_CANCELED,
- RESULT_SERVICE_UNAVAILABLE,
- RESULT_BILLING_UNAVAILABLE,
- RESULT_ITEM_UNAVAILABLE,
- RESULT_DEVELOPER_ERROR,
- RESULT_ERROR;
-
- // Converts from an ordinal value to the ResponseCode
- public static ResponseCode valueOf(int index) {
- ResponseCode[] values = ResponseCode.values();
- if (index < 0 || index >= values.length) {
- return RESULT_ERROR;
- }
- return values[index];
- }
- }
-
- // The possible states of an in-app purchase, as defined by Android Market.
- public enum PurchaseState {
- // Responses to requestPurchase or restoreTransactions.
- PURCHASED, // User was charged for the order.
- CANCELED, // The charge failed on the server.
- REFUNDED; // User received a refund for the order.
-
- // Converts from an ordinal value to the PurchaseState
- public static PurchaseState valueOf(int index) {
- PurchaseState[] values = PurchaseState.values();
- if (index < 0 || index >= values.length) {
- return CANCELED;
- }
- return values[index];
- }
- }
-
- // These are the names of the extras that are passed in an intent from
- // Market to this application and cannot be changed.
- public static final String NOTIFICATION_ID = "notification_id";
- public static final String INAPP_SIGNED_DATA = "inapp_signed_data";
- public static final String INAPP_SIGNATURE = "inapp_signature";
- public static final String INAPP_REQUEST_ID = "request_id";
- public static final String INAPP_RESPONSE_CODE = "response_code";
-
- // Intent actions that we send from the BillingReceiver to the
- // BillingService. Defined by this application.
- public static final String ACTION_CONFIRM_NOTIFICATION = "com.example.dungeons.CONFIRM_NOTIFICATION";
- public static final String ACTION_GET_PURCHASE_INFORMATION = "com.example.dungeons.GET_PURCHASE_INFORMATION";
- public static final String ACTION_RESTORE_TRANSACTIONS = "com.example.dungeons.RESTORE_TRANSACTIONS";
-
- // Intent actions that we receive in the BillingReceiver from Market.
- // These are defined by Market and cannot be changed.
- public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY";
- public static final String ACTION_RESPONSE_CODE = "com.android.vending.billing.RESPONSE_CODE";
- public static final String ACTION_PURCHASE_STATE_CHANGED = "com.android.vending.billing.PURCHASE_STATE_CHANGED";
-
-}
diff --git a/src/org/fox/ttrss/billing/BillingHelper.java b/src/org/fox/ttrss/billing/BillingHelper.java
deleted file mode 100644
index dcf29322..00000000
--- a/src/org/fox/ttrss/billing/BillingHelper.java
+++ /dev/null
@@ -1,268 +0,0 @@
-package org.fox.ttrss.billing;
-
-import java.util.ArrayList;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.vending.billing.IMarketBillingService;
-
-import org.fox.ttrss.billing.BillingConstants.ResponseCode;
-import org.fox.ttrss.billing.BillingSecurity.VerifiedPurchase;
-
-public class BillingHelper {
-
- private static final String TAG = "BillingService";
-
- private static IMarketBillingService mService;
- private static Context mContext;
- private static Handler mCompletedHandler;
-
- protected static VerifiedPurchase latestPurchase;
-
- protected static void instantiateHelper(Context context, IMarketBillingService service) {
- mService = service;
- mContext = context;
- }
-
- protected static void setCompletedHandler(Handler handler){
- mCompletedHandler = handler;
- }
-
- public static boolean isBillingSupported() {
- if (amIDead()) {
- return false;
- }
- Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
- if (mService != null) {
- try {
- Bundle response = mService.sendBillingRequest(request);
- ResponseCode code = ResponseCode.valueOf((Integer) response.get("RESPONSE_CODE"));
- Log.i(TAG, "isBillingSupported response was: " + code.toString());
- if (ResponseCode.RESULT_OK.equals(code)) {
- return true;
- } else {
- return false;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "isBillingSupported response was: RemoteException", e);
- return false;
- }
- } else {
- Log.i(TAG, "isBillingSupported response was: BillingService.mService = null");
- return false;
- }
- }
-
- /**
- * A REQUEST_PURCHASE request also triggers two asynchronous responses (broadcast intents).
- * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides error information about the request. (which I ignore)
- * Next, if the request was successful, the Android Market application sends an IN_APP_NOTIFY broadcast intent.
- * This message contains a notification ID, which you can use to retrieve the transaction details for the REQUEST_PURCHASE
- * @param activityContext
- * @param itemId
- */
- public static void requestPurchase(Context activityContext, String itemId){
- if (amIDead()) {
- return;
- }
- Log.i(TAG, "requestPurchase()");
- Bundle request = makeRequestBundle("REQUEST_PURCHASE");
- request.putString("ITEM_ID", itemId);
- try {
- Bundle response = mService.sendBillingRequest(request);
-
- //The RESPONSE_CODE key provides you with the status of the request
- Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
- //The PURCHASE_INTENT key provides you with a PendingIntent, which you can use to launch the checkout UI
- PendingIntent pendingIntent = (PendingIntent) response.get("PURCHASE_INTENT");
- //The REQUEST_ID key provides you with a unique request identifier for the request
- Long requestIndentifier = (Long) response.get("REQUEST_ID");
- Log.i(TAG, "current request is:" + requestIndentifier);
- BillingConstants.ResponseCode responseCode = BillingConstants.ResponseCode.valueOf(responseCodeIndex);
- Log.i(TAG, "REQUEST_PURCHASE Sync Response code: "+responseCode.toString());
-
- startBuyPageActivity(pendingIntent, new Intent(), activityContext);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed, internet error maybe", e);
- Log.e(TAG, "Billing supported: "+isBillingSupported());
- }
- }
-
- /**
- * A GET_PURCHASE_INFORMATION request also triggers two asynchronous responses (broadcast intents).
- * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request. (which I ignore)
- * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent.
- * This message contains detailed transaction information.
- * The transaction information is contained in a signed JSON string (unencrypted).
- * The message includes the signature so you can verify the integrity of the signed string
- * @param notifyIds
- */
- protected static void getPurchaseInformation(String[] notifyIds){
- if (amIDead()) {
- return;
- }
- Log.i(TAG, "getPurchaseInformation()");
- Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
- // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate.
- // The Android Market application returns this nonce with the PURCHASE_STATE_CHANGED broadcast intent so you can verify the integrity of the transaction information.
- request.putLong("NONCE", BillingSecurity.generateNonce());
- // The NOTIFY_IDS key contains an array of notification IDs, which you received in the IN_APP_NOTIFY broadcast intent.
- request.putStringArray("NOTIFY_IDS", notifyIds);
- try {
- Bundle response = mService.sendBillingRequest(request);
-
- //The REQUEST_ID key provides you with a unique request identifier for the request
- Long requestIndentifier = (Long) response.get("REQUEST_ID");
- Log.i(TAG, "current request is:" + requestIndentifier);
- //The RESPONSE_CODE key provides you with the status of the request
- Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
- BillingConstants.ResponseCode responseCode = BillingConstants.ResponseCode.valueOf(responseCodeIndex);
- Log.i(TAG, "GET_PURCHASE_INFORMATION Sync Response code: "+responseCode.toString());
-
- } catch (RemoteException e) {
- Log.e(TAG, "Failed, internet error maybe", e);
- Log.e(TAG, "Billing supported: "+isBillingSupported());
- }
- }
-
- /**
- * To acknowledge that you received transaction information you send a
- * CONFIRM_NOTIFICATIONS request.
- *
- * A CONFIRM_NOTIFICATIONS request triggers a single asynchronous response�a RESPONSE_CODE broadcast intent.
- * This broadcast intent provides status and error information about the request.
- *
- * Note: As a best practice, you should not send a CONFIRM_NOTIFICATIONS request for a purchased item until you have delivered the item to the user.
- * This way, if your application crashes or something else prevents your application from delivering the product,
- * your application will still receive an IN_APP_NOTIFY broadcast intent from Android Market indicating that you need to deliver the product
- * @param notifyIds
- */
- protected static void confirmTransaction(String[] notifyIds) {
- if (amIDead()) {
- return;
- }
- Log.i(TAG, "confirmTransaction()");
- Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
- request.putStringArray("NOTIFY_IDS", notifyIds);
- try {
- Bundle response = mService.sendBillingRequest(request);
-
- //The REQUEST_ID key provides you with a unique request identifier for the request
- Long requestIndentifier = (Long) response.get("REQUEST_ID");
- Log.i(TAG, "current request is:" + requestIndentifier);
-
- //The RESPONSE_CODE key provides you with the status of the request
- Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
- BillingConstants.ResponseCode responseCode = BillingConstants.ResponseCode.valueOf(responseCodeIndex);
-
- Log.i(TAG, "CONFIRM_NOTIFICATIONS Sync Response code: "+responseCode.toString());
- } catch (RemoteException e) {
- Log.e(TAG, "Failed, internet error maybe", e);
- Log.e(TAG, "Billing supported: " + isBillingSupported());
- }
- }
-
- /**
- *
- * Can be used for when a user has reinstalled the app to give back prior purchases.
- * if an item for sale's purchase type is "managed per user account" this means google will have a record ofthis transaction
- *
- * A RESTORE_TRANSACTIONS request also triggers two asynchronous responses (broadcast intents).
- * First, the Android Market application sends a RESPONSE_CODE broadcast intent, which provides status and error information about the request.
- * Next, if the request was successful, the Android Market application sends a PURCHASE_STATE_CHANGED broadcast intent.
- * This message contains the detailed transaction information. The transaction information is contained in a signed JSON string (unencrypted).
- * The message includes the signature so you can verify the integrity of the signed string
- * @param nonce
- */
- protected static void restoreTransactionInformation(Long nonce) {
- if (amIDead()) {
- return;
- }
- Log.i(TAG, "confirmTransaction()");
- Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
- // The REQUEST_NONCE key contains a cryptographically secure nonce (number used once) that you must generate
- request.putLong("NONCE", nonce);
- try {
- Bundle response = mService.sendBillingRequest(request);
-
- //The REQUEST_ID key provides you with a unique request identifier for the request
- Long requestIndentifier = (Long) response.get("REQUEST_ID");
- Log.i(TAG, "current request is:" + requestIndentifier);
-
- //The RESPONSE_CODE key provides you with the status of the request
- Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
- BillingConstants.ResponseCode responseCode = BillingConstants.ResponseCode.valueOf(responseCodeIndex);
- Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "+responseCode.toString());
- } catch (RemoteException e) {
- Log.e(TAG, "Failed, internet error maybe", e);
- Log.e(TAG, "Billing supported: " + isBillingSupported());
- }
- }
-
- private static boolean amIDead() {
- if (mService == null || mContext == null) {
- Log.e(TAG, "BillingHelper not fully instantiated");
- return true;
- } else {
- return false;
- }
- }
-
- private static Bundle makeRequestBundle(String method) {
- Bundle request = new Bundle();
- request.putString("BILLING_REQUEST", method);
- request.putInt("API_VERSION", 1);
- request.putString("PACKAGE_NAME", mContext.getPackageName());
- return request;
- }
-
- /**
- *
- *
- * You must launch the pending intent from an activity context and not an application context
- * You cannot use the singleTop launch mode to launch the pending intent
- * @param pendingIntent
- * @param intent
- * @param context
- */
- private static void startBuyPageActivity(PendingIntent pendingIntent, Intent intent, Context context){
- //TODO add above 2.0 implementation with reflection, for now just using 1.6 implem
-
- // This is on Android 1.6. The in-app checkout page activity will be on its
- // own separate activity stack instead of on the activity stack of
- // the application.
- try {
- pendingIntent.send(context, 0, intent);
- } catch (CanceledException e){
- Log.e(TAG, "startBuyPageActivity CanceledException");
- }
- }
-
- protected static void verifyPurchase(String signedData, String signature) {
- ArrayList<VerifiedPurchase> purchases = BillingSecurity.verifyPurchase(signedData, signature);
- latestPurchase = purchases.get(0);
-
- confirmTransaction(new String[]{latestPurchase.notificationId});
-
- if(mCompletedHandler != null){
- mCompletedHandler.sendEmptyMessage(0);
- } else {
- Log.e(TAG, "verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");
- }
- }
-
- public static void stopService(){
- mContext.stopService(new Intent(mContext, BillingService.class));
- mService = null;
- mContext = null;
- mCompletedHandler = null;
- Log.i(TAG, "Stopping Service");
- }
-}
diff --git a/src/org/fox/ttrss/billing/BillingReceiver.java b/src/org/fox/ttrss/billing/BillingReceiver.java
deleted file mode 100644
index 9b772054..00000000
--- a/src/org/fox/ttrss/billing/BillingReceiver.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.fox.ttrss.billing;
-
-import static org.fox.ttrss.billing.BillingConstants.ACTION_NOTIFY;
-import static org.fox.ttrss.billing.BillingConstants.ACTION_PURCHASE_STATE_CHANGED;
-import static org.fox.ttrss.billing.BillingConstants.ACTION_RESPONSE_CODE;
-import static org.fox.ttrss.billing.BillingConstants.INAPP_REQUEST_ID;
-import static org.fox.ttrss.billing.BillingConstants.INAPP_RESPONSE_CODE;
-import static org.fox.ttrss.billing.BillingConstants.INAPP_SIGNATURE;
-import static org.fox.ttrss.billing.BillingConstants.INAPP_SIGNED_DATA;
-import static org.fox.ttrss.billing.BillingConstants.NOTIFICATION_ID;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class BillingReceiver extends BroadcastReceiver {
-
- private static final String TAG = "BillingService";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- Log.i(TAG, "Received action: " + action);
- if (ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
- String signedData = intent.getStringExtra(INAPP_SIGNED_DATA);
- String signature = intent.getStringExtra(INAPP_SIGNATURE);
- purchaseStateChanged(context, signedData, signature);
- } else if (ACTION_NOTIFY.equals(action)) {
- String notifyId = intent.getStringExtra(NOTIFICATION_ID);
- notify(context, notifyId);
- } else if (ACTION_RESPONSE_CODE.equals(action)) {
- long requestId = intent.getLongExtra(INAPP_REQUEST_ID, -1);
- int responseCodeIndex = intent.getIntExtra(INAPP_RESPONSE_CODE, BillingConstants.ResponseCode.RESULT_ERROR.ordinal());
- checkResponseCode(context, requestId, responseCodeIndex);
- } else {
- Log.e(TAG, "unexpected action: " + action);
- }
- }
-
-
- private void purchaseStateChanged(Context context, String signedData, String signature) {
- Log.i(TAG, "purchaseStateChanged got signedData: " + signedData);
- Log.i(TAG, "purchaseStateChanged got signature: " + signature);
- BillingHelper.verifyPurchase(signedData, signature);
- }
-
- private void notify(Context context, String notifyId) {
- Log.i(TAG, "notify got id: " + notifyId);
- String[] notifyIds = {notifyId};
- BillingHelper.getPurchaseInformation(notifyIds);
- }
-
- private void checkResponseCode(Context context, long requestId, int responseCodeIndex) {
- Log.i(TAG, "checkResponseCode got requestId: " + requestId);
- Log.i(TAG, "checkResponseCode got responseCode: " + BillingConstants.ResponseCode.valueOf(responseCodeIndex));
- }
-} \ No newline at end of file
diff --git a/src/org/fox/ttrss/billing/BillingSecurity.java b/src/org/fox/ttrss/billing/BillingSecurity.java
deleted file mode 100644
index 513d6f34..00000000
--- a/src/org/fox/ttrss/billing/BillingSecurity.java
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2010 Google Inc. All Rights Reserved.
-
-package org.fox.ttrss.billing;
-
-import java.security.InvalidKeyException;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.ArrayList;
-import java.util.HashSet;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.fox.ttrss.billing.BillingConstants.PurchaseState;
-import org.fox.ttrss.util.Base64;
-import org.fox.ttrss.util.Base64DecoderException;
-
-/**
- * Security-related methods. For a secure implementation, all of this code
- * should be implemented on a server that communicates with the application on
- * the device. For the sake of simplicity and clarity of this example, this code
- * is included here and is executed on the device. If you must verify the
- * purchases on the phone, you should obfuscate this code to make it harder for
- * an attacker to replace the code with stubs that treat all purchases as
- * verified.
- */
-public class BillingSecurity {
- private static final String TAG = "BillingService";
-
- private static final String KEY_FACTORY_ALGORITHM = "RSA";
- private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
- private static final SecureRandom RANDOM = new SecureRandom();
-
- /**
- * This keeps track of the nonces that we generated and sent to the server.
- * We need to keep track of these until we get back the purchase state and
- * send a confirmation message back to Android Market. If we are killed and
- * lose this list of nonces, it is not fatal. Android Market will send us a
- * new "notify" message and we will re-generate a new nonce. This has to be
- * "static" so that the {@link BillingReceiver} can check if a nonce exists.
- */
- private static HashSet<Long> sKnownNonces = new HashSet<Long>();
-
- /**
- * A class to hold the verified purchase information.
- */
- public static class VerifiedPurchase {
- public PurchaseState purchaseState;
- public String notificationId;
- public String productId;
- public String orderId;
- public long purchaseTime;
- public String developerPayload;
-
- public VerifiedPurchase(PurchaseState purchaseState, String notificationId, String productId, String orderId, long purchaseTime,
- String developerPayload) {
- this.purchaseState = purchaseState;
- this.notificationId = notificationId;
- this.productId = productId;
- this.orderId = orderId;
- this.purchaseTime = purchaseTime;
- this.developerPayload = developerPayload;
- }
-
- public boolean isPurchased(){
- return purchaseState.equals(PurchaseState.PURCHASED);
- }
-
-
- }
-
- /** Generates a nonce (a random number used once). */
- public static long generateNonce() {
- long nonce = RANDOM.nextLong();
- Log.i(TAG, "Nonce generateD: "+nonce);
- sKnownNonces.add(nonce);
- return nonce;
- }
-
- public static void removeNonce(long nonce) {
- sKnownNonces.remove(nonce);
- }
-
- public static boolean isNonceKnown(long nonce) {
- return sKnownNonces.contains(nonce);
- }
-
- /**
- * Verifies that the data was signed with the given signature, and returns
- * the list of verified purchases. The data is in JSON format and contains a
- * nonce (number used once) that we generated and that was signed (as part
- * of the whole data string) with a private key. The data also contains the
- * {@link PurchaseState} and product ID of the purchase. In the general
- * case, there can be an array of purchase transactions because there may be
- * delays in processing the purchase on the backend and then several
- * purchases can be batched together.
- *
- * @param signedData
- * the signed JSON string (signed, not encrypted)
- * @param signature
- * the signature for the data, signed with the private key
- */
- public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature) {
- if (signedData == null) {
- Log.e(TAG, "data is null");
- return null;
- }
- Log.i(TAG, "signedData: " + signedData);
- boolean verified = false;
- if (!TextUtils.isEmpty(signature)) {
- /**
- * Compute your public key (that you got from the Android Market
- * publisher site).
- *
- * Instead of just storing the entire literal string here embedded
- * in the program, construct the key at runtime from pieces or use
- * bit manipulation (for example, XOR with some other string) to
- * hide the actual key. The key itself is not secret information,
- * but we don't want to make it easy for an adversary to replace the
- * public key with one of their own and then fake messages from the
- * server.
- *
- * Generally, encryption keys / passwords should only be kept in
- * memory long enough to perform the operation they need to perform.
- */
- String base64EncodedPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApLWBv8eFC4f7h6gz3VE87XX2nqJB2KL2yNnNawmgaL/0nd6nXvVRiZ3iXLLP9k8RpLJ6rZPV778z8WzDLZATV3b2nh21KgjSNoG4em1oSf7pW4+AujqjLfNVRsXoJIWG+OMMd9o9l/D2YJTCXzSvgFIfF5EJRg6APZHEVrVJo8iXwnYM1tFfLjPfp10MtjLmD5tZW8o3hTmXJ3ZMDI12PL22G4KaE+BuQqI6PZ22m/pA85R6AuhNo2IUSE4XFUE8i7ANWDvdfDzQ5J0TTWAeHmUQCstdZ48z+6AjqD3L2omS/dKoBnlYxEUZms3iUa1/Co40nWU7sc2hqpmfNiG5oQIDAQAB";
- PublicKey key = BillingSecurity.generatePublicKey(base64EncodedPublicKey);
- verified = BillingSecurity.verify(key, signedData, signature);
- if (!verified) {
- Log.w(TAG, "signature does not match data.");
- return null;
- }
- }
-
- JSONObject jObject;
- JSONArray jTransactionsArray = null;
- int numTransactions = 0;
- long nonce = 0L;
- try {
- jObject = new JSONObject(signedData);
-
- // The nonce might be null if the user backed out of the buy page.
- nonce = jObject.optLong("nonce");
- jTransactionsArray = jObject.optJSONArray("orders");
- if (jTransactionsArray != null) {
- numTransactions = jTransactionsArray.length();
- }
- } catch (JSONException e) {
- return null;
- }
-
- if (!BillingSecurity.isNonceKnown(nonce)) {
- Log.w(TAG, "Nonce not found: " + nonce);
- return null;
- }
-
- ArrayList<VerifiedPurchase> purchases = new ArrayList<VerifiedPurchase>();
- try {
- for (int i = 0; i < numTransactions; i++) {
- JSONObject jElement = jTransactionsArray.getJSONObject(i);
- int response = jElement.getInt("purchaseState");
- PurchaseState purchaseState = PurchaseState.valueOf(response);
- String productId = jElement.getString("productId");
- String packageName = jElement.getString("packageName");
- long purchaseTime = jElement.getLong("purchaseTime");
- String orderId = jElement.optString("orderId", "");
- String notifyId = null;
- if (jElement.has("notificationId")) {
- notifyId = jElement.getString("notificationId");
- }
- String developerPayload = jElement.optString("developerPayload", null);
-
- // If the purchase state is PURCHASED, then we require a
- // verified nonce.
- if (purchaseState == PurchaseState.PURCHASED && !verified) {
- continue;
- }
- purchases.add(new VerifiedPurchase(purchaseState, notifyId, productId, orderId, purchaseTime, developerPayload));
- }
- } catch (JSONException e) {
- Log.e(TAG, "JSON exception: ", e);
- return null;
- }
- removeNonce(nonce);
- return purchases;
- }
-
- /**
- * Generates a PublicKey instance from a string containing the
- * Base64-encoded public key.
- *
- * @param encodedPublicKey
- * Base64-encoded public key
- * @throws IllegalArgumentException
- * if encodedPublicKey is invalid
- */
- public static PublicKey generatePublicKey(String encodedPublicKey) {
- try {
- byte[] decodedKey = Base64.decode(encodedPublicKey);
- KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
- return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- } catch (InvalidKeySpecException e) {
- Log.e(TAG, "Invalid key specification.");
- throw new IllegalArgumentException(e);
- } catch (Base64DecoderException e) {
- Log.e(TAG, "Base64DecoderException.", e);
- return null;
- }
- }
-
- /**
- * Verifies that the signature from the server matches the computed
- * signature on the data. Returns true if the data is correctly signed.
- *
- * @param publicKey
- * public key associated with the developer account
- * @param signedData
- * signed data from server
- * @param signature
- * server signature
- * @return true if the data and signature match
- */
- public static boolean verify(PublicKey publicKey, String signedData, String signature) {
- Log.i(TAG, "signature: " + signature);
- Signature sig;
- try {
- sig = Signature.getInstance(SIGNATURE_ALGORITHM);
- sig.initVerify(publicKey);
- sig.update(signedData.getBytes());
- if (!sig.verify(Base64.decode(signature))) {
- Log.e(TAG, "Signature verification failed.");
- return false;
- }
- return true;
- } catch (NoSuchAlgorithmException e) {
- Log.e(TAG, "NoSuchAlgorithmException.");
- } catch (InvalidKeyException e) {
- Log.e(TAG, "Invalid key specification.");
- } catch (SignatureException e) {
- Log.e(TAG, "Signature exception.");
- } catch (Base64DecoderException e) {
- Log.e(TAG, "Base64DecoderException.", e);
- }
- return false;
- }
-}
diff --git a/src/org/fox/ttrss/billing/BillingService.java b/src/org/fox/ttrss/billing/BillingService.java
deleted file mode 100644
index 2ae53234..00000000
--- a/src/org/fox/ttrss/billing/BillingService.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.fox.ttrss.billing;
-
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.vending.billing.IMarketBillingService;
-
-public class BillingService extends Service implements ServiceConnection{
-
- private static final String TAG = "BillingService";
-
- /** The service connection to the remote MarketBillingService. */
- private IMarketBillingService mService;
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i(TAG, "Service starting with onCreate");
-
- try {
- boolean bindResult = bindService(new Intent("com.android.vending.billing.MarketBillingService.BIND"), this, Context.BIND_AUTO_CREATE);
- if(bindResult){
- Log.i(TAG,"Market Billing Service Successfully Bound");
- } else {
- Log.e(TAG,"Market Billing Service could not be bound.");
- //TODO stop user continuing
- }
- } catch (SecurityException e){
- Log.e(TAG,"Market Billing Service could not be bound. SecurityException: "+e);
- //TODO stop user continuing
- }
- }
-
- public void setContext(Context context) {
- attachBaseContext(context);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.i(TAG, "Market Billing Service Connected.");
- mService = IMarketBillingService.Stub.asInterface(service);
- BillingHelper.instantiateHelper(getBaseContext(), mService);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
-
- }
-
-}
diff --git a/src/org/fox/ttrss/offline/OfflineHeadlinesFragment.java b/src/org/fox/ttrss/offline/OfflineHeadlinesFragment.java
index 69e662ab..40d42eb1 100644
--- a/src/org/fox/ttrss/offline/OfflineHeadlinesFragment.java
+++ b/src/org/fox/ttrss/offline/OfflineHeadlinesFragment.java
@@ -34,6 +34,7 @@ import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CheckBox;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@@ -453,6 +454,18 @@ public class OfflineHeadlinesFragment extends Fragment implements OnItemClickLis
});
}
+ ImageButton ib = (ImageButton) v.findViewById(R.id.article_menu_button);
+
+ if (ib != null) {
+ ib.setVisibility(android.os.Build.VERSION.SDK_INT >= 10 ? View.VISIBLE : View.GONE);
+ ib.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ getActivity().openContextMenu(v);
+ }
+ });
+ }
+
return v;
}
}