diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/com/android/vending/billing/IInAppBillingService.aidl | 144 | ||||
| -rw-r--r-- | src/de/blinkt/openvpn/fragments/AboutFragment.java | 293 | 
2 files changed, 404 insertions, 33 deletions
| diff --git a/src/com/android/vending/billing/IInAppBillingService.aidl b/src/com/android/vending/billing/IInAppBillingService.aidl new file mode 100644 index 00000000..2a492f78 --- /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/de/blinkt/openvpn/fragments/AboutFragment.java b/src/de/blinkt/openvpn/fragments/AboutFragment.java index 54bd3667..156493c1 100644 --- a/src/de/blinkt/openvpn/fragments/AboutFragment.java +++ b/src/de/blinkt/openvpn/fragments/AboutFragment.java @@ -1,54 +1,281 @@  package de.blinkt.openvpn.fragments;  import android.app.Fragment; +import android.app.PendingIntent; +import android.content.*;  import android.content.pm.PackageInfo;  import android.content.pm.PackageManager.NameNotFoundException;  import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException;  import android.text.Html; +import android.text.SpannableString;  import android.text.Spanned; +import android.text.TextUtils;  import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.util.Log; +import android.util.Pair;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup;  import android.widget.TextView; +import com.android.vending.billing.IInAppBillingService;  import de.blinkt.openvpn.R; +import org.json.JSONException; +import org.json.JSONObject; -public class AboutFragment extends Fragment  { +import java.util.*; + +public class AboutFragment extends Fragment implements View.OnClickListener { + +    public static final String INAPPITEM_TYPE_INAPP = "inapp"; +    public static final String RESPONSE_CODE = "RESPONSE_CODE"; +    private static final int DONATION_CODE = 12; +    private static final int BILLING_RESPONSE_RESULT_OK = 0; +    private static final String RESPONSE_BUY_INTENT = "BUY_INTENT"; +    private static final String[] donationSkus = { "donation1eur", "donation2eur", "donation5eur", "donation10eur"}; +    IInAppBillingService mService; +    Hashtable<View, String> viewToProduct = new Hashtable<View, String>(); +    ServiceConnection mServiceConn = new ServiceConnection() { +        @Override +        public void onServiceDisconnected(ComponentName name) { +            mService = null; +        } + +        @Override +        public void onServiceConnected(ComponentName name, +                                       IBinder service) { +            mService = IInAppBillingService.Stub.asInterface(service); +            initGooglePlayDonation(); + +        } +    }; + +    private void initGooglePlayDonation() { +        new Thread("queryGMSInApp") { +            @Override +            public void run() { +                initGMSDonateOptions(); +            } +        }.start(); +    } + +    private TextView gmsTextView; + +    @Override +    public void onCreate(Bundle savedInstanceState) { +        super.onCreate(savedInstanceState); +        getActivity().bindService(new +                Intent("com.android.vending.billing.InAppBillingService.BIND"), +                mServiceConn, Context.BIND_AUTO_CREATE); + +    } + +    @Override +    public void onDestroy() { +        super.onDestroy(); +        if (mServiceConn != null) { +            getActivity().unbindService(mServiceConn); +        } + +    } + +    private void initGMSDonateOptions() { +        try { +            int billingSupported = mService.isBillingSupported(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP); +            if (billingSupported != BILLING_RESPONSE_RESULT_OK) { +                Log.i("OpenVPN", "Play store billing not supported"); +                return; +            } + +            ArrayList skuList = new ArrayList(); +            Collections.addAll(skuList, donationSkus); +            Bundle querySkus = new Bundle(); +            querySkus.putStringArrayList("ITEM_ID_LIST", skuList); + +            Bundle ownedItems = mService.getPurchases(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP, null); + + +            if (ownedItems.getInt(RESPONSE_CODE) != BILLING_RESPONSE_RESULT_OK) +                return; + +            final ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); + +            Bundle skuDetails = mService.getSkuDetails(3, getActivity().getPackageName(), INAPPITEM_TYPE_INAPP, querySkus); + + +            if (skuDetails.getInt(RESPONSE_CODE) != BILLING_RESPONSE_RESULT_OK) +                return; + +            final ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST"); + +            getActivity().runOnUiThread(new Runnable() { +                @Override +                public void run() { +                    createPlayBuyOptions(ownedSkus, responseList); + +                } +            }); + +        } catch (RemoteException e) { +            e.printStackTrace(); +        } +    } + +    private static class SkuResponse { +        String title; +        String price; + +        SkuResponse(String p, String t) +        { +            title=t; +            price=p; +        } +    } + + + +    private void createPlayBuyOptions(ArrayList<String> ownedSkus, ArrayList<String> responseList) { +        try { +            Vector<Pair<String,String>> gdonation = new Vector<Pair<String, String>>(); + +            gdonation.add(new Pair<String, String>(getString(R.string.donatePlayStore),null)); +            HashMap<String, SkuResponse> responseMap = new HashMap<String, SkuResponse>(); +            for (String thisResponse : responseList) { +                JSONObject object = new JSONObject(thisResponse); +                responseMap.put( +                        object.getString("productId"), +                        new SkuResponse( +                                object.getString("price"), +                                object.getString("title"))); + +            } +            for (String sku: donationSkus) +                if (responseMap.containsKey(sku)) +                    gdonation.add(getSkuTitle(sku, +                            responseMap.get(sku).title, responseMap.get(sku).price, ownedSkus)); + +            String gmsTextString=""; +            for(int i=0;i<gdonation.size();i++) { +                if(i==1) +                    gmsTextString+= "  "; +                else if(i>1) +                    gmsTextString+= ", "; +                gmsTextString+=gdonation.elementAt(i).first; +            } +            SpannableString gmsText = new SpannableString(gmsTextString); + + +            int lStart = 0; +            int lEnd=0; +            for(Pair<String, String> item:gdonation){ +                lEnd = lStart + item.first.length(); +                if (item.second!=null) { +                    final String mSku = item.second; +                    ClickableSpan cspan = new ClickableSpan() +                    { +                        @Override +                        public void onClick(View widget) { +                            triggerBuy(mSku); +                        } +                    }; +                    gmsText.setSpan(cspan,lStart,lEnd,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); +                } +                lStart = lEnd+2; // Account for ", " between items +            } + +            if(gmsTextView !=null) { +                gmsTextView.setText(gmsText); +                gmsTextView.setMovementMethod(LinkMovementMethod.getInstance()); +                gmsTextView.setVisibility(View.VISIBLE); +            } + +        } catch (JSONException e) { +            e.printStackTrace(); +        } + +    } + +    private Pair<String,String> getSkuTitle(final String sku, String title, String price, ArrayList<String> ownedSkus) { +        String text; +        if (ownedSkus.contains(sku)) +            return new Pair<String,String>(getString(R.string.thanks_for_donation, price),null); + +        if (price.contains("€")|| price.contains("\u20ac")) { +            text= title; +        } else { +            text = String.format(Locale.getDefault(), "%s (%s)", title, price); +        } +        //return text; +        return new Pair<String,String>(price, sku); + +    } + +    private void triggerBuy(String sku) { +        try { +            Bundle buyBundle +                    = mService.getBuyIntent(3, getActivity().getPackageName(), +                    sku, INAPPITEM_TYPE_INAPP, "Thanks for the donation! :)"); + + +            if (buyBundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) { +                PendingIntent buyIntent = (PendingIntent) buyBundle.getParcelable(RESPONSE_BUY_INTENT); +                getActivity().startIntentSenderForResult(buyIntent.getIntentSender(), DONATION_CODE, new Intent(), +                        0, 0, 0); +            } + +        } catch (RemoteException e) { +            e.printStackTrace(); +        } catch (IntentSender.SendIntentException e) { +            e.printStackTrace(); +        } +    }      @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container, -    		Bundle savedInstanceState) { -    	View v= inflater.inflate(R.layout.about, container, false); -    	TextView ver = (TextView) v.findViewById(R.id.version); -    	 -    	String version; -    	String name="Openvpn"; -		try { -			PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); -			version = packageinfo.versionName; -			name = getString(R.string.app); -		} catch (NameNotFoundException e) { -			version = "error fetching version"; -		} - -    	 -    	ver.setText(getString(R.string.version_info,name,version)); -    	 -    	TextView paypal = (TextView) v.findViewById(R.id.donatestring); -    	 -    	String donatetext = getActivity().getString(R.string.donatewithpaypal); -    	Spanned htmltext = Html.fromHtml(donatetext); -    	paypal.setText(htmltext); -    	paypal.setMovementMethod(LinkMovementMethod.getInstance()); -    	 -    	TextView translation = (TextView) v.findViewById(R.id.translation); -    	 -    	// Don't print a text for myself -    	if ( getString(R.string.translationby).contains("Arne Schwabe")) -    		translation.setText(""); -    	else -    		translation.setText(R.string.translationby); -    	return v; +                             Bundle savedInstanceState) { +        View v = inflater.inflate(R.layout.about, container, false); +        TextView ver = (TextView) v.findViewById(R.id.version); + +        String version; +        String name = "Openvpn"; +        try { +            PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0); +            version = packageinfo.versionName; +            name = getString(R.string.app); +        } catch (NameNotFoundException e) { +            version = "error fetching version"; +        } + + +        ver.setText(getString(R.string.version_info, name, version)); + +        TextView paypal = (TextView) v.findViewById(R.id.donatestring); + +        String donatetext = getActivity().getString(R.string.donatewithpaypal); +        Spanned htmltext = Html.fromHtml(donatetext); +        paypal.setText(htmltext); +        paypal.setMovementMethod(LinkMovementMethod.getInstance()); +        gmsTextView = (TextView) v.findViewById(R.id.donategms); +        /* recreating view without onCreate/onDestroy cycle */ +        if (mService!=null) +            initGooglePlayDonation(); + +        TextView translation = (TextView) v.findViewById(R.id.translation); + +        // Don't print a text for myself +        if (getString(R.string.translationby).contains("Arne Schwabe")) +            translation.setText(""); +        else +            translation.setText(R.string.translationby); +        return v;      } + + +    @Override +    public void onClick(View v) { + +    }  } | 
