├── lib
├── signpost.jar
└── signpost-commonshttp4-1.2.1.1.jar
├── libs
└── android-support-v4.jar
├── res
├── drawable-hdpi
│ ├── ic_list_2cloud.png
│ ├── ic_menu_2cloud.png
│ ├── ic_tab_2cloud.png
│ ├── ic_dialog_2cloud.png
│ └── ic_launcher_2cloud.png
├── drawable-ldpi
│ ├── ic_list_2cloud.png
│ ├── ic_menu_2cloud.png
│ ├── ic_tab_2cloud.png
│ ├── ic_dialog_2cloud.png
│ └── ic_launcher_2cloud.png
├── drawable-mdpi
│ ├── ic_list_2cloud.png
│ ├── ic_menu_2cloud.png
│ ├── ic_tab_2cloud.png
│ ├── ic_dialog_2cloud.png
│ └── ic_launcher_2cloud.png
├── values
│ ├── arrays.xml
│ └── strings.xml
├── menu
│ └── post_link.xml
├── xml
│ └── preferences.xml
├── layout
│ ├── billing.xml
│ ├── main.xml
│ └── add_account.xml
└── layout-land
│ └── main.xml
├── .gitignore
├── default.properties
├── src
└── com
│ ├── suchagit
│ └── android2cloud
│ │ ├── util
│ │ ├── CheckTimeRequest.java
│ │ ├── PaymentNotificationResponse.java
│ │ ├── Base64DecoderException.java
│ │ ├── ErrorMethods.java
│ │ ├── AddLinkResponse.java
│ │ ├── CheckTimeResponse.java
│ │ ├── PaymentNotificationRequest.java
│ │ ├── AddLinkRequest.java
│ │ ├── OAuth.java
│ │ ├── OAuthAccount.java
│ │ ├── HttpClient.java
│ │ └── Security.java
│ │ ├── errors
│ │ ├── DefaultErrorDialogFragment.java
│ │ ├── PostLinkNullLinkDialogFragment.java
│ │ ├── IntentWithoutLinkDialogFragment.java
│ │ ├── PostLinkNullReceiverDialogFragment.java
│ │ ├── SelectLinkDialogFragment.java
│ │ ├── BillingCannotConnectDialogFragment.java
│ │ ├── OAuthActivityNullUriDialogFragment.java
│ │ ├── BillingNotSupportedDialogFragment.java
│ │ ├── OAuthWebviewNullIntentDialogFragment.java
│ │ ├── NoAccountsDialogFragment.java
│ │ ├── CorruptedAccountDialogFragment.java
│ │ ├── NoAccountSelectedDialogFragment.java
│ │ ├── DeprecatedHostExceptionDialogFragment.java
│ │ ├── OverQuotaDialogFragment.java
│ │ ├── IncorrectTimeDialogFragment.java
│ │ ├── IllegalStateExceptionDialogFragment.java
│ │ ├── IllegalArgumentExceptionDialogFragment.java
│ │ ├── HttpClientErrorDialogFragment.java
│ │ ├── UnsupportedEncodingExceptionDialogFragment.java
│ │ ├── OAuthMessageSignerExceptionDialogFragment.java
│ │ ├── OAuthNotAuthorizedExceptionDialogFragment.java
│ │ ├── OAuthExpectationFailedExceptionDialogFragment.java
│ │ ├── PostLinkAuthErrorDialogFragment.java
│ │ └── OAuthCommunicationExceptionDialogFragment.java
│ │ ├── OAuthWebView.java
│ │ ├── Preferences.java
│ │ ├── HttpService.java
│ │ ├── Consts.java
│ │ ├── BillingReceiver.java
│ │ ├── ResponseHandler.java
│ │ ├── PurchaseObserver.java
│ │ ├── Billing.java
│ │ ├── OAuthActivity.java
│ │ ├── ErrorDialogBuilder.java
│ │ ├── PostLinkActivity.java
│ │ └── BillingService.java
│ └── android
│ └── vending
│ └── billing
│ └── IMarketBillingService.aidl
├── LICENSE.md
├── proguard.cfg
├── AndroidManifest.xml
└── README.md
/lib/signpost.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/lib/signpost.jar
--------------------------------------------------------------------------------
/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_list_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-hdpi/ic_list_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_menu_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-hdpi/ic_menu_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_tab_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-hdpi/ic_tab_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_list_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-ldpi/ic_list_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_menu_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-ldpi/ic_menu_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_tab_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-ldpi/ic_tab_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_list_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-mdpi/ic_list_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_menu_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-mdpi/ic_menu_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_tab_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-mdpi/ic_tab_2cloud.png
--------------------------------------------------------------------------------
/lib/signpost-commonshttp4-1.2.1.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/lib/signpost-commonshttp4-1.2.1.1.jar
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_dialog_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-hdpi/ic_dialog_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_dialog_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-ldpi/ic_dialog_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_dialog_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-mdpi/ic_dialog_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-hdpi/ic_launcher_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/ic_launcher_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-ldpi/ic_launcher_2cloud.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher_2cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/2cloud/android2cloud/HEAD/res/drawable-mdpi/ic_launcher_2cloud.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.DS_Store
3 | *.class
4 | *.swp
5 | *java#
6 | *.classpath
7 | *.project
8 | *.settings
9 | bin/
10 | gen/
11 | private.txt
12 |
--------------------------------------------------------------------------------
/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/res/menu/post_link.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/default.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "build.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-8
12 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/CheckTimeRequest.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import java.io.UnsupportedEncodingException;
4 |
5 | import org.apache.http.client.methods.HttpGet;
6 | import org.apache.http.params.CoreProtocolPNames;
7 |
8 | public class CheckTimeRequest extends HttpGet {
9 |
10 | public CheckTimeRequest(String host) throws UnsupportedEncodingException {
11 | super(host+"util/time");
12 | this.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/PaymentNotificationResponse.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import android.os.Bundle;
4 | import android.os.Handler;
5 | import android.os.ResultReceiver;
6 |
7 | public class PaymentNotificationResponse extends ResultReceiver {
8 | private Receiver mReceiver;
9 |
10 | public PaymentNotificationResponse(Handler handler) {
11 | super(handler);
12 | }
13 |
14 | public void setReceiver(Receiver receiver) {
15 | mReceiver = receiver;
16 | }
17 |
18 | public interface Receiver {
19 | public void onReceiveResult(int resultCode, Bundle resultData);
20 | }
21 |
22 | @Override
23 | protected void onReceiveResult(int resultCode, Bundle resultData) {
24 | if(mReceiver != null) {
25 | mReceiver.onReceiveResult(resultCode, resultData);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/com/android/vending/billing/IMarketBillingService.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.vending.billing;
18 |
19 | import android.os.Bundle;
20 |
21 | interface IMarketBillingService {
22 | /** Given the arguments in bundle form, returns a bundle for results. */
23 | Bundle sendBillingRequest(in Bundle bundle);
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Second Bit LLC
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/Base64DecoderException.java:
--------------------------------------------------------------------------------
1 | // Copyright 2002, Google, Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package com.suchagit.android2cloud.util;
16 |
17 | /**
18 | * Exception thrown when encountering an invalid Base64 input character.
19 | *
20 | * @author nelson
21 | */
22 | public class Base64DecoderException extends Exception {
23 | public Base64DecoderException() {
24 | super();
25 | }
26 |
27 | public Base64DecoderException(String s) {
28 | super(s);
29 | }
30 |
31 | private static final long serialVersionUID = 1L;
32 | }
33 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/ErrorMethods.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import android.content.Intent;
4 | import android.content.pm.PackageManager.NameNotFoundException;
5 | import android.support.v4.app.Fragment;
6 |
7 | public class ErrorMethods {
8 | public static Intent getEmailIntent(Fragment context, String type, String message) {
9 | Intent i = new Intent(Intent.ACTION_SEND);
10 | i.setType("message/rfc822");
11 | i.putExtra(Intent.EXTRA_EMAIL , new String[]{"android@2cloudproject.com"});
12 | i.putExtra(Intent.EXTRA_SUBJECT, "In-App Error Report");
13 | String message_prefix = "Error:\n";
14 | message_prefix += "Type: "+type+"\n";
15 | try {
16 | message_prefix += "Version: " + context.getActivity().getPackageManager().getPackageInfo(context.getActivity().getPackageName(), 0 ).versionCode + "\n";
17 | } catch (NameNotFoundException e) {
18 | message_prefix += "\n";
19 | }
20 | message_prefix += "Android Version: " + android.os.Build.VERSION.SDK + "\n";
21 | message_prefix += "Phone: " + android.os.Build.MODEL + "\n";
22 | message = message_prefix + message;
23 | i.putExtra(Intent.EXTRA_TEXT , message);
24 | return i;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembernames class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembernames class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers enum * {
30 | public static **[] values();
31 | public static ** valueOf(java.lang.String);
32 | }
33 |
34 | -keep class * implements android.os.Parcelable {
35 | public static final android.os.Parcelable$Creator *;
36 | }
37 |
--------------------------------------------------------------------------------
/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
13 |
14 |
15 |
16 |
20 |
24 |
25 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/DefaultErrorDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.R;
4 |
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.os.Bundle;
9 | import android.support.v4.app.DialogFragment;
10 |
11 | public class DefaultErrorDialogFragment extends DialogFragment {
12 |
13 | public static DefaultErrorDialogFragment newInstance() {
14 | DefaultErrorDialogFragment frag = new DefaultErrorDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.default_error_title;
21 | int body = R.string.default_error_message;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(true)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | }
33 | }
34 | )
35 | .create();
36 | }
37 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/PostLinkNullLinkDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.support.v4.app.DialogFragment;
8 |
9 | import com.suchagit.android2cloud.R;
10 |
11 | public class PostLinkNullLinkDialogFragment extends DialogFragment {
12 |
13 | public static PostLinkNullLinkDialogFragment newInstance() {
14 | PostLinkNullLinkDialogFragment frag = new PostLinkNullLinkDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.default_error_title;
21 | int body = R.string.postlink_null_link_error;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(true)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | }
33 | }
34 | )
35 | .create();
36 | }
37 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/IntentWithoutLinkDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.R;
4 |
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.os.Bundle;
9 | import android.support.v4.app.DialogFragment;
10 |
11 | public class IntentWithoutLinkDialogFragment extends DialogFragment {
12 |
13 | public static IntentWithoutLinkDialogFragment newInstance() {
14 | IntentWithoutLinkDialogFragment frag = new IntentWithoutLinkDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.default_error_title;
21 | int body = R.string.intent_without_link_error;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(true)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | }
33 | }
34 | )
35 | .create();
36 | }
37 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/PostLinkNullReceiverDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.support.v4.app.DialogFragment;
8 |
9 | import com.suchagit.android2cloud.R;
10 |
11 | public class PostLinkNullReceiverDialogFragment extends DialogFragment {
12 |
13 | public static PostLinkNullReceiverDialogFragment newInstance() {
14 | PostLinkNullReceiverDialogFragment frag = new PostLinkNullReceiverDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.default_error_title;
21 | int body = R.string.postlink_null_receiver_error;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(true)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | }
33 | }
34 | )
35 | .create();
36 | }
37 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/SelectLinkDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.support.v4.app.DialogFragment;
8 |
9 | import com.suchagit.android2cloud.PostLinkActivity;
10 | import com.suchagit.android2cloud.R;
11 |
12 | public class SelectLinkDialogFragment extends DialogFragment {
13 |
14 | public static SelectLinkDialogFragment newInstance(Bundle args) {
15 | SelectLinkDialogFragment frag = new SelectLinkDialogFragment();
16 | frag.setArguments(args);
17 | return frag;
18 | }
19 |
20 | @Override
21 | public Dialog onCreateDialog(Bundle savedInstanceState) {
22 | int title = R.string.select_link_title;
23 | final CharSequence[] choices = getArguments().getCharSequenceArray("choices");
24 |
25 | return new AlertDialog.Builder(getActivity())
26 | .setCancelable(false)
27 | .setTitle(title)
28 | .setItems(choices, new DialogInterface.OnClickListener() {
29 | public void onClick(DialogInterface dialog, int item) {
30 | ((PostLinkActivity) getActivity()).linkChosen((String) choices[item]);
31 | }
32 | }
33 | )
34 | .create();
35 | }
36 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/BillingCannotConnectDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.R;
4 |
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.os.Bundle;
9 | import android.support.v4.app.DialogFragment;
10 |
11 | public class BillingCannotConnectDialogFragment extends DialogFragment {
12 |
13 | public static BillingCannotConnectDialogFragment newInstance() {
14 | BillingCannotConnectDialogFragment frag = new BillingCannotConnectDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.cannot_connect_title;
21 | int body = R.string.cannot_connect_message;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(false)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | getActivity().finish();
33 | }
34 | }
35 | )
36 | .create();
37 | }
38 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/OAuthActivityNullUriDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.support.v4.app.DialogFragment;
8 |
9 | import com.suchagit.android2cloud.R;
10 |
11 | public class OAuthActivityNullUriDialogFragment extends DialogFragment {
12 |
13 | public static OAuthActivityNullUriDialogFragment newInstance() {
14 | OAuthActivityNullUriDialogFragment frag = new OAuthActivityNullUriDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.default_error_title;
21 | int body = R.string.oauthactivity_null_uri_error;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(false)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | getActivity().finish();
33 | }
34 | }
35 | )
36 | .create();
37 | }
38 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/BillingNotSupportedDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.R;
4 |
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.os.Bundle;
9 | import android.support.v4.app.DialogFragment;
10 |
11 | public class BillingNotSupportedDialogFragment extends DialogFragment {
12 |
13 | public static BillingNotSupportedDialogFragment newInstance() {
14 | BillingNotSupportedDialogFragment frag = new BillingNotSupportedDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.billing_not_supported_title;
21 | int body = R.string.billing_not_supported_message;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(false)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | getActivity().finish();
33 | }
34 | }
35 | )
36 | .create();
37 | }
38 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/OAuthWebviewNullIntentDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.os.Bundle;
7 | import android.support.v4.app.DialogFragment;
8 |
9 | import com.suchagit.android2cloud.R;
10 |
11 | public class OAuthWebviewNullIntentDialogFragment extends DialogFragment {
12 |
13 | public static OAuthWebviewNullIntentDialogFragment newInstance() {
14 | OAuthWebviewNullIntentDialogFragment frag = new OAuthWebviewNullIntentDialogFragment();
15 | return frag;
16 | }
17 |
18 | @Override
19 | public Dialog onCreateDialog(Bundle savedInstanceState) {
20 | int title = R.string.default_error_title;
21 | int body = R.string.oauthwebview_null_intent_error;
22 | int yesButton = R.string.default_error_ok;
23 |
24 | return new AlertDialog.Builder(getActivity())
25 | .setCancelable(false)
26 | .setTitle(title)
27 | .setMessage(body)
28 | .setPositiveButton(yesButton,
29 | new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int whichButton) {
31 | dialog.cancel();
32 | getActivity().finish();
33 | }
34 | }
35 | )
36 | .create();
37 | }
38 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/NoAccountsDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.OAuthActivity;
4 | import com.suchagit.android2cloud.R;
5 |
6 | import android.app.AlertDialog;
7 | import android.app.Dialog;
8 | import android.content.DialogInterface;
9 | import android.content.Intent;
10 | import android.os.Bundle;
11 | import android.support.v4.app.DialogFragment;
12 |
13 | public class NoAccountsDialogFragment extends DialogFragment {
14 |
15 | public static NoAccountsDialogFragment newInstance() {
16 | NoAccountsDialogFragment frag = new NoAccountsDialogFragment();
17 | return frag;
18 | }
19 |
20 | @Override
21 | public Dialog onCreateDialog(Bundle savedInstanceState) {
22 | int title = R.string.default_error_title;
23 | int body = R.string.no_accounts_error;
24 | int yesButton = R.string.no_accounts_error_account_button;
25 |
26 | return new AlertDialog.Builder(getActivity())
27 | .setCancelable(false)
28 | .setTitle(title)
29 | .setMessage(body)
30 | .setPositiveButton(yesButton,
31 | new DialogInterface.OnClickListener() {
32 | public void onClick(DialogInterface dialog, int whichButton) {
33 | Intent i = new Intent(getActivity(), OAuthActivity.class);
34 | getActivity().startActivityForResult(i, 0x1234);
35 | }
36 | }
37 | )
38 | .create();
39 | }
40 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/CorruptedAccountDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.OAuthActivity;
4 | import com.suchagit.android2cloud.R;
5 |
6 | import android.app.AlertDialog;
7 | import android.app.Dialog;
8 | import android.content.DialogInterface;
9 | import android.content.Intent;
10 | import android.os.Bundle;
11 | import android.support.v4.app.DialogFragment;
12 |
13 | public class CorruptedAccountDialogFragment extends DialogFragment {
14 |
15 | public static CorruptedAccountDialogFragment newInstance() {
16 | CorruptedAccountDialogFragment frag = new CorruptedAccountDialogFragment();
17 | return frag;
18 | }
19 |
20 | @Override
21 | public Dialog onCreateDialog(Bundle savedInstanceState) {
22 | int title = R.string.default_error_title;
23 | int body = R.string.corrupted_account_error;
24 | int yesButton = R.string.corrupted_account_error_account_button;
25 |
26 | return new AlertDialog.Builder(getActivity())
27 | .setCancelable(false)
28 | .setTitle(title)
29 | .setMessage(body)
30 | .setPositiveButton(yesButton,
31 | new DialogInterface.OnClickListener() {
32 | public void onClick(DialogInterface dialog, int whichButton) {
33 | Intent i = new Intent(getActivity(), OAuthActivity.class);
34 | getActivity().startActivityForResult(i, 0x1234);
35 | }
36 | }
37 | )
38 | .create();
39 | }
40 | }
--------------------------------------------------------------------------------
/res/layout/billing.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
19 |
20 |
22 |
26 |
30 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/NoAccountSelectedDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.PostLinkActivity;
11 | import com.suchagit.android2cloud.Preferences;
12 | import com.suchagit.android2cloud.R;
13 |
14 | public class NoAccountSelectedDialogFragment extends DialogFragment {
15 |
16 | public static NoAccountSelectedDialogFragment newInstance() {
17 | NoAccountSelectedDialogFragment frag = new NoAccountSelectedDialogFragment();
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.default_error_title;
24 | int body = R.string.no_account_selected_error;
25 | int yesButton = R.string.no_account_selected_error_account_button;
26 |
27 | return new AlertDialog.Builder(getActivity())
28 | .setCancelable(false)
29 | .setTitle(title)
30 | .setMessage(body)
31 | .setPositiveButton(yesButton,
32 | new DialogInterface.OnClickListener() {
33 | public void onClick(DialogInterface dialog, int whichButton) {
34 | Intent i = new Intent(getActivity(), Preferences.class);
35 | getActivity().startActivityForResult(i, PostLinkActivity.EDIT_SETTINGS_REQ_CODE);
36 | }
37 | }
38 | )
39 | .create();
40 | }
41 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/DeprecatedHostExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.PostLinkActivity;
11 | import com.suchagit.android2cloud.Preferences;
12 | import com.suchagit.android2cloud.R;
13 |
14 | public class DeprecatedHostExceptionDialogFragment extends DialogFragment {
15 |
16 | public static DeprecatedHostExceptionDialogFragment newInstance() {
17 | DeprecatedHostExceptionDialogFragment frag = new DeprecatedHostExceptionDialogFragment();
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.default_error_title;
24 | int body = R.string.deprecated_host_error;
25 | int yesButton = R.string.default_error_ok;
26 |
27 | return new AlertDialog.Builder(getActivity())
28 | .setCancelable(false)
29 | .setTitle(title)
30 | .setMessage(body)
31 | .setPositiveButton(yesButton,
32 | new DialogInterface.OnClickListener() {
33 | public void onClick(DialogInterface dialog, int whichButton) {
34 | Intent i = new Intent(getActivity(), Preferences.class);
35 | getActivity().startActivityForResult(i, PostLinkActivity.EDIT_SETTINGS_REQ_CODE);
36 | }
37 | }
38 | )
39 | .create();
40 | }
41 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/AddLinkResponse.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.os.ResultReceiver;
9 | import android.util.Log;
10 |
11 | public class AddLinkResponse extends ResultReceiver {
12 | private Receiver mReceiver;
13 |
14 | public AddLinkResponse(Handler handler) {
15 | super(handler);
16 | }
17 |
18 | public void setReceiver(Receiver receiver) {
19 | mReceiver = receiver;
20 | }
21 |
22 | public interface Receiver {
23 | public void onReceiveResult(int resultCode, Bundle resultData);
24 | }
25 |
26 | @Override
27 | protected void onReceiveResult(int resultCode, Bundle resultData) {
28 | if (mReceiver != null) {
29 | Bundle newData = new Bundle();
30 | if(resultCode == HttpClient.STATUS_COMPLETE) {
31 | try {
32 | JSONObject json = new JSONObject(resultData.getString("raw_result"));
33 | newData.putInt("response_code", json.getInt("code"));
34 | newData.putString("link", json.getString("link"));
35 | newData.putString("raw_result", resultData.getString("raw_result"));
36 | } catch (JSONException e) {
37 | newData.putInt("response_code", 500);
38 | newData.putString("type", "client_error");
39 | Log.d("AddLinkResponse", resultData.getString("raw_result"));
40 | Log.d("AddLinkResponse", e.getMessage());
41 | }
42 | } else if(resultCode == HttpClient.STATUS_ERROR) {
43 | newData = resultData;
44 | }
45 | mReceiver.onReceiveResult(resultCode, newData);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/OverQuotaDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.Billing;
11 | import com.suchagit.android2cloud.PostLinkActivity;
12 | import com.suchagit.android2cloud.R;
13 |
14 | public class OverQuotaDialogFragment extends DialogFragment {
15 |
16 | public static OverQuotaDialogFragment newInstance() {
17 | OverQuotaDialogFragment frag = new OverQuotaDialogFragment();
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.over_quota_error_title;
24 | int body = R.string.over_quota_error;
25 | int yesButton = R.string.default_error_ok;
26 | int noButton = R.string.over_quota_error_pay_button;
27 |
28 | return new AlertDialog.Builder(getActivity())
29 | .setCancelable(true)
30 | .setTitle(title)
31 | .setMessage(body)
32 | .setPositiveButton(yesButton,
33 | new DialogInterface.OnClickListener() {
34 | public void onClick(DialogInterface dialog, int whichButton) {
35 | dialog.cancel();
36 | }
37 | }
38 | ).setNegativeButton(noButton,
39 | new DialogInterface.OnClickListener() {
40 | public void onClick(DialogInterface dialog, int which) {
41 | Intent i = new Intent(getActivity(), Billing.class);
42 | getActivity().startActivityForResult(i, PostLinkActivity.BILLING_INTENT_CODE);
43 | }
44 | })
45 | .create();
46 | }
47 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/CheckTimeResponse.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.os.ResultReceiver;
9 | import android.util.Log;
10 |
11 | public class CheckTimeResponse extends ResultReceiver {
12 | private Receiver mReceiver;
13 | private Bundle passThrough;
14 |
15 | public CheckTimeResponse(Handler handler) {
16 | super(handler);
17 | }
18 |
19 | public void setReceiver(Receiver receiver) {
20 | mReceiver = receiver;
21 | }
22 |
23 | public void setPassThrough(Bundle error) {
24 | passThrough = error;
25 | }
26 |
27 | public Bundle getPassThrough() {
28 | return passThrough;
29 | }
30 |
31 | public interface Receiver {
32 | public void onReceiveResult(int resultCode, Bundle resultData);
33 | }
34 |
35 | @Override
36 | protected void onReceiveResult(int resultCode, Bundle resultData) {
37 | if (mReceiver != null) {
38 | Bundle newData = new Bundle();
39 | if(resultCode == HttpClient.STATUS_COMPLETE) {
40 | try {
41 | JSONObject json = new JSONObject(resultData.getString("raw_result"));
42 | newData.putInt("response_code", json.getInt("code"));
43 | newData.putString("timestamp", json.getString("timestamp"));
44 | newData.putString("raw_result", resultData.getString("raw_result"));
45 | } catch (JSONException e) {
46 | newData.putInt("response_code", 500);
47 | newData.putString("type", "client_error");
48 | Log.d("CheckTimeResponse", resultData.getString("raw_result"));
49 | Log.d("CheckTimeResponse", e.getMessage());
50 | }
51 | } else if(resultCode == HttpClient.STATUS_ERROR) {
52 | newData = resultData;
53 | }
54 | mReceiver.onReceiveResult(resultCode, newData);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/PaymentNotificationRequest.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import org.apache.http.NameValuePair;
8 | import org.apache.http.client.entity.UrlEncodedFormEntity;
9 | import org.apache.http.client.methods.HttpPost;
10 | import org.apache.http.message.BasicNameValuePair;
11 | import org.apache.http.params.CoreProtocolPNames;
12 |
13 | public class PaymentNotificationRequest extends HttpPost {
14 |
15 | private String orderNumber;
16 | private String itemId;
17 | private List data;
18 |
19 | public void setOrderNumber(String newOrderNumber) {
20 | this.orderNumber = newOrderNumber;
21 | }
22 |
23 | public String getOrderNumber() {
24 | return this.orderNumber;
25 | }
26 |
27 | public void setItemId(String newItemId) {
28 | this.itemId = newItemId;
29 | }
30 |
31 | public String getItemId() {
32 | return this.itemId;
33 | }
34 |
35 | public void addData(String name, String value) {
36 | if(this.data == null) {
37 | this.data = new ArrayList();
38 | }
39 | this.data.add(new BasicNameValuePair(name, value));
40 | }
41 |
42 | public void clearData() {
43 | this.data.clear();
44 | }
45 |
46 | public List getData() {
47 | return this.data;
48 | }
49 |
50 | public PaymentNotificationRequest(String host, String orderNumber, String itemId) throws UnsupportedEncodingException {
51 | super(host+"payments/notification");
52 | this.setOrderNumber(orderNumber);
53 | this.setItemId(itemId);
54 | this.addData("order_number", this.getOrderNumber());
55 | this.addData("item_id", this.getItemId());
56 | this.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
57 | this.addHeader("Content-Type", "application/x-www-form-urlencoded");
58 | UrlEncodedFormEntity entity = null;
59 | entity = new UrlEncodedFormEntity(this.getData(), "UTF-8");
60 | this.setEntity(entity);
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/IncorrectTimeDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.R;
11 |
12 | public class IncorrectTimeDialogFragment extends DialogFragment {
13 |
14 | public static IncorrectTimeDialogFragment newInstance(Bundle args) {
15 | IncorrectTimeDialogFragment frag = new IncorrectTimeDialogFragment();
16 | frag.setArguments(args);
17 | return frag;
18 | }
19 |
20 | @Override
21 | public Dialog onCreateDialog(Bundle savedInstanceState) {
22 | int title = R.string.default_error_title;
23 | int body = R.string.incorrect_time_error;
24 | int yesButton = R.string.default_error_ok;
25 | int noButton = R.string.incorrect_time_error_time_button;
26 |
27 | //final String timezone = getArguments().getString("timezone");
28 | //final String friendlyTime = getArguments().getString("friendlyTime");
29 |
30 | return new AlertDialog.Builder(getActivity())
31 | .setCancelable(false)
32 | .setTitle(title)
33 | .setMessage(body)
34 | .setPositiveButton(yesButton,
35 | new DialogInterface.OnClickListener() {
36 | public void onClick(DialogInterface dialog, int whichButton) {
37 | dialog.cancel();
38 | getActivity().finish();
39 | }
40 | }
41 | )
42 | .setNegativeButton(noButton,
43 | new DialogInterface.OnClickListener() {
44 | public void onClick(DialogInterface dialog, int id) {
45 | Intent i = new Intent(android.provider.Settings.ACTION_DATE_SETTINGS);
46 | getActivity().startActivity(i);
47 | }
48 | })
49 | .create();
50 | }
51 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/OAuthWebView.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Bundle;
7 | import android.support.v4.app.DialogFragment;
8 | import android.support.v4.app.FragmentActivity;
9 | import android.view.Window;
10 | import android.webkit.WebChromeClient;
11 | import android.webkit.WebView;
12 | import android.webkit.WebViewClient;
13 |
14 | import com.suchagit.android2cloud.errors.OAuthWebviewNullIntentDialogFragment;
15 | import com.suchagit.android2cloud.util.OAuth;
16 |
17 | public class OAuthWebView extends FragmentActivity {
18 |
19 | @Override
20 | public void onCreate(Bundle savedInstanceState){
21 | super.onCreate(savedInstanceState);
22 | getWindow().requestFeature(Window.FEATURE_PROGRESS);
23 | String request_url = "";
24 | if(this.getIntent() != null && this.getIntent().getDataString() != null){
25 | request_url = this.getIntent().getDataString();
26 | } else {
27 | showDialog(R.string.oauthwebview_null_intent_error);
28 | DialogFragment errorFragment = OAuthWebviewNullIntentDialogFragment.newInstance();
29 | errorFragment.show(getSupportFragmentManager(), "dialog");
30 | }
31 | WebView browser= new WebView(this);
32 | setContentView(browser);
33 |
34 | browser.getSettings().setJavaScriptEnabled(true);
35 |
36 | final Activity activity = this;
37 | browser.setWebChromeClient(new WebChromeClient() {
38 | public void onProgressChanged(WebView view, int progress) {
39 | activity.setProgress(progress * 1000);
40 | }
41 | });
42 | browser.setWebViewClient(new WebViewClient(){
43 |
44 | @Override
45 | public void onPageFinished(WebView view, String url){
46 | super.onPageFinished(view, url);
47 | Uri uri = Uri.parse(url);
48 | if(("/" + OAuth.CALLBACK).equals(uri.getPath())){
49 | Intent intent = new Intent(OAuthWebView.this, OAuthActivity.class);
50 | intent.setData(uri);
51 | setResult(RESULT_OK, intent);
52 | finish();
53 | }
54 | }
55 | });
56 | browser.loadUrl(request_url);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/AddLinkRequest.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import org.apache.http.NameValuePair;
8 | import org.apache.http.client.entity.UrlEncodedFormEntity;
9 | import org.apache.http.client.methods.HttpPost;
10 | import org.apache.http.message.BasicNameValuePair;
11 | import org.apache.http.params.CoreProtocolPNames;
12 |
13 | public class AddLinkRequest extends HttpPost {
14 |
15 | private String link;
16 | private String sender;
17 | private String receiver;
18 | private List data;
19 |
20 | public void setLink(String url) {
21 | this.link = url;
22 | }
23 |
24 | public String getLink() {
25 | return this.link;
26 | }
27 |
28 | public void setSender(String newSender) {
29 | this.sender = newSender;
30 | }
31 |
32 | public String getSender() {
33 | return this.sender;
34 | }
35 |
36 | public void setReceiver(String newReceiver) {
37 | this.receiver = newReceiver;
38 | }
39 |
40 | public String getReceiver() {
41 | return this.receiver;
42 | }
43 |
44 | public void addData(String name, String value) {
45 | if(this.data == null) {
46 | this.data = new ArrayList();
47 | }
48 | this.data.add(new BasicNameValuePair(name, value));
49 | }
50 |
51 | public void clearData() {
52 | this.data.clear();
53 | }
54 |
55 | public List getData() {
56 | return this.data;
57 | }
58 |
59 | public AddLinkRequest(String host, String receiver, String sender, String link) throws UnsupportedEncodingException {
60 | super(host+"links/add");
61 | this.setReceiver(receiver);
62 | this.setSender(sender);
63 | this.setLink(link);
64 | this.addData("link", this.getLink());
65 | this.addData("name", this.getSender());
66 | this.addData("receiver", this.getReceiver());
67 | this.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
68 | this.addHeader("Content-Type", "application/x-www-form-urlencoded");
69 | UrlEncodedFormEntity entity = null;
70 | entity = new UrlEncodedFormEntity(this.getData(), "UTF-8");
71 | this.setEntity(entity);
72 | }
73 |
74 | }
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
55 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/OAuth.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import oauth.signpost.OAuthConsumer;
4 | import oauth.signpost.OAuthProvider;
5 | import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
6 | import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
7 | import oauth.signpost.exception.OAuthCommunicationException;
8 | import oauth.signpost.exception.OAuthExpectationFailedException;
9 | import oauth.signpost.exception.OAuthMessageSignerException;
10 | import oauth.signpost.exception.OAuthNotAuthorizedException;
11 | import android.net.Uri;
12 |
13 | public class OAuth {
14 |
15 | private static OAuthProvider provider;
16 | private static OAuthConsumer consumer;
17 |
18 | private static final String REQUEST_TOKEN_URL = "_ah/OAuthGetRequestToken";
19 | private static final String ACCESS_TOKEN_URL = "_ah/OAuthGetAccessToken";
20 | private static final String AUTHORISE_TOKEN_URL = "_ah/OAuthAuthorizeToken?btmpl=mobile";
21 |
22 | public static final String CALLBACK = "callback/android/";
23 | public static final int INTENT_ID = 0x1234;
24 |
25 |
26 | public static OAuthConsumer makeConsumer() {
27 | return new CommonsHttpOAuthConsumer(HttpClient.CONSUMER_KEY, HttpClient.CONSUMER_SECRET);
28 | }
29 |
30 | public static OAuthProvider makeProvider(String host) {
31 | String request_token_url = host + REQUEST_TOKEN_URL;
32 | String access_token_url = host + ACCESS_TOKEN_URL;
33 | String authorise_token_url = host + AUTHORISE_TOKEN_URL;
34 | return new CommonsHttpOAuthProvider(request_token_url, access_token_url, authorise_token_url);
35 | }
36 |
37 | public static String getRequestUrl(String host, String account) throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
38 | if(consumer == null) {
39 | consumer = makeConsumer();
40 | }
41 |
42 | if(provider == null) {
43 | provider = makeProvider(host);
44 | }
45 | account = Uri.encode(account);
46 | String target = provider.retrieveRequestToken(consumer, host+CALLBACK+"?account="+account);
47 | return target;
48 | }
49 |
50 | public static OAuthConsumer getAccessToken(String host, String verifier) throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {
51 | if(provider == null) {
52 | provider = makeProvider(host);
53 | }
54 |
55 | if(consumer == null) {
56 | consumer = makeConsumer();
57 | }
58 | provider.retrieveAccessToken(consumer, verifier);
59 | return consumer;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
4 |
7 |
9 |
13 |
14 |
17 |
21 |
22 |
26 |
30 |
31 |
32 |
37 |
43 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/IllegalStateExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.R;
4 | import com.suchagit.android2cloud.util.ErrorMethods;
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.content.Intent;
9 | import android.os.Bundle;
10 | import android.support.v4.app.DialogFragment;
11 |
12 | public class IllegalStateExceptionDialogFragment extends DialogFragment {
13 |
14 | public static IllegalStateExceptionDialogFragment newInstance(Bundle args) {
15 | IllegalStateExceptionDialogFragment frag = new IllegalStateExceptionDialogFragment();
16 | frag.setArguments(args);
17 | return frag;
18 | }
19 |
20 | @Override
21 | public Dialog onCreateDialog(Bundle savedInstanceState) {
22 | int title = R.string.default_error_title;
23 | int body = R.string.illegal_state_exception_error;
24 | int yesButton = R.string.default_error_ok;
25 | int noButton = R.string.report_error_button;
26 |
27 | final String host = getArguments().getString("host");
28 | final String request_host = getArguments().getString("request_host");
29 | final String request_type = getArguments().getString("request_type");
30 | final String stacktrace = getArguments().getString("stacktrace");
31 |
32 | return new AlertDialog.Builder(getActivity())
33 | .setCancelable(true)
34 | .setTitle(title)
35 | .setMessage(body)
36 | .setPositiveButton(yesButton,
37 | new DialogInterface.OnClickListener() {
38 | public void onClick(DialogInterface dialog, int whichButton) {
39 | dialog.cancel();
40 | }
41 | }
42 | )
43 | .setNegativeButton(noButton,
44 | new DialogInterface.OnClickListener() {
45 | public void onClick(DialogInterface dialog, int whichButton) {
46 | String message = "Host: " + host + "\n";
47 | message += "Request Host: " + request_host + "\n";
48 | message += "Request Type: " + request_type + "\n";
49 | message += "Stacktrace: " + stacktrace + "\n";
50 | Intent report = ErrorMethods.getEmailIntent(IllegalStateExceptionDialogFragment.this, "illegal_state_exception_error", message);
51 | getActivity().startActivity(report);
52 | }
53 | }
54 | )
55 | .create();
56 | }
57 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/IllegalArgumentExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.R;
4 | import com.suchagit.android2cloud.util.ErrorMethods;
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.content.Intent;
9 | import android.os.Bundle;
10 | import android.support.v4.app.DialogFragment;
11 |
12 | public class IllegalArgumentExceptionDialogFragment extends DialogFragment {
13 |
14 | public static IllegalArgumentExceptionDialogFragment newInstance(Bundle args) {
15 | IllegalArgumentExceptionDialogFragment frag = new IllegalArgumentExceptionDialogFragment();
16 | frag.setArguments(args);
17 | return frag;
18 | }
19 |
20 | @Override
21 | public Dialog onCreateDialog(Bundle savedInstanceState) {
22 | int title = R.string.default_error_title;
23 | int body = R.string.illegal_argument_exception_error;
24 | int yesButton = R.string.default_error_ok;
25 | int noButton = R.string.report_error_button;
26 |
27 | final String host = getArguments().getString("host");
28 | final String request_host = getArguments().getString("request_host");
29 | final String request_type = getArguments().getString("request_type");
30 | final String stacktrace = getArguments().getString("stacktrace");
31 |
32 | return new AlertDialog.Builder(getActivity())
33 | .setCancelable(true)
34 | .setTitle(title)
35 | .setMessage(body)
36 | .setPositiveButton(yesButton,
37 | new DialogInterface.OnClickListener() {
38 | public void onClick(DialogInterface dialog, int whichButton) {
39 | dialog.cancel();
40 | }
41 | }
42 | )
43 | .setNegativeButton(noButton,
44 | new DialogInterface.OnClickListener() {
45 | public void onClick(DialogInterface dialog, int whichButton) {
46 | String message = "Host: " + host + "\n";
47 | message += "Request Host: " + request_host + "\n";
48 | message += "Request Type: " + request_type + "\n";
49 | message += "Stacktrace: " + stacktrace + "\n";
50 | Intent report = ErrorMethods.getEmailIntent(IllegalArgumentExceptionDialogFragment.this, "illegal_argument_exception_error", message);
51 | getActivity().startActivity(report);
52 | }
53 | }
54 | )
55 | .create();
56 | }
57 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/HttpClientErrorDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import com.suchagit.android2cloud.R;
4 | import com.suchagit.android2cloud.util.ErrorMethods;
5 | import android.app.AlertDialog;
6 | import android.app.Dialog;
7 | import android.content.DialogInterface;
8 | import android.content.Intent;
9 | import android.os.Bundle;
10 | import android.support.v4.app.DialogFragment;
11 |
12 | public class HttpClientErrorDialogFragment extends DialogFragment {
13 |
14 | public static HttpClientErrorDialogFragment newInstance(Bundle args) {
15 | HttpClientErrorDialogFragment frag = new HttpClientErrorDialogFragment();
16 | frag.setArguments(args);
17 | return frag;
18 | }
19 |
20 | @Override
21 | public Dialog onCreateDialog(Bundle savedInstanceState) {
22 | int title = R.string.default_error_title;
23 | int body = R.string.http_client_error;
24 | int yesButton = R.string.default_error_ok;
25 | int noButton = R.string.report_error_button;
26 |
27 | final String host = getArguments().getString("host");
28 | final String account = getArguments().getString("account");
29 | final String token = getArguments().getString("token");
30 | final String secret = getArguments().getString("secret");
31 | final String raw_data = getArguments().getString("raw_data");
32 |
33 | return new AlertDialog.Builder(getActivity())
34 | .setCancelable(true)
35 | .setTitle(title)
36 | .setMessage(body)
37 | .setPositiveButton(yesButton,
38 | new DialogInterface.OnClickListener() {
39 | public void onClick(DialogInterface dialog, int whichButton) {
40 | dialog.cancel();
41 | }
42 | }
43 | )
44 | .setNegativeButton(noButton,
45 | new DialogInterface.OnClickListener() {
46 | public void onClick(DialogInterface dialog, int whichButton) {
47 | String message = "Account: " + account +"\n";
48 | message += "Host: " + host + "\n";
49 | message += "Token: " + token + "\n";
50 | message += "Secret: " + secret + "\n";
51 | message += "Raw Result Data: \n";
52 | message += raw_data + "\n";
53 | Intent report = ErrorMethods.getEmailIntent(HttpClientErrorDialogFragment.this, "http_client_error", message);
54 | getActivity().startActivity(report);
55 | }
56 | }
57 | )
58 | .create();
59 | }
60 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/UnsupportedEncodingExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.R;
11 | import com.suchagit.android2cloud.util.ErrorMethods;
12 |
13 | public class UnsupportedEncodingExceptionDialogFragment extends DialogFragment {
14 |
15 | public static UnsupportedEncodingExceptionDialogFragment newInstance(Bundle args) {
16 | UnsupportedEncodingExceptionDialogFragment frag = new UnsupportedEncodingExceptionDialogFragment();
17 | frag.setArguments(args);
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.default_error_title;
24 | int body = R.string.unsupported_encoding_exception_error;
25 | int yesButton = R.string.default_error_ok;
26 | int noButton = R.string.report_error_button;
27 |
28 | final String host = getArguments().getString("host");
29 | final String account = getArguments().getString("account");
30 | final String device = getArguments().getString("device_name");
31 | final String receiver = getArguments().getString("receiver");
32 | final String link = getArguments().getString("link");
33 |
34 | return new AlertDialog.Builder(getActivity())
35 | .setCancelable(true)
36 | .setTitle(title)
37 | .setMessage(body)
38 | .setPositiveButton(yesButton,
39 | new DialogInterface.OnClickListener() {
40 | public void onClick(DialogInterface dialog, int whichButton) {
41 | dialog.cancel();
42 | }
43 | }
44 | )
45 | .setNegativeButton(noButton,
46 | new DialogInterface.OnClickListener() {
47 | public void onClick(DialogInterface dialog, int whichButton) {
48 | String message = "Account: " + account +"\n";
49 | message += "Host: " + host + "\n";
50 | message += "Device Name: " + device + "\n";
51 | message += "Receiver: " + receiver + "\n";
52 | message += "Link: " + link + "\n";
53 | Intent report = ErrorMethods.getEmailIntent(UnsupportedEncodingExceptionDialogFragment.this, "unsupported_encoding_exception_error", message);
54 | getActivity().startActivity(report);
55 | }
56 | }
57 | )
58 | .create();
59 | }
60 | }
--------------------------------------------------------------------------------
/res/layout-land/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
11 |
18 |
24 |
31 |
39 |
43 |
52 |
62 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/OAuthMessageSignerExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.R;
11 | import com.suchagit.android2cloud.util.ErrorMethods;
12 |
13 | public class OAuthMessageSignerExceptionDialogFragment extends DialogFragment {
14 |
15 | public static OAuthMessageSignerExceptionDialogFragment newInstance(Bundle args) {
16 | OAuthMessageSignerExceptionDialogFragment frag = new OAuthMessageSignerExceptionDialogFragment();
17 | frag.setArguments(args);
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.default_error_title;
24 | int body = R.string.oauth_message_signer_exception_error;
25 | int yesButton = R.string.default_error_ok;
26 | int noButton = R.string.report_error_button;
27 |
28 | final String stacktrace = getArguments().getString("stacktrace");
29 | final String host = getArguments().getString("host");
30 | final String account = getArguments().getString("account");
31 | final String verifier = getArguments().getString("verifier");
32 | final String requestUrl = getArguments().getString("request_url");
33 |
34 | return new AlertDialog.Builder(getActivity())
35 | .setCancelable(false)
36 | .setTitle(title)
37 | .setMessage(body)
38 | .setPositiveButton(yesButton,
39 | new DialogInterface.OnClickListener() {
40 | public void onClick(DialogInterface dialog, int whichButton) {
41 | dialog.cancel();
42 | getActivity().finish();
43 | }
44 | }
45 | )
46 | .setNegativeButton(noButton,
47 | new DialogInterface.OnClickListener() {
48 | public void onClick(DialogInterface dialog, int whichButton) {
49 | String message = "Account: " + account +"\n";
50 | message += "Host: " + host + "\n";
51 | if(requestUrl != null)
52 | message += "Request URL: " + requestUrl + "\n";
53 | else if(verifier != null)
54 | message += "Verifier: " + verifier + "\n";
55 | message += "Stacktrace: \n";
56 | message += stacktrace + "\n";
57 | Intent report = ErrorMethods.getEmailIntent(OAuthMessageSignerExceptionDialogFragment.this, "oauth_message_signer_exception_error", message);
58 | getActivity().startActivity(report);
59 | }
60 | }
61 | )
62 | .create();
63 | }
64 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/OAuthNotAuthorizedExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.R;
11 | import com.suchagit.android2cloud.util.ErrorMethods;
12 |
13 | public class OAuthNotAuthorizedExceptionDialogFragment extends DialogFragment {
14 |
15 | public static OAuthNotAuthorizedExceptionDialogFragment newInstance(Bundle args) {
16 | OAuthNotAuthorizedExceptionDialogFragment frag = new OAuthNotAuthorizedExceptionDialogFragment();
17 | frag.setArguments(args);
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.default_error_title;
24 | int body = R.string.oauth_not_authorized_exception_error;
25 | int yesButton = R.string.default_error_ok;
26 | int noButton = R.string.report_error_button;
27 |
28 | final String stacktrace = getArguments().getString("stacktrace");
29 | final String host = getArguments().getString("host");
30 | final String account = getArguments().getString("account");
31 | final String verifier = getArguments().getString("verifier");
32 | final String requestUrl = getArguments().getString("request_url");
33 |
34 | return new AlertDialog.Builder(getActivity())
35 | .setCancelable(false)
36 | .setTitle(title)
37 | .setMessage(body)
38 | .setPositiveButton(yesButton,
39 | new DialogInterface.OnClickListener() {
40 | public void onClick(DialogInterface dialog, int whichButton) {
41 | dialog.cancel();
42 | getActivity().finish();
43 | }
44 | }
45 | )
46 | .setNegativeButton(noButton,
47 | new DialogInterface.OnClickListener() {
48 | public void onClick(DialogInterface dialog, int whichButton) {
49 | String message = "Account: " + account +"\n";
50 | message += "Host: " + host + "\n";
51 | if(requestUrl != null)
52 | message += "Request URL: " + requestUrl + "\n";
53 | else if(verifier != null)
54 | message += "Verifier: " + verifier + "\n";
55 | message += "Stacktrace: \n";
56 | message += stacktrace + "\n";
57 | Intent report = ErrorMethods.getEmailIntent(OAuthNotAuthorizedExceptionDialogFragment.this, "oauth_not_authorized_exception_error", message);
58 | getActivity().startActivity(report);
59 | }
60 | }
61 | )
62 | .create();
63 | }
64 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/OAuthExpectationFailedExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.R;
11 | import com.suchagit.android2cloud.util.ErrorMethods;
12 |
13 | public class OAuthExpectationFailedExceptionDialogFragment extends DialogFragment {
14 |
15 | public static OAuthExpectationFailedExceptionDialogFragment newInstance(Bundle args) {
16 | OAuthExpectationFailedExceptionDialogFragment frag = new OAuthExpectationFailedExceptionDialogFragment();
17 | frag.setArguments(args);
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.default_error_title;
24 | int body = R.string.oauth_expectation_failed_exception_error;
25 | int yesButton = R.string.default_error_ok;
26 | int noButton = R.string.report_error_button;
27 |
28 | final String stacktrace = getArguments().getString("stacktrace");
29 | final String host = getArguments().getString("host");
30 | final String account = getArguments().getString("account");
31 | final String verifier = getArguments().getString("verifier");
32 | final String requestUrl = getArguments().getString("request_url");
33 |
34 | return new AlertDialog.Builder(getActivity())
35 | .setCancelable(false)
36 | .setTitle(title)
37 | .setMessage(body)
38 | .setPositiveButton(yesButton,
39 | new DialogInterface.OnClickListener() {
40 | public void onClick(DialogInterface dialog, int whichButton) {
41 | dialog.cancel();
42 | getActivity().finish();
43 | }
44 | }
45 | )
46 | .setNegativeButton(noButton,
47 | new DialogInterface.OnClickListener() {
48 | public void onClick(DialogInterface dialog, int whichButton) {
49 | String message = "Account: " + account +"\n";
50 | message += "Host: " + host + "\n";
51 | if(requestUrl != null)
52 | message += "Request URL: " + requestUrl + "\n";
53 | else if(verifier != null)
54 | message += "Verifier: " + verifier + "\n";
55 | message += "Stacktrace: \n";
56 | message += stacktrace + "\n";
57 | Intent report = ErrorMethods.getEmailIntent(OAuthExpectationFailedExceptionDialogFragment.this, "oauth_expectation_failed_exception_error", message);
58 | getActivity().startActivity(report);
59 | }
60 | }
61 | )
62 | .create();
63 | }
64 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/PostLinkAuthErrorDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.PostLinkActivity;
11 | import com.suchagit.android2cloud.Preferences;
12 | import com.suchagit.android2cloud.R;
13 | import com.suchagit.android2cloud.util.ErrorMethods;
14 |
15 | public class PostLinkAuthErrorDialogFragment extends DialogFragment {
16 |
17 | public static PostLinkAuthErrorDialogFragment newInstance(Bundle args) {
18 | PostLinkAuthErrorDialogFragment frag = new PostLinkAuthErrorDialogFragment();
19 | frag.setArguments(args);
20 | return frag;
21 | }
22 |
23 | @Override
24 | public Dialog onCreateDialog(Bundle savedInstanceState) {
25 | int title = R.string.default_error_title;
26 | int body = R.string.postlink_auth_error;
27 | int yesButton = R.string.default_error_ok;
28 | int noButton = R.string.report_error_button;
29 | int neutButton = R.string.postlink_auth_error_account_button;
30 |
31 | final String host = getArguments().getString("host");
32 | final String account = getArguments().getString("account");
33 | final String token = getArguments().getString("token");
34 | final String secret = getArguments().getString("secret");
35 |
36 |
37 | return new AlertDialog.Builder(getActivity())
38 | .setCancelable(true)
39 | .setTitle(title)
40 | .setMessage(body)
41 | .setPositiveButton(yesButton,
42 | new DialogInterface.OnClickListener() {
43 | public void onClick(DialogInterface dialog, int whichButton) {
44 | dialog.cancel();
45 | }
46 | }
47 | )
48 | .setNegativeButton(noButton,
49 | new DialogInterface.OnClickListener() {
50 | public void onClick(DialogInterface dialog, int whichButton) {
51 | String message = "Account: " + account +"\n";
52 | message += "Host: " + host + "\n";
53 | message += "Token: " + token + "\n";
54 | message += "Secret: " + secret + "\n";
55 | Intent report = ErrorMethods.getEmailIntent(PostLinkAuthErrorDialogFragment.this, "postlink_auth_error", message);
56 | getActivity().startActivity(report);
57 | }
58 | }
59 | )
60 | .setNeutralButton(neutButton, new DialogInterface.OnClickListener() {
61 | public void onClick(DialogInterface dialog, int id) {
62 | Intent i = new Intent(getActivity(), Preferences.class);
63 | getActivity().startActivityForResult(i, PostLinkActivity.EDIT_SETTINGS_REQ_CODE);
64 | }
65 | })
66 | .create();
67 | }
68 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/Preferences.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud;
2 |
3 | import com.suchagit.android2cloud.util.OAuthAccount;
4 |
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.os.Bundle;
8 | import android.preference.ListPreference;
9 | import android.preference.Preference;
10 | import android.preference.PreferenceActivity;
11 | import android.preference.PreferenceManager;
12 | import android.preference.Preference.OnPreferenceChangeListener;
13 | import android.preference.Preference.OnPreferenceClickListener;
14 | import android.widget.Toast;
15 |
16 |
17 | public class Preferences extends PreferenceActivity implements OnPreferenceChangeListener {
18 | protected String ACCOUNTS_PREFERENCES = "android2cloud-accounts";
19 | protected String SETTINGS_PREFERENCES = "android2cloud-settings";
20 | final int ACCOUNT_LIST_REQ_CODE = 0x1337;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | addPreferencesFromResource(R.xml.preferences);
26 | ListPreference accounts_pref = (ListPreference) findPreference("account");
27 | accounts_pref.setOnPreferenceChangeListener(this);
28 |
29 | Preference addNewAccount = (Preference) findPreference("addNewAccount");
30 | addNewAccount.setOnPreferenceClickListener(new OnPreferenceClickListener() {
31 | public boolean onPreferenceClick(Preference preference) {
32 | Intent i = new Intent(Preferences.this, OAuthActivity.class);
33 | startActivityForResult(i, ACCOUNT_LIST_REQ_CODE);
34 | return true;
35 | }
36 | });
37 |
38 | Preference deleteAccount = (Preference) findPreference("deleteAccount");
39 | deleteAccount.setOnPreferenceClickListener(new OnPreferenceClickListener() {
40 | public boolean onPreferenceClick(Preference preference) {
41 | SharedPreferences accounts = getSharedPreferences("android2cloud-accounts", 0);
42 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
43 | OAuthAccount account = new OAuthAccount(prefs.getString("account", ""), accounts);
44 | account.delete(accounts);
45 | Toast.makeText(Preferences.this, "Deleted account: "+account.getAccount(), Toast.LENGTH_LONG).show();
46 | return true;
47 | }
48 | });
49 | }
50 |
51 | @Override
52 | protected void onResume() {
53 | super.onResume();
54 | ListPreference accounts_pref = (ListPreference) findPreference("account");
55 | SharedPreferences accounts_prefs = getSharedPreferences(ACCOUNTS_PREFERENCES, 0);
56 | String[] accounts = OAuthAccount.getAccounts(accounts_prefs);
57 | int size = accounts.length;
58 | if(size == 1){
59 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
60 | SharedPreferences.Editor settings_editor = settings.edit();
61 | settings_editor.putString("account", accounts[0]);
62 | settings_editor.commit();
63 | } else if(size > 1) {
64 | accounts_pref.setEnabled(true);
65 | accounts_pref.setEntries(accounts);
66 | accounts_pref.setEntryValues(accounts);
67 | }
68 | }
69 |
70 | public boolean onPreferenceChange(Preference arg0, Object arg1) {
71 | // TODO Auto-generated method stub
72 | return true;
73 | }
74 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/OAuthAccount.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import android.content.SharedPreferences;
4 |
5 | public class OAuthAccount {
6 | private String account;
7 | private String host;
8 | private String token;
9 | private String key;
10 |
11 | public void setAccount(String newAccount) {
12 | this.account = newAccount;
13 | }
14 |
15 | public String getAccount() {
16 | return this.account;
17 | }
18 |
19 | public void setHost(String newHost) {
20 | this.host = newHost;
21 | }
22 |
23 | public String getHost() {
24 | return this.host;
25 | }
26 |
27 | public void setToken(String newToken) {
28 | this.token = newToken;
29 | }
30 |
31 | public String getToken() {
32 | return this.token;
33 | }
34 |
35 | public void setKey(String newKey) {
36 | this.key = newKey;
37 | }
38 |
39 | public String getKey() {
40 | return this.key;
41 | }
42 |
43 | public OAuthAccount(String account, SharedPreferences preferences) {
44 | this.setAccount(account);
45 | this.load(preferences);
46 | }
47 | public OAuthAccount(String account, String host, String token, String key) {
48 | this.setAccount(account);
49 | this.setHost(host);
50 | this.setToken(token);
51 | this.setKey(key);
52 | }
53 |
54 | public OAuthAccount save(SharedPreferences preferences) {
55 | SharedPreferences.Editor editor = preferences.edit();
56 | editor.putString("host_"+this.account, this.host);
57 | editor.putString("oauth_token_"+this.account, this.token);
58 | editor.putString("oauth_secret_"+this.account, this.key);
59 | String accounts = preferences.getString("accounts", "|");
60 | if(accounts.indexOf("|" + this.account + "|") == -1) {
61 | accounts += this.account + "|";
62 | }
63 | editor.putString("accounts", accounts);
64 | editor.commit();
65 | return this;
66 | }
67 |
68 | public boolean delete(SharedPreferences preferences) {
69 | SharedPreferences.Editor editor = preferences.edit();
70 | editor.remove("host_"+this.account);
71 | editor.remove("oauth_token_"+this.account);
72 | editor.remove("oauth_secret_"+this.account);
73 | String accounts = preferences.getString("accounts", "|"+this.account+"|");
74 | accounts = accounts.replace("|"+this.account+"|", "|");
75 | editor.putString("accounts", accounts);
76 | return editor.commit();
77 | }
78 |
79 | public void load(SharedPreferences preferences) {
80 | this.setHost(preferences.getString("host_"+this.account, "error"));
81 | this.setToken(preferences.getString("oauth_token_"+this.account, "error"));
82 | this.setKey(preferences.getString("oauth_secret_"+this.account, "error"));
83 | }
84 |
85 | public static String[] getAccounts(SharedPreferences preferences) {
86 | String accountString = preferences.getString("accounts", "|");
87 | int chopStart = 0;
88 | int chopEnd = accountString.length();
89 | if(accountString.charAt(accountString.length() - 1) == '|') {
90 | chopEnd--;
91 | }
92 | if(accountString.charAt(0) == '|') {
93 | chopStart++;
94 | }
95 | if(chopEnd <= chopStart) {
96 | accountString = "";
97 | } else {
98 | accountString = accountString.substring(chopStart, chopEnd);
99 | }
100 | return accountString.split("\\|");
101 | }
102 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/HttpClient.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.util;
2 |
3 | import java.io.IOException;
4 |
5 | import oauth.signpost.OAuthConsumer;
6 | import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
7 | import oauth.signpost.exception.OAuthCommunicationException;
8 | import oauth.signpost.exception.OAuthExpectationFailedException;
9 | import oauth.signpost.exception.OAuthMessageSignerException;
10 |
11 | import org.apache.http.client.ClientProtocolException;
12 | import org.apache.http.client.methods.HttpRequestBase;
13 | import org.apache.http.impl.client.BasicResponseHandler;
14 | import org.apache.http.impl.client.DefaultHttpClient;
15 |
16 |
17 | public class HttpClient extends DefaultHttpClient {
18 |
19 | public static final int STATUS_COMPLETE = 1;
20 | public static final int STATUS_RUNNING = 0;
21 | public static final int STATUS_ERROR = -1;
22 |
23 | protected static final String CONSUMER_KEY = "INSERT CONSUMER KEY";
24 | protected static final String CONSUMER_SECRET = "INSERT CONSUMER SECRET";
25 |
26 | private String oauth_token;
27 | private String oauth_secret;
28 |
29 | private OAuthConsumer consumer;
30 |
31 | public void setOAuthToken(String newToken) {
32 | this.oauth_token = newToken;
33 | }
34 |
35 | public String getOAuthToken() {
36 | return this.oauth_token;
37 | }
38 |
39 | public void setOAuthSecret(String newSecret) {
40 | this.oauth_secret = newSecret;
41 | }
42 |
43 | public String getOAuthSecret() {
44 | return this.oauth_secret;
45 | }
46 |
47 | public void setConsumer(OAuthConsumer newConsumer) {
48 | this.consumer = newConsumer;
49 | }
50 |
51 | public OAuthConsumer getConsumer() {
52 | return this.consumer;
53 | }
54 |
55 | public HttpClient(String oauth_token, String oauth_secret) {
56 | super();
57 | if(!"error".equals(oauth_token)) {
58 | this.setOAuthToken(oauth_token);
59 | } else {
60 | //TODO: find some way of erroring
61 | }
62 | if(!"error".equals(oauth_secret)) {
63 | this.setOAuthSecret(oauth_secret);
64 | } else {
65 | //TODO: find some way of erroring
66 | }
67 | this.setConsumer(initConsumer());
68 | }
69 |
70 | public OAuthConsumer initConsumer() {
71 | OAuthConsumer consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
72 | consumer.setTokenWithSecret(this.getOAuthToken(), this.getOAuthSecret());
73 | return consumer;
74 | }
75 |
76 | public void sign(HttpRequestBase request) {
77 | OAuthConsumer consumer = this.getConsumer();
78 | try {
79 | consumer.sign(request);
80 | } catch(OAuthMessageSignerException e) {
81 | //TODO: Handle error
82 | } catch(OAuthExpectationFailedException e) {
83 | //TODO: Handle error
84 | } catch(OAuthCommunicationException e) {
85 | //TODO: Handle error
86 | }
87 | }
88 |
89 | public String exec(HttpRequestBase request) throws IllegalStateException {
90 | this.sign(request);
91 | String returnString = "";
92 | try {
93 | String response = super.execute(request, new BasicResponseHandler());
94 | returnString = response;
95 | } catch(ClientProtocolException e) {
96 | //TODO: Handle error
97 | } catch(IOException e) {
98 | //TODO: Handle error
99 | }
100 | return returnString;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/res/layout/add_account.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
12 |
20 |
29 |
35 |
42 |
48 |
56 |
65 |
72 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/errors/OAuthCommunicationExceptionDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud.errors;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.DialogInterface;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.support.v4.app.DialogFragment;
9 |
10 | import com.suchagit.android2cloud.R;
11 | import com.suchagit.android2cloud.util.ErrorMethods;
12 |
13 | public class OAuthCommunicationExceptionDialogFragment extends DialogFragment {
14 |
15 | public static OAuthCommunicationExceptionDialogFragment newInstance(Bundle args) {
16 | OAuthCommunicationExceptionDialogFragment frag = new OAuthCommunicationExceptionDialogFragment();
17 | frag.setArguments(args);
18 | return frag;
19 | }
20 |
21 | @Override
22 | public Dialog onCreateDialog(Bundle savedInstanceState) {
23 | int title = R.string.default_error_title;
24 | int body = R.string.oauth_communication_exception_error;
25 | int yesButton = R.string.default_error_ok;
26 | int noButton = R.string.report_error_button;
27 | int neutButton = R.string.oauth_communication_exception_error_networking_button;
28 |
29 | final String stacktrace = getArguments().getString("stacktrace");
30 | final String host = getArguments().getString("host");
31 | final String account = getArguments().getString("account");
32 | final String verifier = getArguments().getString("verifier");
33 | final String requestUrl = getArguments().getString("request_url");
34 | final String responseBody = getArguments().getString("response_body");
35 |
36 | return new AlertDialog.Builder(getActivity())
37 | .setCancelable(false)
38 | .setTitle(title)
39 | .setMessage(body)
40 | .setPositiveButton(yesButton,
41 | new DialogInterface.OnClickListener() {
42 | public void onClick(DialogInterface dialog, int whichButton) {
43 | dialog.cancel();
44 | getActivity().finish();
45 | }
46 | }
47 | )
48 | .setNegativeButton(noButton,
49 | new DialogInterface.OnClickListener() {
50 | public void onClick(DialogInterface dialog, int whichButton) {
51 | String message = "Account: " + account +"\n";
52 | message += "Host: " + host + "\n";
53 | if(requestUrl != null)
54 | message += "Request URL: " + requestUrl + "\n";
55 | else if(verifier != null)
56 | message += "Verifier: " + verifier + "\n";
57 | message += "Response Body: \n";
58 | message += responseBody + "\n";
59 | message += "Stacktrace: \n";
60 | message += stacktrace + "\n";
61 | Intent report = ErrorMethods.getEmailIntent(OAuthCommunicationExceptionDialogFragment.this, "oauth_communication_exception_error", message);
62 | getActivity().startActivity(report);
63 | }
64 | }
65 | )
66 | .setNeutralButton(neutButton, new DialogInterface.OnClickListener() {
67 | public void onClick(DialogInterface dialog, int id) {
68 | Intent i = new Intent(Intent.ACTION_MAIN);
69 | i.setClassName("com.android.phone", "com.android.phone.NetworkSetting");
70 | getActivity().startActivity(i);
71 | }
72 | })
73 | .create();
74 | }
75 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/HttpService.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud;
2 |
3 | import java.io.PrintWriter;
4 | import java.io.StringWriter;
5 | import java.io.UnsupportedEncodingException;
6 |
7 | import org.apache.http.client.methods.HttpRequestBase;
8 |
9 | import android.app.IntentService;
10 | import android.content.Intent;
11 | import android.os.Bundle;
12 | import android.os.ResultReceiver;
13 | import com.suchagit.android2cloud.util.AddLinkRequest;
14 | import com.suchagit.android2cloud.util.CheckTimeRequest;
15 | import com.suchagit.android2cloud.util.HttpClient;
16 | import com.suchagit.android2cloud.util.PaymentNotificationRequest;
17 |
18 |
19 | public class HttpService extends IntentService {
20 |
21 | public HttpService() {
22 | super("HttpService");
23 | }
24 |
25 | private HttpClient client;
26 |
27 | @Override
28 | protected void onHandleIntent(Intent intent) {
29 | if(client == null) {
30 | String oauth_token = intent.getStringExtra("com.suchagit.android2cloud.oauth_token");
31 | String oauth_secret = intent.getStringExtra("com.suchagit.android2cloud.oauth_secret");
32 | client = new HttpClient(oauth_token, oauth_secret);
33 | }
34 | String requestType = intent.getAction();
35 | String host = intent.getStringExtra("com.suchagit.android2cloud.host");
36 | final ResultReceiver result = intent.getParcelableExtra("com.suchagit.android2cloud.result_receiver");
37 | HttpRequestBase request = null;
38 | Bundle b = new Bundle();
39 | try {
40 | if(requestType.equals("com.suchagit.android2cloud.AddLink")) {
41 | String link = intent.getStringExtra("com.suchagit.android2cloud.link");
42 | String receiver = intent.getStringExtra("com.suchagit.android2cloud.receiver");
43 | String sender = intent.getStringExtra("com.suchagit.android2cloud.sender");
44 | request = new AddLinkRequest(host, receiver, sender, link);
45 | } else if (requestType.equals("com.suchagit.android2cloud.PaymentNotification")) {
46 | String itemId = intent.getStringExtra("com.suchagit.android2cloud.item_id");
47 | String orderNumber = intent.getStringExtra("com.suchagit.android2cloud.order_number");
48 | request = new PaymentNotificationRequest(host, orderNumber, itemId);
49 | } else if (requestType.equals("com.suchagit.android2cloud.CheckTime")) {
50 | request = new CheckTimeRequest(host);
51 | }
52 | String response = client.exec(request);
53 | b.putString("raw_result", response);
54 | result.send(HttpClient.STATUS_COMPLETE, b);
55 | } catch (UnsupportedEncodingException e) {
56 | b.putInt("response_code", 600);
57 | b.putString("type", "unsupported_encoding_exception_error");
58 | result.send(HttpClient.STATUS_ERROR, b);
59 | } catch (IllegalStateException e) {
60 | b.putInt("response_code", 600);
61 | b.putString("type", "illegal_state_exception_error");
62 | b.putString("request_type", requestType);
63 | if(request != null)
64 | b.putString("host", request.getURI().getHost());
65 | StringWriter sw = new StringWriter();
66 | PrintWriter pw = new PrintWriter(sw);
67 | e.printStackTrace(pw);
68 | b.putString("stacktrace", sw.toString());
69 | result.send(HttpClient.STATUS_ERROR, b);
70 | } catch (IllegalArgumentException e) {
71 | b.putInt("response_code", 600);
72 | b.putString("type", "illegal_argument_exception_error");
73 | b.putString("request_type", requestType);
74 | if(request != null)
75 | b.putString("host", request.getURI().getHost());
76 | StringWriter sw = new StringWriter();
77 | PrintWriter pw = new PrintWriter(sw);
78 | e.printStackTrace(pw);
79 | b.putString("stacktrace", sw.toString());
80 | result.send(HttpClient.STATUS_ERROR, b);
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 2cloud
3 | Preferences
4 |
5 | - Account
6 | - Silent Sending
7 |
8 |
9 | - add_account
10 | - silent_sending
11 |
12 |
13 | - Change the account that you\'re using to send links.
14 | - Send links straight from your apps, without having to click Send
15 | in 2cloud.
16 |
17 |
18 | Can\'t make purchases
19 | The Market billing
20 | service is not available at this time. You can continue to use this app but
21 | you
22 | won\'t be able to make purchases.
23 | Can\'t connect to Market
24 | This app cannot connect to Market.
25 | Your version of Market may be out of date.
26 | You can continue to use this app but you
27 | won\'t be able to make purchases.
28 | An unexpected error has occurred.
29 | OK
30 | Report Error
31 | There was a problem retrieving the authentication results. Please try again.
32 | You need to enter a link to send.
33 | You need to specify a device to send the link to. If in doubt, try "Chrome".
34 | There are no accounts set up. Please set one up before sending links.
35 | You have accounts set up, but none is selected. Please select one before sending links.
36 | You\'re trying to send something that has no link.
37 | There was an error communicating with the server. Please try again.
38 | There was an error authenticating you. Please check your account and try again.
39 | The server is over quota. Your link has been stored, and will be opened tomorrow. If you wish, you may pay to get around the quota for the rest of the day.
40 | There was a problem signing your request. Please try again.
41 | There was a problem authenticating you. Please try again.
42 | There was a problem signing your request. Please try again.
43 | There was a problem communicating with the server. Please check your connection and try again.
44 | There was a problem with your request. Please report this to the development team.
45 | Error!
46 | Over Quota
47 | There was an error authenticating your account. Please try again.
48 | Go To Account Settings
49 | Set One Up Now
50 | Choose an Account
51 | Pay $1
52 | Open Networking Settings
53 | Select a link to send:
54 | There was a problem contacting the host. We apologise for the issue, and are working on finding a fix. Please report this issue using the button below to assist us in fixing this bug.
55 | The host you are using is no longer available. The account using it has been removed. Please add a new account.
56 | It appears as though your device\'s time is set incorrectly. Please check your timezone, date, and time.
57 | Open Date & Time Settings
58 | There was a problem contacting the host. We apologise for the issue, and are working on finding a fix. Please report this issue using the button below to assist us in fixing this bug.
59 | There was an error with your account. Please add it again to send links.
60 | Add a New Account
61 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/Consts.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.suchagit.android2cloud;
18 |
19 | /**
20 | * This class holds global constants that are used throughout the application
21 | * to support in-app billing.
22 | */
23 | public class Consts {
24 | // The response codes for a request, defined by Android Market.
25 | public enum ResponseCode {
26 | RESULT_OK,
27 | RESULT_USER_CANCELED,
28 | RESULT_SERVICE_UNAVAILABLE,
29 | RESULT_BILLING_UNAVAILABLE,
30 | RESULT_ITEM_UNAVAILABLE,
31 | RESULT_DEVELOPER_ERROR,
32 | RESULT_ERROR;
33 |
34 | // Converts from an ordinal value to the ResponseCode
35 | public static ResponseCode valueOf(int index) {
36 | ResponseCode[] values = ResponseCode.values();
37 | if (index < 0 || index >= values.length) {
38 | return RESULT_ERROR;
39 | }
40 | return values[index];
41 | }
42 | }
43 |
44 | // The possible states of an in-app purchase, as defined by Android Market.
45 | public enum PurchaseState {
46 | // Responses to requestPurchase or restoreTransactions.
47 | PURCHASED, // User was charged for the order.
48 | CANCELED, // The charge failed on the server.
49 | REFUNDED; // User received a refund for the order.
50 |
51 | // Converts from an ordinal value to the PurchaseState
52 | public static PurchaseState valueOf(int index) {
53 | PurchaseState[] values = PurchaseState.values();
54 | if (index < 0 || index >= values.length) {
55 | return CANCELED;
56 | }
57 | return values[index];
58 | }
59 | }
60 |
61 | /** This is the action we use to bind to the MarketBillingService. */
62 | public static final String MARKET_BILLING_SERVICE_ACTION =
63 | "com.android.vending.billing.MarketBillingService.BIND";
64 |
65 | // Intent actions that we send from the BillingReceiver to the
66 | // BillingService. Defined by this application.
67 | public static final String ACTION_CONFIRM_NOTIFICATION =
68 | "com.suchagit.android2cloud.CONFIRM_NOTIFICATION";
69 | public static final String ACTION_GET_PURCHASE_INFORMATION =
70 | "com.suchagit.android2cloud.GET_PURCHASE_INFORMATION";
71 | public static final String ACTION_RESTORE_TRANSACTIONS =
72 | "com.suchagit.android2cloud.RESTORE_TRANSACTIONS";
73 |
74 | // Intent actions that we receive in the BillingReceiver from Market.
75 | // These are defined by Market and cannot be changed.
76 | public static final String ACTION_NOTIFY = "com.android.vending.billing.IN_APP_NOTIFY";
77 | public static final String ACTION_RESPONSE_CODE =
78 | "com.android.vending.billing.RESPONSE_CODE";
79 | public static final String ACTION_PURCHASE_STATE_CHANGED =
80 | "com.android.vending.billing.PURCHASE_STATE_CHANGED";
81 |
82 | // These are the names of the extras that are passed in an intent from
83 | // Market to this application and cannot be changed.
84 | public static final String NOTIFICATION_ID = "notification_id";
85 | public static final String INAPP_SIGNED_DATA = "inapp_signed_data";
86 | public static final String INAPP_SIGNATURE = "inapp_signature";
87 | public static final String INAPP_REQUEST_ID = "request_id";
88 | public static final String INAPP_RESPONSE_CODE = "response_code";
89 |
90 | // These are the names of the fields in the request bundle.
91 | public static final String BILLING_REQUEST_METHOD = "BILLING_REQUEST";
92 | public static final String BILLING_REQUEST_API_VERSION = "API_VERSION";
93 | public static final String BILLING_REQUEST_PACKAGE_NAME = "PACKAGE_NAME";
94 | public static final String BILLING_REQUEST_ITEM_ID = "ITEM_ID";
95 | public static final String BILLING_REQUEST_DEVELOPER_PAYLOAD = "DEVELOPER_PAYLOAD";
96 | public static final String BILLING_REQUEST_NOTIFY_IDS = "NOTIFY_IDS";
97 | public static final String BILLING_REQUEST_NONCE = "NONCE";
98 |
99 | public static final String BILLING_RESPONSE_RESPONSE_CODE = "RESPONSE_CODE";
100 | public static final String BILLING_RESPONSE_PURCHASE_INTENT = "PURCHASE_INTENT";
101 | public static final String BILLING_RESPONSE_REQUEST_ID = "REQUEST_ID";
102 | public static long BILLING_RESPONSE_INVALID_REQUEST_ID = -1;
103 |
104 | public static final boolean DEBUG = false;
105 | }
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 2cloud Android Client
2 |
3 | ### About
4 |
5 | 2cloud is a free, decentralised, open source project to try and make sharing
6 | content between browsers and devices as seamless and effortless as possible. An
7 | up-to-date list of devices and browsers supported by the project is available at
8 | http://www.2cloudproject.com/clients
9 |
10 | This is the Android client that was launched originally in July of 2010. It has
11 | been
12 | [deprecated](http://blog.android2cloud.org/2011/07/you-cant-teach-old-dog-new-tricks.html)
13 | in an effort to free our team to focus on the future, instead of the past. This
14 | repository will remain here for archival purposes, as well as to make the code
15 | available to any of the users whose phones can't run the [newer version](https://www.github.com/2cloud/Android).
16 |
17 | ### Installation Instructions
18 |
19 | We tried to make installation and modification as easy as possible. Simply download
20 | the code (either using git or the download button on Github) and build it like any
21 | other Android application. You'll need the [SDK](http://developer.android.com/sdk/index.html),
22 | but Google has some [helpful resources](http://developer.android.com/resources/faq/commontasks.html#neweclipseandroidproject)
23 | for people who need help getting started.
24 |
25 | ### Where to Get Help
26 |
27 | We try to maintain a presence with our users. To wit, we have:
28 |
29 | * A Tender support site (the best way to get help with "it's not working"): http://help.2cloudproject.com
30 | * An announcement mailing list (the best way to stay up-to-date on downtime and changes): http://groups.google.com/group/2cloud-announce
31 | * A discussion mailing list (the best way to talk to other users and the team): http://groups.google.com/group/2cloud
32 | * A development mailing list (the best way to stay on top of API changes): http://groups.google.com/groups/2cloud-dev
33 | * A beta mailing list (if you want to help test buggy software): http://groups.google.com/group/2cloud-beta
34 | * A Twitter account (the best way to stay on top of new releases and other updates): http://www.twitter.com/2cloudproject
35 | * A Facebook page (the second best way to stay on top of new releases and other updates): http://www.facebook.com/2cloud
36 | * A website (for a bunch of other links and information): http://www.2cloudproject.com
37 | * A blog (for lengthier updates and explanations): http://blog.2cloudproject.com
38 | * A Github account (where all our source code and issues reside): https://www.github.com/2cloud
39 |
40 | If you don't use _any_ of those... you're kind of out of luck.
41 |
42 | ### Contribution Guidelines
43 |
44 | The quickest, easiest, and most assured way to contribute is to be a beta tester.
45 | Simply join the [mailing list](http://groups.google.com/group/2cloud-beta) and
46 | wait for a new beta to be released. Try and break it. Submit feedback. Wash,
47 | rinse, repeat.
48 |
49 | If you're interested in contributing code, we use different guidelines for each
50 | part of our app. This is driven by necessity; you can't use JUnit on Javascript, for
51 | example. Unfortunately, we're still in the process of defining our guidelines
52 | for Android. This project has been retired, but we'll still accept pull requests
53 | for it provided you explain what you changed, why you changed it, and why the app
54 | is better that way. In our future version, expect to see unit tests with JUnit and
55 | Checkstyle implemented and required for pull requests.
56 |
57 | The best way to figure out what's on our to-do list is to look at the
58 | [issue tracker](https://www.github.com/2cloud/android2cloud/issues) or ask on the
59 | [dev mailing list](http://groups.google.com/group/2cloud-dev). Whatever you work
60 | on should be something _you_ want to see implemented, though.
61 |
62 | ### Contributors
63 |
64 | 2cloud is an open source application. It is technically "owned" by [Second Bit LLC](http://www.secondbit.org),
65 | but all that really means is they take care of the mundane administrative and
66 | financial stuff. The team behind 2cloud is separate from the Second Bit team
67 | (despite some overlap). The 2cloud team is as follows:
68 |
69 | * Paddy Foran - Lead Developer - [@paddyforan](http://www.twitter.com/paddyforan) - http://www.paddyforan.com/
70 | * Dylan Staley - UI/UX Lead - [@dstaley](http://www.twitter.com/dstaley) - http://www.dstaley.me
71 | * Tino Galizio - Project Manager - [@tinogalizio](http://www.twitter.com/tinogalizio) - http://www.secondbit.org/team/tino
72 |
73 | They're pretty friendly. Please do get in touch!
74 |
75 | ### Credits and Alternatives
76 |
77 | One of the great parts about being an open source project is how often we get to
78 | stand on the shoulders of giants. Without these people and projects, we couldn't
79 | do what we do.
80 |
81 | * blog.notdot.net
82 | * Signpost, a (sadly discontinued) OAuth library for Java that suits our needs wonderfully
83 | * Chrome to Phone (Inspiration)
84 | * The sample In-App Payments app was largely cannibalised for our In-App payments system
85 |
86 | There are some alternatives to 2cloud out there, and we encourage you to try them
87 | out. Use what works best for you. You can find an up-to-date list on
88 | [our website](http://links.2cloudproject.com/competition).
89 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/BillingReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.suchagit.android2cloud;
18 |
19 | import com.suchagit.android2cloud.Consts.ResponseCode;
20 |
21 | import android.content.BroadcastReceiver;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.util.Log;
25 |
26 | /**
27 | * This class implements the broadcast receiver for in-app billing. All asynchronous messages from
28 | * Android Market come to this app through this receiver. This class forwards all
29 | * messages to the {@link BillingService}, which can start background threads,
30 | * if necessary, to process the messages. This class runs on the UI thread and must not do any
31 | * network I/O, database updates, or any tasks that might take a long time to complete.
32 | * It also must not start a background thread because that may be killed as soon as
33 | * {@link #onReceive(Context, Intent)} returns.
34 | *
35 | * You should modify and obfuscate this code before using it.
36 | */
37 | public class BillingReceiver extends BroadcastReceiver {
38 | private static final String TAG = "BillingReceiver";
39 |
40 | /**
41 | * This is the entry point for all asynchronous messages sent from Android Market to
42 | * the application. This method forwards the messages on to the
43 | * {@link BillingService}, which handles the communication back to Android Market.
44 | * The {@link BillingService} also reports state changes back to the application through
45 | * the {@link ResponseHandler}.
46 | */
47 | @Override
48 | public void onReceive(Context context, Intent intent) {
49 | String action = intent.getAction();
50 | if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
51 | String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
52 | String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
53 | purchaseStateChanged(context, signedData, signature);
54 | } else if (Consts.ACTION_NOTIFY.equals(action)) {
55 | String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
56 | if (Consts.DEBUG) {
57 | Log.i(TAG, "notifyId: " + notifyId);
58 | }
59 | notify(context, notifyId);
60 | } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
61 | long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
62 | int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
63 | ResponseCode.RESULT_ERROR.ordinal());
64 | checkResponseCode(context, requestId, responseCodeIndex);
65 | } else {
66 | Log.w(TAG, "unexpected action: " + action);
67 | }
68 | }
69 |
70 | /**
71 | * This is called when Android Market sends information about a purchase state
72 | * change. The signedData parameter is a plaintext JSON string that is
73 | * signed by the server with the developer's private key. The signature
74 | * for the signed data is passed in the signature parameter.
75 | * @param context the context
76 | * @param signedData the (unencrypted) JSON string
77 | * @param signature the signature for the signedData
78 | */
79 | private void purchaseStateChanged(Context context, String signedData, String signature) {
80 | Intent intent = new Intent(Consts.ACTION_PURCHASE_STATE_CHANGED);
81 | intent.setClass(context, BillingService.class);
82 | intent.putExtra(Consts.INAPP_SIGNED_DATA, signedData);
83 | intent.putExtra(Consts.INAPP_SIGNATURE, signature);
84 | context.startService(intent);
85 | }
86 |
87 | /**
88 | * This is called when Android Market sends a "notify" message indicating that transaction
89 | * information is available. The request includes a nonce (random number used once) that
90 | * we generate and Android Market signs and sends back to us with the purchase state and
91 | * other transaction details. This BroadcastReceiver cannot bind to the
92 | * MarketBillingService directly so it starts the {@link BillingService}, which does the
93 | * actual work of sending the message.
94 | *
95 | * @param context the context
96 | * @param notifyId the notification ID
97 | */
98 | private void notify(Context context, String notifyId) {
99 | Intent intent = new Intent(Consts.ACTION_GET_PURCHASE_INFORMATION);
100 | intent.setClass(context, BillingService.class);
101 | intent.putExtra(Consts.NOTIFICATION_ID, notifyId);
102 | context.startService(intent);
103 | }
104 |
105 | /**
106 | * This is called when Android Market sends a server response code. The BillingService can
107 | * then report the status of the response if desired.
108 | *
109 | * @param context the context
110 | * @param requestId the request ID that corresponds to a previous request
111 | * @param responseCodeIndex the ResponseCode ordinal value for the request
112 | */
113 | private void checkResponseCode(Context context, long requestId, int responseCodeIndex) {
114 | Intent intent = new Intent(Consts.ACTION_RESPONSE_CODE);
115 | intent.setClass(context, BillingService.class);
116 | intent.putExtra(Consts.INAPP_REQUEST_ID, requestId);
117 | intent.putExtra(Consts.INAPP_RESPONSE_CODE, responseCodeIndex);
118 | context.startService(intent);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/ResponseHandler.java:
--------------------------------------------------------------------------------
1 | // Copyright 2010 Google Inc. All Rights Reserved.
2 |
3 | package com.suchagit.android2cloud;
4 |
5 | import android.app.PendingIntent;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.util.Log;
9 |
10 | import com.suchagit.android2cloud.BillingService.RequestPurchase;
11 | import com.suchagit.android2cloud.Consts.PurchaseState;
12 | import com.suchagit.android2cloud.Consts.ResponseCode;
13 | import com.suchagit.android2cloud.util.Security;
14 |
15 | /**
16 | * This class contains the methods that handle responses from Android Market. The
17 | * implementation of these methods is specific to a particular application.
18 | * The methods in this example update the database and, if the main application
19 | * has registered a {@llink PurchaseObserver}, will also update the UI. An
20 | * application might also want to forward some responses on to its own server,
21 | * and that could be done here (in a background thread) but this example does
22 | * not do that.
23 | *
24 | * You should modify and obfuscate this code before using it.
25 | */
26 | public class ResponseHandler {
27 | private static final String TAG = "ResponseHandler";
28 |
29 | /**
30 | * This is a static instance of {@link PurchaseObserver} that the
31 | * application creates and registers with this class. The PurchaseObserver
32 | * is used for updating the UI if the UI is visible.
33 | */
34 | private static PurchaseObserver sPurchaseObserver;
35 |
36 | /**
37 | * Registers an observer that updates the UI.
38 | * @param observer the observer to register
39 | */
40 | public static synchronized void register(PurchaseObserver observer) {
41 | sPurchaseObserver = observer;
42 | }
43 |
44 | /**
45 | * Unregisters a previously registered observer.
46 | * @param observer the previously registered observer.
47 | */
48 | public static synchronized void unregister(PurchaseObserver observer) {
49 | sPurchaseObserver = null;
50 | }
51 |
52 | /**
53 | * Notifies the application of the availability of the MarketBillingService.
54 | * This method is called in response to the application calling
55 | * {@link BillingService#checkBillingSupported()}.
56 | * @param supported true if in-app billing is supported.
57 | */
58 | public static void checkBillingSupportedResponse(boolean supported) {
59 | if (sPurchaseObserver != null) {
60 | sPurchaseObserver.onBillingSupported(supported);
61 | }
62 | }
63 |
64 | /**
65 | * Starts a new activity for the user to buy an item for sale. This method
66 | * forwards the intent on to the PurchaseObserver (if it exists) because
67 | * we need to start the activity on the activity stack of the application.
68 | *
69 | * @param pendingIntent a PendingIntent that we received from Android Market that
70 | * will create the new buy page activity
71 | * @param intent an intent containing a request id in an extra field that
72 | * will be passed to the buy page activity when it is created
73 | */
74 | public static void buyPageIntentResponse(PendingIntent pendingIntent, Intent intent) {
75 | if (sPurchaseObserver == null) {
76 | if (Consts.DEBUG) {
77 | Log.d(TAG, "UI is not running");
78 | }
79 | return;
80 | }
81 | sPurchaseObserver.startBuyPageActivity(pendingIntent, intent);
82 | }
83 |
84 | /**
85 | * Notifies the application of purchase state changes. The application
86 | * can offer an item for sale to the user via
87 | * {@link BillingService#requestPurchase(String)}. The BillingService
88 | * calls this method after it gets the response. Another way this method
89 | * can be called is if the user bought something on another device running
90 | * this same app. Then Android Market notifies the other devices that
91 | * the user has purchased an item, in which case the BillingService will
92 | * also call this method. Finally, this method can be called if the item
93 | * was refunded.
94 | * @param purchaseState the state of the purchase request (PURCHASED,
95 | * CANCELED, or REFUNDED)
96 | * @param productId a string identifying a product for sale
97 | * @param orderId a string identifying the order
98 | * @param purchaseTime the time the product was purchased, in milliseconds
99 | * since the epoch (Jan 1, 1970)
100 | * @param developerPayload the developer provided "payload" associated with
101 | * the order
102 | */
103 | public static void purchaseResponse(
104 | final Context context, final PurchaseState purchaseState, final String productId,
105 | final String orderId, final long purchaseTime, final String developerPayload) {
106 |
107 | // Update the database with the purchase state. We shouldn't do that
108 | // from the main thread so we do the work in a background thread.
109 | // We don't update the UI here. We will update the UI after we update
110 | // the database because we need to read and update the current quantity
111 | // first.
112 | new Thread(new Runnable() {
113 | public void run() {
114 | // This needs to be synchronized because the UI thread can change the
115 | // value of sPurchaseObserver.
116 | synchronized(ResponseHandler.class) {
117 | if (sPurchaseObserver != null) {
118 | sPurchaseObserver.postPurchaseStateChange(
119 | purchaseState, productId, 1, purchaseTime, developerPayload, orderId);
120 | }
121 | }
122 | }
123 | }).start();
124 | }
125 |
126 | /**
127 | * This is called when we receive a response code from Android Market for a
128 | * RequestPurchase request that we made. This is used for reporting various
129 | * errors and also for acknowledging that an order was sent successfully to
130 | * the server. This is NOT used for any purchase state changes. All
131 | * purchase state changes are received in the {@link BillingReceiver} and
132 | * are handled in {@link Security#verifyPurchase(String, String)}.
133 | * @param context the context
134 | * @param request the RequestPurchase request for which we received a
135 | * response code
136 | * @param responseCode a response code from Market to indicate the state
137 | * of the request
138 | */
139 | public static void responseCodeReceived(Context context, RequestPurchase request,
140 | ResponseCode responseCode) {
141 | if (sPurchaseObserver != null) {
142 | sPurchaseObserver.onRequestPurchaseResponse(request, responseCode);
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/PurchaseObserver.java:
--------------------------------------------------------------------------------
1 | // Copyright 2010 Google Inc. All Rights Reserved.
2 |
3 | package com.suchagit.android2cloud;
4 |
5 | import com.suchagit.android2cloud.BillingService.RequestPurchase;
6 | import com.suchagit.android2cloud.Consts.PurchaseState;
7 | import com.suchagit.android2cloud.Consts.ResponseCode;
8 |
9 | import android.app.Activity;
10 | import android.app.PendingIntent;
11 | import android.app.PendingIntent.CanceledException;
12 | import android.content.Intent;
13 | import android.content.IntentSender;
14 | import android.os.Handler;
15 | import android.util.Log;
16 |
17 | import java.lang.reflect.Method;
18 |
19 | /**
20 | * An interface for observing changes related to purchases. The main application
21 | * extends this class and registers an instance of that derived class with
22 | * {@link ResponseHandler}. The main application implements the callbacks
23 | * {@link #onBillingSupported(boolean)} and
24 | * {@link #onPurchaseStateChange(PurchaseState, String, int, long)}. These methods
25 | * are used to update the UI.
26 | */
27 | public abstract class PurchaseObserver {
28 | private static final String TAG = "PurchaseObserver";
29 | private final Activity mActivity;
30 | private final Handler mHandler;
31 | private Method mStartIntentSender;
32 | private Object[] mStartIntentSenderArgs = new Object[5];
33 | @SuppressWarnings({ "rawtypes" })
34 | private static final Class[] START_INTENT_SENDER_SIG = new Class[] {
35 | IntentSender.class, Intent.class, int.class, int.class, int.class
36 | };
37 |
38 | public PurchaseObserver(Activity activity, Handler handler) {
39 | mActivity = activity;
40 | mHandler = handler;
41 | initCompatibilityLayer();
42 | }
43 |
44 | /**
45 | * This is the callback that is invoked when Android Market responds to the
46 | * {@link BillingService#checkBillingSupported()} request.
47 | * @param supported true if in-app billing is supported.
48 | */
49 | public abstract void onBillingSupported(boolean supported);
50 |
51 | /**
52 | * This is the callback that is invoked when an item is purchased,
53 | * refunded, or canceled. It is the callback invoked in response to
54 | * calling {@link BillingService#requestPurchase(String)}. It may also
55 | * be invoked asynchronously when a purchase is made on another device
56 | * (if the purchase was for a Market-managed item), or if the purchase
57 | * was refunded, or the charge was canceled. This handles the UI
58 | * update. The database update is handled in
59 | * {@link ResponseHandler#purchaseResponse(Context, PurchaseState,
60 | * String, String, long)}.
61 | * @param purchaseState the purchase state of the item
62 | * @param itemId a string identifying the item (the "SKU")
63 | * @param quantity the current quantity of this item after the purchase
64 | * @param purchaseTime the time the product was purchased, in
65 | * milliseconds since the epoch (Jan 1, 1970)
66 | * @param orderId the Google Checkout order ID number
67 | */
68 | public abstract void onPurchaseStateChange(PurchaseState purchaseState,
69 | String itemId, int quantity, long purchaseTime, String developerPayload, String orderId);
70 |
71 | /**
72 | * This is called when we receive a response code from Market for a
73 | * RequestPurchase request that we made. This is NOT used for any
74 | * purchase state changes. All purchase state changes are received in
75 | * {@link #onPurchaseStateChange(PurchaseState, String, int, long)}.
76 | * This is used for reporting various errors, or if the user backed out
77 | * and didn't purchase the item. The possible response codes are:
78 | * RESULT_OK means that the order was sent successfully to the server.
79 | * The onPurchaseStateChange() will be invoked later (with a
80 | * purchase state of PURCHASED or CANCELED) when the order is
81 | * charged or canceled. This response code can also happen if an
82 | * order for a Market-managed item was already sent to the server.
83 | * RESULT_USER_CANCELED means that the user didn't buy the item.
84 | * RESULT_SERVICE_UNAVAILABLE means that we couldn't connect to the
85 | * Android Market server (for example if the data connection is down).
86 | * RESULT_BILLING_UNAVAILABLE means that in-app billing is not
87 | * supported yet.
88 | * RESULT_ITEM_UNAVAILABLE means that the item this app offered for
89 | * sale does not exist (or is not published) in the server-side
90 | * catalog.
91 | * RESULT_ERROR is used for any other errors (such as a server error).
92 | */
93 | public abstract void onRequestPurchaseResponse(RequestPurchase request,
94 | ResponseCode responseCode);
95 |
96 | private void initCompatibilityLayer() {
97 | try {
98 | mStartIntentSender = mActivity.getClass().getMethod("startIntentSender",
99 | START_INTENT_SENDER_SIG);
100 | } catch (SecurityException e) {
101 | mStartIntentSender = null;
102 | } catch (NoSuchMethodException e) {
103 | mStartIntentSender = null;
104 | }
105 | }
106 |
107 | void startBuyPageActivity(PendingIntent pendingIntent, Intent intent) {
108 | if (mStartIntentSender != null) {
109 | // This is on Android 2.0 and beyond. The in-app buy page activity
110 | // must be on the activity stack of the application.
111 | try {
112 | // This implements the method call:
113 | // mActivity.startIntentSender(pendingIntent.getIntentSender(),
114 | // intent, 0, 0, 0);
115 | mStartIntentSenderArgs[0] = pendingIntent.getIntentSender();
116 | mStartIntentSenderArgs[1] = intent;
117 | mStartIntentSenderArgs[2] = Integer.valueOf(0);
118 | mStartIntentSenderArgs[3] = Integer.valueOf(0);
119 | mStartIntentSenderArgs[4] = Integer.valueOf(0);
120 | mStartIntentSender.invoke(mActivity, mStartIntentSenderArgs);
121 | } catch (Exception e) {
122 | Log.e(TAG, "error starting activity", e);
123 | }
124 | } else {
125 | // This is on Android version 1.6. The in-app buy page activity must be on its
126 | // own separate activity stack instead of on the activity stack of
127 | // the application.
128 | try {
129 | pendingIntent.send(mActivity, 0 /* code */, intent);
130 | } catch (CanceledException e) {
131 | Log.e(TAG, "error starting activity", e);
132 | }
133 | }
134 | }
135 |
136 | /**
137 | * Updates the UI after the database has been updated. This method runs
138 | * in a background thread so it has to post a Runnable to run on the UI
139 | * thread.
140 | * @param purchaseState the purchase state of the item
141 | * @param itemId a string identifying the item
142 | * @param quantity the quantity of items in this purchase
143 | */
144 | void postPurchaseStateChange(final PurchaseState purchaseState, final String itemId,
145 | final int quantity, final long purchaseTime, final String developerPayload, final String orderId) {
146 | mHandler.post(new Runnable() {
147 | public void run() {
148 | onPurchaseStateChange(
149 | purchaseState, itemId, quantity, purchaseTime, developerPayload, orderId);
150 | }
151 | });
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/Billing.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.suchagit.android2cloud;
18 |
19 | import android.app.Dialog;
20 | import android.content.ComponentName;
21 | import android.content.Intent;
22 | import android.content.SharedPreferences;
23 | import android.os.Bundle;
24 | import android.os.Handler;
25 | import android.preference.PreferenceManager;
26 | import android.support.v4.app.DialogFragment;
27 | import android.support.v4.app.FragmentActivity;
28 | import android.util.Log;
29 | import android.view.View;
30 | import android.widget.ProgressBar;
31 | import android.widget.TextView;
32 |
33 | import com.suchagit.android2cloud.BillingService.RequestPurchase;
34 | import com.suchagit.android2cloud.Consts.PurchaseState;
35 | import com.suchagit.android2cloud.Consts.ResponseCode;
36 | import com.suchagit.android2cloud.errors.BillingCannotConnectDialogFragment;
37 | import com.suchagit.android2cloud.errors.BillingNotSupportedDialogFragment;
38 | import com.suchagit.android2cloud.util.OAuthAccount;
39 | import com.suchagit.android2cloud.util.PaymentNotificationResponse;
40 |
41 | /**
42 | * A sample application that demonstrates in-app billing.
43 | */
44 | public class Billing extends FragmentActivity implements PaymentNotificationResponse.Receiver {
45 | private static final String TAG = "Billing";
46 |
47 | /**
48 | * The SharedPreferences key for recording whether we initialized the
49 | * database. If false, then we perform a RestoreTransactions request
50 | * to get all the purchases for this user.
51 | */
52 |
53 | private BillingPurchaseObserver mBillingPurchaseObserver;
54 | private Handler mHandler;
55 |
56 | private BillingService mBillingService;
57 | private TextView statusText;
58 | private ProgressBar throbber;
59 |
60 | /**
61 | * The developer payload that is sent with subsequent
62 | * purchase requests.
63 | */
64 | private String mPayloadContents = null;
65 |
66 | private static final int DIALOG_CANNOT_CONNECT_ID = 1;
67 | private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
68 |
69 |
70 | public PaymentNotificationResponse mReceiver;
71 |
72 | /**
73 | * A {@link PurchaseObserver} is used to get callbacks when Android Market sends
74 | * messages to this application so that we can update the UI.
75 | */
76 | private class BillingPurchaseObserver extends PurchaseObserver {
77 | public BillingPurchaseObserver(Handler handler) {
78 | super(Billing.this, handler);
79 | }
80 |
81 | @Override
82 | public void onBillingSupported(boolean supported) {
83 | if (Consts.DEBUG) {
84 | Log.i(TAG, "supported: " + supported);
85 | }
86 | if (!supported) {
87 | showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
88 | }
89 | }
90 |
91 | @Override
92 | public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
93 | int quantity, long purchaseTime, String developerPayload, String orderId) {
94 | if (Consts.DEBUG) {
95 | Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
96 | }
97 |
98 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
99 | SharedPreferences accounts_preferences = getSharedPreferences("android2cloud-accounts", 0);
100 |
101 | String account_name = settings.getString("account", "");
102 | OAuthAccount account = new OAuthAccount(account_name, accounts_preferences);
103 |
104 | Log.d("Billing", purchaseState.toString());
105 | if(purchaseState.equals(Consts.PurchaseState.PURCHASED)) {
106 | statusText.setText("Contacting server...");
107 | Intent intent = new Intent();
108 | intent.setComponent(new ComponentName("com.suchagit.android2cloud", "com.suchagit.android2cloud.HttpService"));
109 | intent.setAction("com.suchagit.android2cloud.PaymentNotification");
110 | intent.putExtra("com.suchagit.android2cloud.result_receiver", mReceiver);
111 | intent.putExtra("com.suchagit.android2cloud.host", account.getHost());
112 | intent.putExtra("com.suchagit.android2cloud.oauth_token", account.getToken());
113 | intent.putExtra("com.suchagit.android2cloud.oauth_secret", account.getKey());
114 | intent.putExtra("com.suchagit.android2cloud.item_id", itemId);
115 | intent.putExtra("com.suchagit.android2cloud.order_number", orderId);
116 | startService(intent);
117 | }
118 | }
119 |
120 | @Override
121 | public void onRequestPurchaseResponse(RequestPurchase request,
122 | ResponseCode responseCode) {
123 | if (Consts.DEBUG) {
124 | Log.d(TAG, request.mProductId + ": " + responseCode);
125 | }
126 | if (responseCode == ResponseCode.RESULT_OK) {
127 | if (Consts.DEBUG) {
128 | Log.i(TAG, "purchase was successfully sent to server");
129 | }
130 | } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
131 | if (Consts.DEBUG) {
132 | Log.i(TAG, "user canceled purchase");
133 | statusText.setText("Payment cancelled.");
134 | throbber.setVisibility(View.GONE);
135 | }
136 | } else {
137 | if (Consts.DEBUG) {
138 | Log.i(TAG, "purchase failed");
139 | }
140 | statusText.setText("Payment failed. Please try again.");
141 | throbber.setVisibility(View.GONE);
142 | }
143 | }
144 | }
145 |
146 | private String mSku;
147 |
148 | /** Called when the activity is first created. */
149 | @Override
150 | public void onCreate(Bundle savedInstanceState) {
151 | super.onCreate(savedInstanceState);
152 | setContentView(R.layout.billing);
153 | mSku = "quota_immunity_day";
154 |
155 | mHandler = new Handler();
156 | mBillingPurchaseObserver = new BillingPurchaseObserver(mHandler);
157 | mBillingService = new BillingService();
158 | mBillingService.setContext(this);
159 |
160 | statusText = (TextView) findViewById(R.id.status);
161 | throbber = (ProgressBar) findViewById(R.id.throbber);
162 |
163 | // Check if billing is supported.
164 | ResponseHandler.register(mBillingPurchaseObserver);
165 | statusText.setText("Purchasing Quota Exemption");
166 | if (!mBillingService.checkBillingSupported()) {
167 | showDialog(DIALOG_CANNOT_CONNECT_ID);
168 | }
169 | if (!mBillingService.requestPurchase(mSku, mPayloadContents)) {
170 | showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
171 | }
172 | }
173 |
174 | @Override
175 | public void onResume() {
176 | super.onResume();
177 | mReceiver = new PaymentNotificationResponse(new Handler());
178 | mReceiver.setReceiver(this);
179 | }
180 |
181 | public void onPause() {
182 | super.onPause();
183 | mReceiver.setReceiver(null);
184 | }
185 |
186 | /**
187 | * Called when this activity becomes visible.
188 | */
189 | @Override
190 | protected void onStart() {
191 | super.onStart();
192 | ResponseHandler.register(mBillingPurchaseObserver);
193 | }
194 |
195 | /**
196 | * Called when this activity is no longer visible.
197 | */
198 | @Override
199 | protected void onStop() {
200 | super.onStop();
201 | ResponseHandler.unregister(mBillingPurchaseObserver);
202 | }
203 |
204 | @Override
205 | protected void onDestroy() {
206 | super.onDestroy();
207 | mBillingService.unbind();
208 | }
209 |
210 | @Override
211 | protected Dialog onCreateDialog(int id) {
212 | switch (id) {
213 | case DIALOG_CANNOT_CONNECT_ID:
214 | DialogFragment cannotConnectFragment = BillingCannotConnectDialogFragment.newInstance();
215 | cannotConnectFragment.show(getSupportFragmentManager(), "dialog");
216 | case DIALOG_BILLING_NOT_SUPPORTED_ID:
217 | DialogFragment unsupportedFragment = BillingNotSupportedDialogFragment.newInstance();
218 | unsupportedFragment.show(getSupportFragmentManager(), "dialog");
219 | default:
220 | return null;
221 | }
222 | }
223 |
224 | public void onReceiveResult(int resultCode, Bundle resultData) {
225 | statusText.setText("Purchase completed.");
226 | throbber.setVisibility(View.GONE);
227 | finish();
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/util/Security.java:
--------------------------------------------------------------------------------
1 | // Copyright 2010 Google Inc. All Rights Reserved.
2 |
3 | package com.suchagit.android2cloud.util;
4 |
5 | import com.suchagit.android2cloud.Consts;
6 | import com.suchagit.android2cloud.util.Base64;
7 | import com.suchagit.android2cloud.util.Base64DecoderException;
8 |
9 | import org.json.JSONArray;
10 | import org.json.JSONException;
11 | import org.json.JSONObject;
12 |
13 | import android.text.TextUtils;
14 | import android.util.Log;
15 |
16 | import java.security.InvalidKeyException;
17 | import java.security.KeyFactory;
18 | import java.security.NoSuchAlgorithmException;
19 | import java.security.PublicKey;
20 | import java.security.SecureRandom;
21 | import java.security.Signature;
22 | import java.security.SignatureException;
23 | import java.security.spec.InvalidKeySpecException;
24 | import java.security.spec.X509EncodedKeySpec;
25 | import java.util.ArrayList;
26 | import java.util.HashSet;
27 |
28 | /**
29 | * Security-related methods. For a secure implementation, all of this code
30 | * should be implemented on a server that communicates with the
31 | * application on the device. For the sake of simplicity and clarity of this
32 | * example, this code is included here and is executed on the device. If you
33 | * must verify the purchases on the phone, you should obfuscate this code to
34 | * make it harder for an attacker to replace the code with stubs that treat all
35 | * purchases as verified.
36 | */
37 | public class Security {
38 | private static final String TAG = "Security";
39 |
40 | private static final String KEY_FACTORY_ALGORITHM = "RSA";
41 | private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
42 | private static final SecureRandom RANDOM = new SecureRandom();
43 |
44 | /**
45 | * This keeps track of the nonces that we generated and sent to the
46 | * server. We need to keep track of these until we get back the purchase
47 | * state and send a confirmation message back to Android Market. If we are
48 | * killed and lose this list of nonces, it is not fatal. Android Market will
49 | * send us a new "notify" message and we will re-generate a new nonce.
50 | * This has to be "static" so that the {@link BillingReceiver} can
51 | * check if a nonce exists.
52 | */
53 | private static HashSet sKnownNonces = new HashSet();
54 |
55 | /**
56 | * A class to hold the verified purchase information.
57 | */
58 | public static class VerifiedPurchase {
59 | public Consts.PurchaseState purchaseState;
60 | public String notificationId;
61 | public String productId;
62 | public String orderId;
63 | public long purchaseTime;
64 | public String developerPayload;
65 |
66 | public VerifiedPurchase(Consts.PurchaseState purchaseState, String notificationId,
67 | String productId, String orderId, long purchaseTime, String developerPayload) {
68 | this.purchaseState = purchaseState;
69 | this.notificationId = notificationId;
70 | this.productId = productId;
71 | this.orderId = orderId;
72 | this.purchaseTime = purchaseTime;
73 | this.developerPayload = developerPayload;
74 | }
75 | }
76 |
77 | /** Generates a nonce (a random number used once). */
78 | public static long generateNonce() {
79 | long nonce = RANDOM.nextLong();
80 | sKnownNonces.add(nonce);
81 | return nonce;
82 | }
83 |
84 | public static void removeNonce(long nonce) {
85 | sKnownNonces.remove(nonce);
86 | }
87 |
88 | public static boolean isNonceKnown(long nonce) {
89 | return sKnownNonces.contains(nonce);
90 | }
91 |
92 | /**
93 | * Verifies that the data was signed with the given signature, and returns
94 | * the list of verified purchases. The data is in JSON format and contains
95 | * a nonce (number used once) that we generated and that was signed
96 | * (as part of the whole data string) with a private key. The data also
97 | * contains the {@link PurchaseState} and product ID of the purchase.
98 | * In the general case, there can be an array of purchase transactions
99 | * because there may be delays in processing the purchase on the backend
100 | * and then several purchases can be batched together.
101 | * @param signedData the signed JSON string (signed, not encrypted)
102 | * @param signature the signature for the data, signed with the private key
103 | */
104 | public static ArrayList verifyPurchase(String signedData, String signature) {
105 | if (signedData == null) {
106 | Log.e(TAG, "data is null");
107 | return null;
108 | }
109 | if (Consts.DEBUG) {
110 | Log.i(TAG, "signedData: " + signedData);
111 | }
112 | boolean verified = false;
113 | if (!TextUtils.isEmpty(signature)) {
114 | /**
115 | * Compute your public key (that you got from the Android Market publisher site).
116 | *
117 | * Instead of just storing the entire literal string here embedded in the
118 | * program, construct the key at runtime from pieces or
119 | * use bit manipulation (for example, XOR with some other string) to hide
120 | * the actual key. The key itself is not secret information, but we don't
121 | * want to make it easy for an adversary to replace the public key with one
122 | * of their own and then fake messages from the server.
123 | *
124 | * Generally, encryption keys / passwords should only be kept in memory
125 | * long enough to perform the operation they need to perform.
126 | */
127 | String base64EncodedPublicKey = "INSERT PUBLIC KEY";
128 | PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
129 | verified = Security.verify(key, signedData, signature);
130 | if (!verified) {
131 | Log.w(TAG, "signature does not match data.");
132 | return null;
133 | }
134 | }
135 |
136 | JSONObject jObject;
137 | JSONArray jTransactionsArray = null;
138 | int numTransactions = 0;
139 | long nonce = 0L;
140 | try {
141 | jObject = new JSONObject(signedData);
142 |
143 | // The nonce might be null if the user backed out of the buy page.
144 | nonce = jObject.optLong("nonce");
145 | jTransactionsArray = jObject.optJSONArray("orders");
146 | if (jTransactionsArray != null) {
147 | numTransactions = jTransactionsArray.length();
148 | }
149 | } catch (JSONException e) {
150 | return null;
151 | }
152 |
153 | if (!Security.isNonceKnown(nonce)) {
154 | Log.w(TAG, "Nonce not found: " + nonce);
155 | return null;
156 | }
157 |
158 | ArrayList purchases = new ArrayList();
159 | try {
160 | for (int i = 0; i < numTransactions; i++) {
161 | JSONObject jElement = jTransactionsArray.getJSONObject(i);
162 | int response = jElement.getInt("purchaseState");
163 | Consts.PurchaseState purchaseState = Consts.PurchaseState.valueOf(response);
164 | String productId = jElement.getString("productId");
165 | String packageName = jElement.getString("packageName");
166 | long purchaseTime = jElement.getLong("purchaseTime");
167 | String orderId = jElement.optString("orderId", "");
168 | String notifyId = null;
169 | if (jElement.has("notificationId")) {
170 | notifyId = jElement.getString("notificationId");
171 | }
172 | String developerPayload = jElement.optString("developerPayload", null);
173 |
174 | // If the purchase state is PURCHASED, then we require a
175 | // verified nonce.
176 | if (purchaseState == Consts.PurchaseState.PURCHASED && !verified) {
177 | continue;
178 | }
179 | purchases.add(new VerifiedPurchase(purchaseState, notifyId, productId,
180 | orderId, purchaseTime, developerPayload));
181 | }
182 | } catch (JSONException e) {
183 | Log.e(TAG, "JSON exception: ", e);
184 | return null;
185 | }
186 | removeNonce(nonce);
187 | return purchases;
188 | }
189 |
190 | /**
191 | * Generates a PublicKey instance from a string containing the
192 | * Base64-encoded public key.
193 | *
194 | * @param encodedPublicKey Base64-encoded public key
195 | * @throws IllegalArgumentException if encodedPublicKey is invalid
196 | */
197 | public static PublicKey generatePublicKey(String encodedPublicKey) {
198 | try {
199 | byte[] decodedKey = Base64.decode(encodedPublicKey);
200 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
201 | return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
202 | } catch (NoSuchAlgorithmException e) {
203 | throw new RuntimeException(e);
204 | } catch (InvalidKeySpecException e) {
205 | Log.e(TAG, "Invalid key specification.");
206 | throw new IllegalArgumentException(e);
207 | } catch (Base64DecoderException e) {
208 | Log.e(TAG, "Base64 decoding failed.");
209 | throw new IllegalArgumentException(e);
210 | }
211 | }
212 |
213 | /**
214 | * Verifies that the signature from the server matches the computed
215 | * signature on the data. Returns true if the data is correctly signed.
216 | *
217 | * @param publicKey public key associated with the developer account
218 | * @param signedData signed data from server
219 | * @param signature server signature
220 | * @return true if the data and signature match
221 | */
222 | public static boolean verify(PublicKey publicKey, String signedData, String signature) {
223 | if (Consts.DEBUG) {
224 | Log.i(TAG, "signature: " + signature);
225 | }
226 | Signature sig;
227 | try {
228 | sig = Signature.getInstance(SIGNATURE_ALGORITHM);
229 | sig.initVerify(publicKey);
230 | sig.update(signedData.getBytes());
231 | if (!sig.verify(Base64.decode(signature))) {
232 | Log.e(TAG, "Signature verification failed.");
233 | return false;
234 | }
235 | return true;
236 | } catch (NoSuchAlgorithmException e) {
237 | Log.e(TAG, "NoSuchAlgorithmException.");
238 | } catch (InvalidKeyException e) {
239 | Log.e(TAG, "Invalid key specification.");
240 | } catch (SignatureException e) {
241 | Log.e(TAG, "Signature exception.");
242 | } catch (Base64DecoderException e) {
243 | Log.e(TAG, "Base64 decoding failed.");
244 | }
245 | return false;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/OAuthActivity.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud;
2 |
3 | import java.io.PrintWriter;
4 | import java.io.StringWriter;
5 | import java.text.SimpleDateFormat;
6 | import java.util.Date;
7 |
8 | import oauth.signpost.OAuthConsumer;
9 | import oauth.signpost.exception.OAuthCommunicationException;
10 | import oauth.signpost.exception.OAuthExpectationFailedException;
11 | import oauth.signpost.exception.OAuthMessageSignerException;
12 | import oauth.signpost.exception.OAuthNotAuthorizedException;
13 | import android.content.ComponentName;
14 | import android.content.Intent;
15 | import android.content.SharedPreferences;
16 | import android.net.Uri;
17 | import android.os.Bundle;
18 | import android.os.Handler;
19 | import android.preference.PreferenceManager;
20 | import android.support.v4.app.DialogFragment;
21 | import android.support.v4.app.FragmentActivity;
22 | import android.text.format.Time;
23 | import android.util.Log;
24 | import android.view.View;
25 | import android.widget.Button;
26 | import android.widget.EditText;
27 |
28 | import com.suchagit.android2cloud.errors.IncorrectTimeDialogFragment;
29 | import com.suchagit.android2cloud.errors.OAuthActivityNullUriDialogFragment;
30 | import com.suchagit.android2cloud.errors.OAuthCommunicationExceptionDialogFragment;
31 | import com.suchagit.android2cloud.errors.OAuthExpectationFailedExceptionDialogFragment;
32 | import com.suchagit.android2cloud.errors.OAuthMessageSignerExceptionDialogFragment;
33 | import com.suchagit.android2cloud.errors.OAuthNotAuthorizedExceptionDialogFragment;
34 | import com.suchagit.android2cloud.errors.OAuthWebviewNullIntentDialogFragment;
35 | import com.suchagit.android2cloud.util.CheckTimeResponse;
36 | import com.suchagit.android2cloud.util.HttpClient;
37 | import com.suchagit.android2cloud.util.OAuth;
38 | import com.suchagit.android2cloud.util.OAuthAccount;
39 |
40 | public class OAuthActivity extends FragmentActivity implements CheckTimeResponse.Receiver {
41 | private EditText host_input;
42 | private EditText account_input;
43 | public CheckTimeResponse mReceiver;
44 |
45 | @Override
46 | public void onCreate(Bundle savedInstance) {
47 | super.onCreate(savedInstance);
48 | setContentView(R.layout.add_account);
49 | host_input = (EditText) findViewById(R.id.host_entry);
50 | account_input = (EditText) findViewById(R.id.account_entry);
51 |
52 | final Button add_button = (Button) findViewById(R.id.add_account);
53 |
54 | add_button.setOnClickListener(new View.OnClickListener() {
55 | public void onClick(View v) {
56 | String requestUrl = "";
57 | try {
58 | requestUrl = OAuth.getRequestUrl(host_input.getText().toString(), account_input.getText().toString());
59 | Intent intent = new Intent(OAuthActivity.this, OAuthWebView.class);
60 | intent.setData(Uri.parse(requestUrl));
61 | startActivityForResult(intent, OAuth.INTENT_ID);
62 | } catch (OAuthMessageSignerException e) {
63 | StringWriter sw = new StringWriter();
64 | PrintWriter pw = new PrintWriter(sw);
65 | e.printStackTrace(pw);
66 | Bundle error_data = new Bundle();
67 | error_data.putString("stacktrace", sw.toString());
68 | error_data.putString("host", host_input.getText().toString());
69 | error_data.putString("account", account_input.getText().toString());
70 | error_data.putString("request_url", requestUrl);
71 | DialogFragment errorFragment = OAuthMessageSignerExceptionDialogFragment.newInstance(error_data);
72 | errorFragment.show(getSupportFragmentManager(), "dialog");
73 | } catch (OAuthNotAuthorizedException e) {
74 | StringWriter sw = new StringWriter();
75 | PrintWriter pw = new PrintWriter(sw);
76 | e.printStackTrace(pw);
77 | Bundle error_data = new Bundle();
78 | error_data.putString("stacktrace", sw.toString());
79 | error_data.putString("host", host_input.getText().toString());
80 | error_data.putString("account", account_input.getText().toString());
81 | error_data.putString("request_url", requestUrl);
82 | DialogFragment errorFragment = OAuthNotAuthorizedExceptionDialogFragment.newInstance(error_data);
83 | errorFragment.show(getSupportFragmentManager(), "dialog");
84 | } catch (OAuthExpectationFailedException e) {
85 | StringWriter sw = new StringWriter();
86 | PrintWriter pw = new PrintWriter(sw);
87 | e.printStackTrace(pw);
88 | Bundle error_data = new Bundle();
89 | error_data.putString("stacktrace", sw.toString());
90 | error_data.putString("host", host_input.getText().toString());
91 | error_data.putString("account", account_input.getText().toString());
92 | error_data.putString("request_url", requestUrl);
93 | DialogFragment errorFragment = OAuthExpectationFailedExceptionDialogFragment.newInstance(error_data);
94 | errorFragment.show(getSupportFragmentManager(), "dialog");
95 | } catch (OAuthCommunicationException e) {
96 | StringWriter sw = new StringWriter();
97 | PrintWriter pw = new PrintWriter(sw);
98 | e.printStackTrace(pw);
99 | Bundle error_data = new Bundle();
100 | error_data.putString("stacktrace", sw.toString());
101 | error_data.putString("host", host_input.getText().toString());
102 | error_data.putString("account", account_input.getText().toString());
103 | error_data.putString("request_url", requestUrl);
104 | error_data.putString("response_body", e.getResponseBody());
105 | getServerTime();
106 | mReceiver.setPassThrough(error_data);
107 | //getServerTime() is asynchronous, so we pass the error information to it so it can display the error if the time is right
108 | //DialogFragment errorFragment = OAuthCommunicationExceptionDialogFragment.newInstance(error_data);
109 | //errorFragment.show(getSupportFragmentManager(), "dialog");
110 | }
111 | }
112 | });
113 | }
114 |
115 | @Override
116 | protected void onResume() {
117 | super.onResume();
118 | mReceiver = new CheckTimeResponse(new Handler());
119 | mReceiver.setReceiver(this);
120 | }
121 |
122 |
123 | @Override
124 | protected void onActivityResult(int req_code, int res_code, Intent intent) {
125 | super.onActivityResult(req_code, res_code, intent);
126 | if(intent != null) {
127 | Uri uri = intent.getData();
128 | if(uri != null) {
129 | SharedPreferences accounts = getSharedPreferences("android2cloud-accounts", 0);
130 | String verifier = uri.getQueryParameter("oauth_token");
131 | String account = uri.getQueryParameter("account");
132 | String host = uri.getScheme() + "://" + uri.getHost() + "/";
133 | try {
134 | OAuthConsumer consumer = OAuth.getAccessToken(host, verifier);
135 | OAuthAccount oauth_account = new OAuthAccount(account, host, consumer.getToken(), consumer.getTokenSecret());
136 | oauth_account.save(accounts);
137 | SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
138 | SharedPreferences.Editor editor = settings.edit();
139 | editor.putString("account", account);
140 | editor.commit();
141 | finish();
142 | } catch (OAuthMessageSignerException e) {
143 | StringWriter sw = new StringWriter();
144 | PrintWriter pw = new PrintWriter(sw);
145 | e.printStackTrace(pw);
146 | Bundle error_data = new Bundle();
147 | error_data.putString("stacktrace", sw.toString());
148 | error_data.putString("host", host);
149 | error_data.putString("account", account);
150 | error_data.putString("verifier", verifier);
151 | showDialog(R.string.oauth_message_signer_exception_error, error_data);
152 | DialogFragment errorFragment = OAuthMessageSignerExceptionDialogFragment.newInstance(error_data);
153 | errorFragment.show(getSupportFragmentManager(), "dialog");
154 | } catch (OAuthNotAuthorizedException e) {
155 | StringWriter sw = new StringWriter();
156 | PrintWriter pw = new PrintWriter(sw);
157 | e.printStackTrace(pw);
158 | Bundle error_data = new Bundle();
159 | error_data.putString("stacktrace", sw.toString());
160 | error_data.putString("host", host);
161 | error_data.putString("account", account);
162 | error_data.putString("verifier", verifier);
163 | DialogFragment errorFragment = OAuthNotAuthorizedExceptionDialogFragment.newInstance(error_data);
164 | errorFragment.show(getSupportFragmentManager(), "dialog");
165 | } catch (OAuthExpectationFailedException e) {
166 | StringWriter sw = new StringWriter();
167 | PrintWriter pw = new PrintWriter(sw);
168 | e.printStackTrace(pw);
169 | Bundle error_data = new Bundle();
170 | error_data.putString("stacktrace", sw.toString());
171 | error_data.putString("host", host);
172 | error_data.putString("account", account);
173 | error_data.putString("verifier", verifier);
174 | DialogFragment errorFragment = OAuthExpectationFailedExceptionDialogFragment.newInstance(error_data);
175 | errorFragment.show(getSupportFragmentManager(), "dialog");
176 | } catch (OAuthCommunicationException e) {
177 | StringWriter sw = new StringWriter();
178 | PrintWriter pw = new PrintWriter(sw);
179 | e.printStackTrace(pw);
180 | Bundle error_data = new Bundle();
181 | error_data.putString("stacktrace", sw.toString());
182 | error_data.putString("host", host);
183 | error_data.putString("account", account);
184 | error_data.putString("verifier", verifier);
185 | error_data.putString("response_body", e.getResponseBody());
186 | DialogFragment errorFragment = OAuthCommunicationExceptionDialogFragment.newInstance(error_data);
187 | errorFragment.show(getSupportFragmentManager(), "dialog");
188 | }
189 | } else {
190 | DialogFragment errorFragment = OAuthActivityNullUriDialogFragment.newInstance();
191 | errorFragment.show(getSupportFragmentManager(), "dialog");
192 | }
193 | } else {
194 | DialogFragment errorFragment = OAuthWebviewNullIntentDialogFragment.newInstance();
195 | errorFragment.show(getSupportFragmentManager(), "dialog");
196 | }
197 | }
198 |
199 | public void getServerTime() {
200 | Intent intent = new Intent();
201 | intent.setComponent(new ComponentName("com.suchagit.android2cloud", "com.suchagit.android2cloud.HttpService"));
202 | intent.setAction("com.suchagit.android2cloud.CheckTime");
203 | intent.putExtra("com.suchagit.android2cloud.result_receiver", mReceiver);
204 | intent.putExtra("com.suchagit.android2cloud.host", host_input.getText().toString());
205 | startService(intent);
206 | }
207 |
208 | public Bundle getDeviceTime() {
209 | Bundle deviceTime = new Bundle();
210 | deviceTime.putLong("currentTime", System.currentTimeMillis());
211 | deviceTime.putString("timezone", Time.getCurrentTimezone());
212 | deviceTime.putString("friendlyTime", new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(new Date(System.currentTimeMillis())));
213 | return deviceTime;
214 | }
215 |
216 | public boolean correctTime(Long serverTime) {
217 | Bundle device = getDeviceTime();
218 | long diff = device.getLong("currentTime") - serverTime;
219 | long acceptableDiff = 1800000; // thirty minutes in milliseconds
220 | Log.d("OAuthActivity", device.getLong("currentTime") + "");
221 | return (diff < acceptableDiff && diff > 0);
222 | }
223 |
224 | @Override
225 | public void onReceiveResult(int resultCode, Bundle resultData) {
226 | if(resultCode == HttpClient.STATUS_COMPLETE && resultData.getInt("response_code") == 200 && !correctTime(resultData.getLong("currentTime"))) {
227 | DialogFragment errorFragment = IncorrectTimeDialogFragment.newInstance(getDeviceTime());
228 | errorFragment.show(getSupportFragmentManager(), "dialog");
229 | } else {
230 | // display the OAuthCommunicationError we swallowed
231 | Bundle error_data = mReceiver.getPassThrough();
232 | DialogFragment errorFragment = OAuthCommunicationExceptionDialogFragment.newInstance(error_data);
233 | errorFragment.show(getSupportFragmentManager(), "dialog");
234 | }
235 | }
236 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/ErrorDialogBuilder.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.content.Context;
6 | import android.content.DialogInterface;
7 | import android.content.Intent;
8 | import android.content.pm.PackageManager.NameNotFoundException;
9 | import android.os.Bundle;
10 |
11 | public class ErrorDialogBuilder extends AlertDialog.Builder {
12 | private Activity activity;
13 | private Bundle data;
14 |
15 | private DialogInterface.OnClickListener default_ok_listener = new DialogInterface.OnClickListener() {
16 | public void onClick(DialogInterface dialog, int id) {
17 | dialog.cancel();
18 | }
19 | };
20 |
21 | private DialogInterface.OnClickListener default_ok_kill_listener = new DialogInterface.OnClickListener() {
22 | public void onClick(DialogInterface dialog, int id) {
23 | dialog.cancel();
24 | ErrorDialogBuilder.this.activity.finish();
25 | }
26 | };
27 |
28 | public ErrorDialogBuilder(Context context) {
29 | super(context);
30 | this.activity = (Activity) context;
31 | this.buildDefault();
32 | }
33 |
34 | public ErrorDialogBuilder(Context context, Bundle bundle) {
35 | super(context);
36 | this.activity = (Activity) context;
37 | this.buildDefault();
38 | if(bundle != null)
39 | this.data = bundle;
40 | }
41 |
42 | public ErrorDialogBuilder(Context context, String message) {
43 | super(context);
44 | this.activity = (Activity) context;
45 | this.buildDefault();
46 | this.setMessage(message);
47 | }
48 |
49 | public ErrorDialogBuilder(Context context, int message) {
50 | super(context);
51 | this.activity = (Activity) context;
52 | this.buildDefault();
53 | this.setMessage(message);
54 | }
55 |
56 | public ErrorDialogBuilder buildDefault() {
57 | this.setMessage(R.string.default_error_message)
58 | .setTitle(R.string.default_error_title)
59 | .setPositiveButton(R.string.default_error_ok, default_ok_listener)
60 | .setCancelable(false);
61 | this.data = new Bundle();
62 | return this;
63 | }
64 |
65 | public ErrorDialogBuilder build(int error) {
66 | this.setMessage(error);
67 | switch(error) {
68 | case R.string.oauthactivity_null_uri_error:
69 | this.setPositiveButton(R.string.default_error_ok, default_ok_kill_listener);
70 | break;
71 | case R.string.oauthwebview_null_intent_error:
72 | this.setPositiveButton(R.string.default_error_ok, default_ok_kill_listener);
73 | break;
74 | case R.string.postlink_null_link_error:
75 | this.setCancelable(true);
76 | break;
77 | case R.string.postlink_null_receiver_error:
78 | this.setCancelable(true);
79 | break;
80 | case R.string.postlink_auth_error:
81 | this.setNegativeButton(R.string.postlink_auth_error_account_button, new DialogInterface.OnClickListener() {
82 | public void onClick(DialogInterface dialog, int id) {
83 | Intent i = new Intent(ErrorDialogBuilder.this.activity, Preferences.class);
84 | ErrorDialogBuilder.this.activity.startActivityForResult(i, PostLinkActivity.EDIT_SETTINGS_REQ_CODE);
85 | }
86 | })
87 | .setNeutralButton(R.string.report_error_button, new DialogInterface.OnClickListener() {
88 | public void onClick(DialogInterface dialog, int id) {
89 | String message = "Error:\n";
90 | message += "Type: postlink_auth_error\n";
91 | try {
92 | message += "Version: " + ErrorDialogBuilder.this.activity.getPackageManager().getPackageInfo(ErrorDialogBuilder.this.activity.getPackageName(), 0 ).versionCode + "\n";
93 | } catch (NameNotFoundException e) {
94 | }
95 | message += "Account: " + ErrorDialogBuilder.this.data.getString("account") +"\n";
96 | message += "Host: " + ErrorDialogBuilder.this.data.getString("host") + "\n";
97 | message += "Token: " + ErrorDialogBuilder.this.data.getString("token") + "\n";
98 | message += "Secret: " + ErrorDialogBuilder.this.data.getString("secret") + "\n";
99 | Intent report = getEmailIntent(message);
100 | ErrorDialogBuilder.this.activity.startActivity(report);
101 | }
102 | }).setCancelable(true);
103 | break;
104 | case R.string.no_accounts_error:
105 | this.setPositiveButton(R.string.no_accounts_error_account_button, new DialogInterface.OnClickListener() {
106 | public void onClick(DialogInterface dialog, int id) {
107 | Intent i = new Intent(ErrorDialogBuilder.this.activity, OAuthActivity.class);
108 | ErrorDialogBuilder.this.activity.startActivity(i);
109 | }
110 | });
111 | break;
112 | case R.string.no_account_selected_error:
113 | this.setPositiveButton(R.string.no_account_selected_error_account_button, new DialogInterface.OnClickListener() {
114 | public void onClick(DialogInterface dialog, int id) {
115 | Intent i = new Intent(ErrorDialogBuilder.this.activity, Preferences.class);
116 | ErrorDialogBuilder.this.activity.startActivityForResult(i, PostLinkActivity.EDIT_SETTINGS_REQ_CODE);
117 | }
118 | });
119 | break;
120 | case R.string.intent_without_link_error:
121 | this.setCancelable(true);
122 | break;
123 | case R.string.http_client_error:
124 | this.setNegativeButton(R.string.report_error_button, new DialogInterface.OnClickListener() {
125 | public void onClick(DialogInterface dialog, int id) {
126 | String message = "Error:\n";
127 | message += "Type: http_client_error\n";
128 | try {
129 | message += "Version: " + ErrorDialogBuilder.this.activity.getPackageManager().getPackageInfo(ErrorDialogBuilder.this.activity.getPackageName(), 0 ).versionCode + "\n";
130 | } catch (NameNotFoundException e) {
131 | }
132 | message += "Account: " + ErrorDialogBuilder.this.data.getString("account") +"\n";
133 | message += "Host: " + ErrorDialogBuilder.this.data.getString("host") + "\n";
134 | message += "Token: " + ErrorDialogBuilder.this.data.getString("token") + "\n";
135 | message += "Secret: " + ErrorDialogBuilder.this.data.getString("secret") + "\n";
136 | message += "Raw Data: " + ErrorDialogBuilder.this.data.getString("raw_data") + "\n";
137 | Intent report = getEmailIntent(message);
138 | ErrorDialogBuilder.this.activity.startActivity(report);
139 | }
140 | }).setCancelable(true);
141 | break;
142 | case R.string.over_quota_error:
143 | this.setTitle(R.string.over_quota_error_title)
144 | .setNegativeButton(R.string.over_quota_error_pay_button, new DialogInterface.OnClickListener() {
145 | public void onClick(DialogInterface dialog, int id) {
146 | Intent i = new Intent(ErrorDialogBuilder.this.activity, Billing.class);
147 | ErrorDialogBuilder.this.activity.startActivityForResult(i, PostLinkActivity.BILLING_INTENT_CODE);
148 | }
149 | });
150 | break;
151 | case R.string.oauth_message_signer_exception_error:
152 | this.setNegativeButton(R.string.report_error_button, new DialogInterface.OnClickListener() {
153 | public void onClick(DialogInterface dialog, int id) {
154 | String message = "Error:\n";
155 | message += "Type: oauth_message_signer_exception_error\n";
156 | try {
157 | message += "Version: " + ErrorDialogBuilder.this.activity.getPackageManager().getPackageInfo(ErrorDialogBuilder.this.activity.getPackageName(), 0 ).versionCode + "\n";
158 | } catch (NameNotFoundException e) {
159 | }
160 | message += "Account: " + ErrorDialogBuilder.this.data.getString("account") +"\n";
161 | message += "Host: " + ErrorDialogBuilder.this.data.getString("host") + "\n";
162 | if(ErrorDialogBuilder.this.data.get("request_url") != null)
163 | message += "Request URL: " + ErrorDialogBuilder.this.data.getString("request_url") + "\n";
164 | else if(ErrorDialogBuilder.this.data.get("verifier") != null)
165 | message += "Verifier: " + ErrorDialogBuilder.this.data.getString("verifier") + "\n";
166 | message += "Stacktrace: \n";
167 | message += ErrorDialogBuilder.this.data.getString("stacktrace") + "\n";
168 | Intent report = getEmailIntent(message);
169 | ErrorDialogBuilder.this.activity.startActivity(report);
170 | }
171 | }).setCancelable(true);
172 | break;
173 | case R.string.oauth_not_authorized_exception_error:
174 | this.setNegativeButton(R.string.report_error_button, new DialogInterface.OnClickListener() {
175 | public void onClick(DialogInterface dialog, int id) {
176 | String message = "Error:\n";
177 | message += "Type: oauth_not_authorized_exception_error\n";
178 | try {
179 | message += "Version: " + ErrorDialogBuilder.this.activity.getPackageManager().getPackageInfo(ErrorDialogBuilder.this.activity.getPackageName(), 0 ).versionCode + "\n";
180 | } catch (NameNotFoundException e) {
181 | }
182 | message += "Account: " + ErrorDialogBuilder.this.data.getString("account") +"\n";
183 | message += "Host: " + ErrorDialogBuilder.this.data.getString("host") + "\n";
184 | if(ErrorDialogBuilder.this.data.get("request_url") != null)
185 | message += "Request URL: " + ErrorDialogBuilder.this.data.getString("request_url") + "\n";
186 | else if(ErrorDialogBuilder.this.data.get("verifier") != null)
187 | message += "Verifier: " + ErrorDialogBuilder.this.data.getString("verifier") + "\n";
188 | message += "Stacktrace: \n";
189 | message += ErrorDialogBuilder.this.data.getString("stacktrace") + "\n";
190 | Intent report = getEmailIntent(message);
191 | ErrorDialogBuilder.this.activity.startActivity(report);
192 | }
193 | }).setCancelable(true);
194 | break;
195 | case R.string.oauth_expectation_failed_exception_error:
196 | this.setNegativeButton(R.string.report_error_button, new DialogInterface.OnClickListener() {
197 | public void onClick(DialogInterface dialog, int id) {
198 | String message = "Error:\n";
199 | message += "Type: oauth_expectation_failed_exception_error\n";
200 | try {
201 | message += "Version: " + ErrorDialogBuilder.this.activity.getPackageManager().getPackageInfo(ErrorDialogBuilder.this.activity.getPackageName(), 0 ).versionCode + "\n";
202 | } catch (NameNotFoundException e) {
203 | }
204 | message += "Account: " + ErrorDialogBuilder.this.data.getString("account") +"\n";
205 | message += "Host: " + ErrorDialogBuilder.this.data.getString("host") + "\n";
206 | if(ErrorDialogBuilder.this.data.get("request_url") != null)
207 | message += "Request URL: " + ErrorDialogBuilder.this.data.getString("request_url") + "\n";
208 | else if(ErrorDialogBuilder.this.data.get("verifier") != null)
209 | message += "Verifier: " + ErrorDialogBuilder.this.data.getString("verifier") + "\n";
210 | message += "Stacktrace: \n";
211 | message += ErrorDialogBuilder.this.data.getString("stacktrace") + "\n";
212 | Intent report = getEmailIntent(message);
213 | ErrorDialogBuilder.this.activity.startActivity(report);
214 | }
215 | }).setCancelable(true);
216 | break;
217 | case R.string.oauth_communication_exception_error:
218 | this.setNegativeButton(R.string.report_error_button, new DialogInterface.OnClickListener() {
219 | public void onClick(DialogInterface dialog, int id) {
220 | String message = "Error:\n";
221 | message += "Type: oauth_communication_exception_error\n";
222 | try {
223 | message += "Version: " + ErrorDialogBuilder.this.activity.getPackageManager().getPackageInfo(ErrorDialogBuilder.this.activity.getPackageName(), 0 ).versionCode + "\n";
224 | } catch (NameNotFoundException e) {
225 | }
226 | message += "Account: " + ErrorDialogBuilder.this.data.getString("account") +"\n";
227 | message += "Host: " + ErrorDialogBuilder.this.data.getString("host") + "\n";
228 | if(ErrorDialogBuilder.this.data.get("request_url") != null)
229 | message += "Request URL: " + ErrorDialogBuilder.this.data.getString("request_url") + "\n";
230 | else if(ErrorDialogBuilder.this.data.get("verifier") != null)
231 | message += "Verifier: " + ErrorDialogBuilder.this.data.getString("verifier") + "\n";
232 | message += "Stacktrace: \n";
233 | message += ErrorDialogBuilder.this.data.getString("stacktrace") + "\n";
234 | Intent report = getEmailIntent(message);
235 | ErrorDialogBuilder.this.activity.startActivity(report);
236 | }
237 | })
238 | .setNeutralButton(R.string.oauth_communication_exception_error_networking_button, new DialogInterface.OnClickListener() {
239 | public void onClick(DialogInterface dialog, int id) {
240 | Intent i = new Intent(Intent.ACTION_MAIN);
241 | i.setClassName("com.android.phone", "com.android.phone.NetworkSetting");
242 | ErrorDialogBuilder.this.activity.startActivity(i);
243 | }
244 | }).setCancelable(true);
245 | break;
246 | case R.string.unsupported_encoding_exception_error:
247 | this.setNegativeButton(R.string.report_error_button, new DialogInterface.OnClickListener() {
248 | public void onClick(DialogInterface dialog, int id) {
249 | String message = "Error:\n";
250 | message += "Type: unsupported_encoding_exception_error\n";
251 | try {
252 | message += "Version: " + ErrorDialogBuilder.this.activity.getPackageManager().getPackageInfo(ErrorDialogBuilder.this.activity.getPackageName(), 0 ).versionCode + "\n";
253 | } catch (NameNotFoundException e) {
254 | }
255 | message += "Account: " + ErrorDialogBuilder.this.data.getString("account") +"\n";
256 | message += "Host: " + ErrorDialogBuilder.this.data.getString("host") + "\n";
257 | message += "URL: " + ErrorDialogBuilder.this.data.getString("link") + "\n";
258 | message += "Device: " + ErrorDialogBuilder.this.data.getString("device_name") + "\n";
259 | message += "Recipient: " + ErrorDialogBuilder.this.data.getString("reciever") + "\n";
260 | message += "Stacktrace: \n";
261 | message += ErrorDialogBuilder.this.data.getString("stacktrace") + "\n";
262 | Intent report = getEmailIntent(message);
263 | ErrorDialogBuilder.this.activity.startActivity(report);
264 | }
265 | }).setCancelable(true);
266 | break;
267 | }
268 | return this;
269 | }
270 |
271 | public static Intent getEmailIntent(String message) {
272 | Intent i = new Intent(Intent.ACTION_SEND);
273 | i.setType("message/rfc822");
274 | i.putExtra(Intent.EXTRA_EMAIL , new String[]{"android@2cloudproject.com"});
275 | i.putExtra(Intent.EXTRA_SUBJECT, "In-App Error Report");
276 | String message_prefix = "Android Version: " + android.os.Build.VERSION.SDK + "\n";
277 | message_prefix += "Phone: " + android.os.Build.MODEL + "\n";
278 | message = message_prefix + message;
279 | i.putExtra(Intent.EXTRA_TEXT , message);
280 | return i;
281 | }
282 |
283 | }
284 |
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/PostLinkActivity.java:
--------------------------------------------------------------------------------
1 | package com.suchagit.android2cloud;
2 |
3 | import java.util.ArrayList;
4 | import java.util.regex.Matcher;
5 | import java.util.regex.Pattern;
6 |
7 | import android.content.ComponentName;
8 | import android.content.Intent;
9 | import android.content.SharedPreferences;
10 | import android.os.Bundle;
11 | import android.os.Handler;
12 | import android.preference.PreferenceManager;
13 | import android.support.v4.app.DialogFragment;
14 | import android.support.v4.app.FragmentActivity;
15 | import android.util.Log;
16 | import android.view.Menu;
17 | import android.view.MenuInflater;
18 | import android.view.MenuItem;
19 | import android.view.View;
20 | import android.widget.Button;
21 | import android.widget.EditText;
22 | import android.widget.ProgressBar;
23 | import android.widget.TextView;
24 | import android.widget.Toast;
25 |
26 | import com.suchagit.android2cloud.errors.CorruptedAccountDialogFragment;
27 | import com.suchagit.android2cloud.errors.DefaultErrorDialogFragment;
28 | import com.suchagit.android2cloud.errors.DeprecatedHostExceptionDialogFragment;
29 | import com.suchagit.android2cloud.errors.HttpClientErrorDialogFragment;
30 | import com.suchagit.android2cloud.errors.IllegalArgumentExceptionDialogFragment;
31 | import com.suchagit.android2cloud.errors.IllegalStateExceptionDialogFragment;
32 | import com.suchagit.android2cloud.errors.IntentWithoutLinkDialogFragment;
33 | import com.suchagit.android2cloud.errors.NoAccountSelectedDialogFragment;
34 | import com.suchagit.android2cloud.errors.NoAccountsDialogFragment;
35 | import com.suchagit.android2cloud.errors.OverQuotaDialogFragment;
36 | import com.suchagit.android2cloud.errors.PostLinkAuthErrorDialogFragment;
37 | import com.suchagit.android2cloud.errors.PostLinkNullLinkDialogFragment;
38 | import com.suchagit.android2cloud.errors.PostLinkNullReceiverDialogFragment;
39 | import com.suchagit.android2cloud.errors.SelectLinkDialogFragment;
40 | import com.suchagit.android2cloud.errors.UnsupportedEncodingExceptionDialogFragment;
41 | import com.suchagit.android2cloud.util.AddLinkResponse;
42 | import com.suchagit.android2cloud.util.HttpClient;
43 | import com.suchagit.android2cloud.util.OAuthAccount;
44 |
45 | public class PostLinkActivity extends FragmentActivity implements AddLinkResponse.Receiver {
46 |
47 | public static final int BILLING_INTENT_CODE = 0x1079;
48 |
49 | public AddLinkResponse mReceiver;
50 |
51 | private OAuthAccount account;
52 | private SharedPreferences settings;
53 | private SharedPreferences accounts_preferences;
54 |
55 | private EditText device_entry;
56 | private EditText url_input;
57 | private TextView account_display;
58 | private ProgressBar throbber;
59 | private Button send_button;
60 |
61 | private String link = "";
62 | private String receiver = "";
63 | private boolean popup = false;
64 | private boolean contentSet = false;
65 |
66 | public final static int EDIT_SETTINGS_REQ_CODE = 0x1234;
67 |
68 | private static final int NO_ACCOUNT = 0;
69 | private static final int NO_ACCOUNT_SELECTED = 2;
70 | private static final int ACCOUNT = 1;
71 |
72 | @Override
73 | public void onCreate(Bundle savedInstance) {
74 | super.onCreate(savedInstance);
75 |
76 | settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
77 |
78 | receiver = settings.getString("receiver", "Chrome");
79 |
80 | if(!settings.getBoolean("silent", false)) {
81 | render();
82 | }
83 | }
84 |
85 | @Override
86 | public void onResume() {
87 | super.onResume();
88 |
89 | settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
90 | //check to make sure they have an account
91 | popup = false;
92 | int accountStatus = checkAccount();
93 | switch(accountStatus) {
94 | case NO_ACCOUNT:
95 | popup = true;
96 | render();
97 | DialogFragment noAccountFragment = NoAccountsDialogFragment.newInstance();
98 | noAccountFragment.show(getSupportFragmentManager(), "dialog");
99 | break;
100 | case NO_ACCOUNT_SELECTED:
101 | popup = true;
102 | render();
103 | DialogFragment selectAccountFragment = NoAccountSelectedDialogFragment.newInstance();
104 | selectAccountFragment.show(getSupportFragmentManager(), "dialog");
105 | break;
106 | }
107 |
108 | try {
109 | checkHost(account.getHost());
110 | } catch(DeprecatedHostException e) {
111 | popup = true;
112 | render();
113 | account.delete(accounts_preferences);
114 | DialogFragment deprecatedHostFragment = DeprecatedHostExceptionDialogFragment.newInstance();
115 | deprecatedHostFragment.show(getSupportFragmentManager(), "dialog");
116 | } catch(CorruptedAccountException e) {
117 | popup = true;
118 | render();
119 | account.delete(accounts_preferences);
120 | DialogFragment corruptedAccountFragment = CorruptedAccountDialogFragment.newInstance();
121 | corruptedAccountFragment.show(getSupportFragmentManager(), "dialog");
122 | }
123 |
124 | // pull the URL from the intent
125 | if(Intent.ACTION_SEND.equals(getIntent().getAction())) {
126 | String intentText = getStringFromIntent(getIntent());
127 | try{
128 | link = getLinkFromString(intentText);
129 | } catch(NoLinkFoundException e){
130 | popup = true;
131 | render();
132 | DialogFragment errorFragment = IntentWithoutLinkDialogFragment.newInstance();
133 | errorFragment.show(getSupportFragmentManager(), "dialog");
134 | } catch(TooManyLinksException e) {
135 | popup = true;
136 | render();
137 | Bundle data = new Bundle();
138 | data.putCharSequenceArray("choices", e.getLinks());
139 | DialogFragment errorFragment = SelectLinkDialogFragment.newInstance(data);
140 | errorFragment.show(getSupportFragmentManager(), "dialog");
141 | }
142 | }
143 |
144 | mReceiver = new AddLinkResponse(new Handler());
145 | mReceiver.setReceiver(this);
146 | if(settings.getBoolean("silent", false) && popup == false && link != null && link.trim() != "") {
147 | sendLink();
148 | finish();
149 | } else {
150 | render();
151 | url_input.setText(link);
152 | device_entry.setText(receiver);
153 | account_display.setText("Account: "+account.getAccount());
154 | }
155 | }
156 |
157 | public void onPause() {
158 | super.onPause();
159 | mReceiver.setReceiver(null);
160 | contentSet = false;
161 | }
162 |
163 | @Override
164 | public boolean onCreateOptionsMenu(Menu menu) {
165 | MenuInflater inflater = getMenuInflater();
166 | inflater.inflate(R.menu.post_link, menu);
167 | return true;
168 | }
169 |
170 | @Override
171 | public boolean onOptionsItemSelected(MenuItem item) {
172 | // Handle item selection
173 | switch (item.getItemId()) {
174 | case R.id.edit_settings:
175 | Intent i = new Intent(PostLinkActivity.this, Preferences.class);
176 | startActivityForResult(i, EDIT_SETTINGS_REQ_CODE);
177 | return true;
178 | default:
179 | return super.onOptionsItemSelected(item);
180 | }
181 | }
182 |
183 | public void onReceiveResult(int resultCode, Bundle resultData) {
184 | throbber.setVisibility(View.GONE);
185 | send_button.setVisibility(View.VISIBLE);
186 | switch (resultCode) {
187 | case HttpClient.STATUS_COMPLETE:
188 | int code = resultData.getInt("response_code");
189 | String resp = "";
190 | if(code == 200) {
191 | resp = "Successfully sent " + resultData.getString("link") + " to the cloud.";
192 | Toast.makeText(this, resp, Toast.LENGTH_LONG).show();
193 | } else if(code == 500) {
194 | if(resultData.getString("type") == "client_error") {
195 | Bundle error_data = new Bundle();
196 | error_data.putString("account", account.getAccount());
197 | error_data.putString("host", account.getHost());
198 | error_data.putString("token", account.getToken());
199 | error_data.putString("secret", account.getKey());
200 | error_data.putString("raw_data", resultData.getString("raw_result"));
201 | DialogFragment errorFragment = HttpClientErrorDialogFragment.newInstance(error_data);
202 | errorFragment.show(getSupportFragmentManager(), "dialog");
203 | }
204 | } else if(code == 401) {
205 | Bundle error_data = new Bundle();
206 | error_data.putString("account", account.getAccount());
207 | error_data.putString("host", account.getHost());
208 | error_data.putString("token", account.getToken());
209 | error_data.putString("secret", account.getKey());
210 | DialogFragment errorFragment = PostLinkAuthErrorDialogFragment.newInstance(error_data);
211 | errorFragment.show(getSupportFragmentManager(), "dialog");
212 | } else if(code == 503) {
213 | DialogFragment errorFragment = OverQuotaDialogFragment.newInstance();
214 | errorFragment.show(getSupportFragmentManager(), "dialog");
215 | }
216 | break;
217 | case HttpClient.STATUS_ERROR:
218 | int error_code = resultData.getInt("response_code");
219 | if(error_code == 600) {
220 | if(resultData.getString("type").equals("unsupported_encoding_exception_error")) {
221 | Bundle error_data = new Bundle();
222 | error_data.putString("account", account.getAccount());
223 | error_data.putString("host", account.getHost());
224 | error_data.putString("device_name", "Android");
225 | error_data.putString("receiver", receiver);
226 | error_data.putString("link", link);
227 | DialogFragment errorFragment = UnsupportedEncodingExceptionDialogFragment.newInstance(error_data);
228 | errorFragment.show(getSupportFragmentManager(), "dialog");
229 | } else if(resultData.getString("type").equals("illegal_state_exception_error")) {
230 | Bundle error_data = new Bundle();
231 | error_data.putString("host", account.getHost());
232 | error_data.putString("request_type", resultData.getString("request_type"));
233 | error_data.putString("request_host", resultData.getString("host"));
234 | error_data.putString("stacktrace", resultData.getString("stacktrace"));
235 | DialogFragment illegalStateFragment = IllegalStateExceptionDialogFragment.newInstance(error_data);
236 | illegalStateFragment.show(getSupportFragmentManager(), "dialog");
237 | } else if(resultData.getString("type").equals("illegal_argument_exception_error")) {
238 | Bundle error_data = new Bundle();
239 | error_data.putString("host", account.getHost());
240 | error_data.putString("request_type", resultData.getString("request_type"));
241 | error_data.putString("request_host", resultData.getString("host"));
242 | error_data.putString("stacktrace", resultData.getString("stacktrace"));
243 | DialogFragment illegalArgFragment = IllegalArgumentExceptionDialogFragment.newInstance(error_data);
244 | illegalArgFragment.show(getSupportFragmentManager(), "dialog");
245 | }
246 | } else {
247 | DialogFragment errorFragment = DefaultErrorDialogFragment.newInstance();
248 | errorFragment.show(getSupportFragmentManager(), "dialog");
249 | }
250 | break;
251 | }
252 | }
253 |
254 | private void render() {
255 | if(!contentSet) {
256 | setContentView(R.layout.main);
257 | contentSet = true;
258 |
259 | send_button = (Button) findViewById(R.id.send);
260 | device_entry = (EditText) findViewById(R.id.device_entry);
261 | url_input = (EditText) findViewById(R.id.link_entry);
262 | account_display = (TextView) findViewById(R.id.account_label);
263 | throbber = (ProgressBar) findViewById(R.id.sendLinkThrobber);
264 |
265 |
266 | send_button.setOnClickListener(new View.OnClickListener() {
267 | public void onClick(View v) {
268 | link = url_input.getText().toString();
269 | receiver = device_entry.getText().toString();
270 | if(link == null || link.trim().equals("")) {
271 | popup = true;
272 | DialogFragment errorFragment = PostLinkNullLinkDialogFragment.newInstance();
273 | errorFragment.show(getSupportFragmentManager(), "dialog");
274 | } else if(receiver == null || receiver.trim().equals("")) {
275 | popup = true;
276 | DialogFragment errorFragment = PostLinkNullReceiverDialogFragment.newInstance();
277 | errorFragment.show(getSupportFragmentManager(), "dialog");
278 | } else {
279 | send_button.setVisibility(View.GONE);
280 | throbber.setVisibility(View.VISIBLE);
281 | sendLink();
282 | }
283 | }
284 | });
285 | }
286 | }
287 |
288 | private int checkAccount() {
289 | settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
290 | accounts_preferences = getSharedPreferences("android2cloud-accounts", 0);
291 |
292 | String account_name = settings.getString("account", "");
293 | account = new OAuthAccount(account_name, accounts_preferences);
294 | String[] accounts = OAuthAccount.getAccounts(accounts_preferences);
295 | if(account_name.equals("") || accounts.length == 0) {
296 | if(accounts.length == 1 && !account_name.equals("")){
297 | SharedPreferences.Editor settings_editor = settings.edit();
298 | settings_editor.putString("account", accounts[0]);
299 | settings_editor.commit();
300 | return ACCOUNT;
301 | } else if(accounts.length == 1 && accounts[0].equals("")){
302 | return NO_ACCOUNT;
303 | } else {
304 | return NO_ACCOUNT_SELECTED;
305 | }
306 | }
307 | return ACCOUNT;
308 | }
309 |
310 | private String getStringFromIntent(Intent intent) {
311 | return intent.getExtras().getString(Intent.EXTRA_TEXT);
312 | }
313 |
314 | private String getLinkFromString(String string) throws NoLinkFoundException, TooManyLinksException {
315 | Log.d("PostLinkActivity", "getLinkFromString: "+string);
316 | String regex = "\\b(\\w+)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]";
317 | Pattern patt = Pattern.compile(regex);
318 | Matcher matcher = patt.matcher(string);
319 | ArrayList matches = new ArrayList();
320 | while(matcher.find()){
321 | matches.add(matcher.group());
322 | Log.d("PostLinkActivity", "Match: "+matcher.group());
323 | }
324 | final CharSequence[] matches_cs = matches.toArray(new CharSequence[matches.size()]);
325 | if(matches.size() > 1) {
326 | throw new TooManyLinksException(matches_cs);
327 | }else if(matches.size() == 1){
328 | return (String) matches_cs[0];
329 | } else {
330 | throw new NoLinkFoundException();
331 | }
332 | }
333 | private void sendLink() {
334 | Intent intent = new Intent();
335 | intent.setComponent(new ComponentName("com.suchagit.android2cloud", "com.suchagit.android2cloud.HttpService"));
336 | intent.setAction("com.suchagit.android2cloud.AddLink");
337 | intent.putExtra("com.suchagit.android2cloud.result_receiver", mReceiver);
338 | intent.putExtra("com.suchagit.android2cloud.host", account.getHost());
339 | intent.putExtra("com.suchagit.android2cloud.oauth_token", account.getToken());
340 | intent.putExtra("com.suchagit.android2cloud.oauth_secret", account.getKey());
341 | intent.putExtra("com.suchagit.android2cloud.link", link);
342 | intent.putExtra("com.suchagit.android2cloud.receiver", receiver);
343 | intent.putExtra("com.suchagit.android2cloud.sender", "Android");
344 | startService(intent);
345 | SharedPreferences.Editor editor = settings.edit();
346 | editor.putString("receiver", receiver);
347 | editor.commit();
348 | }
349 |
350 | public void linkChosen(String chosenLink) {
351 | settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
352 | url_input.setText(chosenLink);
353 | if(settings.getBoolean("silent", false)) {
354 | link = chosenLink;
355 | sendLink();
356 | finish();
357 | }
358 | }
359 |
360 | public void checkHost(String host) throws DeprecatedHostException, CorruptedAccountException {
361 | if(host == null) {
362 | host = "";
363 | }
364 | String domain = host.replace("http://", "");
365 | domain = domain.replace("https://", "");
366 | domain = domain.replace("/", "");
367 | if(domain.equals("android2cloud.appspot.com")) {
368 | account.delete(accounts_preferences);
369 | throw new DeprecatedHostException(domain);
370 | } else if(domain.equals("error")) {
371 | account.delete(accounts_preferences);
372 | throw new CorruptedAccountException();
373 | }
374 | }
375 |
376 | @SuppressWarnings("serial")
377 | private class TooManyLinksException extends Exception {
378 | CharSequence[] matches;
379 |
380 | TooManyLinksException(CharSequence[] links) {
381 | this.matches = links;
382 | }
383 |
384 | private CharSequence[] getLinks() {
385 | return this.matches;
386 | }
387 | }
388 |
389 |
390 | @SuppressWarnings("serial")
391 | public class DeprecatedHostException extends Exception {
392 | String domain;
393 |
394 | DeprecatedHostException(String host) {
395 | this.domain = host;
396 | }
397 |
398 | public String getDomain() {
399 | return this.domain;
400 | }
401 | }
402 |
403 | @SuppressWarnings("serial")
404 | private class NoLinkFoundException extends Exception {
405 | // just defining the class
406 | // no real data needed
407 | }
408 |
409 | @SuppressWarnings("serial")
410 | private class CorruptedAccountException extends Exception {
411 | // just defining the class
412 | // no real data needed
413 | }
414 | }
--------------------------------------------------------------------------------
/src/com/suchagit/android2cloud/BillingService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.suchagit.android2cloud;
18 |
19 | import com.android.vending.billing.IMarketBillingService;
20 |
21 | import com.suchagit.android2cloud.Consts.PurchaseState;
22 | import com.suchagit.android2cloud.Consts.ResponseCode;
23 | import com.suchagit.android2cloud.util.Security;
24 | import com.suchagit.android2cloud.util.Security.VerifiedPurchase;
25 |
26 | import android.app.PendingIntent;
27 | import android.app.Service;
28 | import android.content.ComponentName;
29 | import android.content.Context;
30 | import android.content.Intent;
31 | import android.content.ServiceConnection;
32 | import android.os.Bundle;
33 | import android.os.IBinder;
34 | import android.os.RemoteException;
35 | import android.util.Log;
36 |
37 | import java.util.ArrayList;
38 | import java.util.HashMap;
39 | import java.util.LinkedList;
40 |
41 |
42 | /**
43 | * This class sends messages to Android Market on behalf of the application by
44 | * connecting (binding) to the MarketBillingService. The application
45 | * creates an instance of this class and invokes billing requests through this service.
46 | *
47 | * The {@link BillingReceiver} class starts this service to process commands
48 | * that it receives from Android Market.
49 | *
50 | * You should modify and obfuscate this code before using it.
51 | */
52 | public class BillingService extends Service implements ServiceConnection {
53 | private static final String TAG = "BillingService";
54 |
55 | /** The service connection to the remote MarketBillingService. */
56 | private static IMarketBillingService mService;
57 |
58 | /**
59 | * The list of requests that are pending while we are waiting for the
60 | * connection to the MarketBillingService to be established.
61 | */
62 | private static LinkedList mPendingRequests = new LinkedList();
63 |
64 | /**
65 | * The list of requests that we have sent to Android Market but for which we have
66 | * not yet received a response code. The HashMap is indexed by the
67 | * request Id that each request receives when it executes.
68 | */
69 | private static HashMap mSentRequests =
70 | new HashMap();
71 |
72 | /**
73 | * The base class for all requests that use the MarketBillingService.
74 | * Each derived class overrides the run() method to call the appropriate
75 | * service interface. If we are already connected to the MarketBillingService,
76 | * then we call the run() method directly. Otherwise, we bind
77 | * to the service and save the request on a queue to be run later when
78 | * the service is connected.
79 | */
80 | abstract class BillingRequest {
81 | private final int mStartId;
82 | protected long mRequestId;
83 |
84 | public BillingRequest(int startId) {
85 | mStartId = startId;
86 | }
87 |
88 | public int getStartId() {
89 | return mStartId;
90 | }
91 |
92 | /**
93 | * Run the request, starting the connection if necessary.
94 | * @return true if the request was executed or queued; false if there
95 | * was an error starting the connection
96 | */
97 | public boolean runRequest() {
98 | if (runIfConnected()) {
99 | return true;
100 | }
101 |
102 | if (bindToMarketBillingService()) {
103 | // Add a pending request to run when the service is connected.
104 | mPendingRequests.add(this);
105 | return true;
106 | }
107 | return false;
108 | }
109 |
110 | /**
111 | * Try running the request directly if the service is already connected.
112 | * @return true if the request ran successfully; false if the service
113 | * is not connected or there was an error when trying to use it
114 | */
115 | public boolean runIfConnected() {
116 | if (Consts.DEBUG) {
117 | Log.d(TAG, getClass().getSimpleName());
118 | }
119 | if (mService != null) {
120 | try {
121 | mRequestId = run();
122 | if (Consts.DEBUG) {
123 | Log.d(TAG, "request id: " + mRequestId);
124 | }
125 | if (mRequestId >= 0) {
126 | mSentRequests.put(mRequestId, this);
127 | }
128 | return true;
129 | } catch (RemoteException e) {
130 | onRemoteException(e);
131 | }
132 | }
133 | return false;
134 | }
135 |
136 | /**
137 | * Called when a remote exception occurs while trying to execute the
138 | * {@link #run()} method. The derived class can override this to
139 | * execute exception-handling code.
140 | * @param e the exception
141 | */
142 | protected void onRemoteException(RemoteException e) {
143 | Log.w(TAG, "remote billing service crashed");
144 | //error: billing_remote_exception_error
145 | mService = null;
146 | }
147 |
148 | /**
149 | * The derived class must implement this method.
150 | * @throws RemoteException
151 | */
152 | abstract protected long run() throws RemoteException;
153 |
154 | /**
155 | * This is called when Android Market sends a response code for this
156 | * request.
157 | * @param responseCode the response code
158 | */
159 | protected void responseCodeReceived(ResponseCode responseCode) {
160 | }
161 |
162 | protected Bundle makeRequestBundle(String method) {
163 | Bundle request = new Bundle();
164 | request.putString(Consts.BILLING_REQUEST_METHOD, method);
165 | request.putInt(Consts.BILLING_REQUEST_API_VERSION, 1);
166 | request.putString(Consts.BILLING_REQUEST_PACKAGE_NAME, getPackageName());
167 | return request;
168 | }
169 |
170 | protected void logResponseCode(String method, Bundle response) {
171 | ResponseCode responseCode = ResponseCode.valueOf(
172 | response.getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE));
173 | if (Consts.DEBUG) {
174 | Log.e(TAG, method + " received " + responseCode.toString());
175 | }
176 | }
177 | }
178 |
179 | /**
180 | * Wrapper class that checks if in-app billing is supported.
181 | */
182 | class CheckBillingSupported extends BillingRequest {
183 | public CheckBillingSupported() {
184 | // This object is never created as a side effect of starting this
185 | // service so we pass -1 as the startId to indicate that we should
186 | // not stop this service after executing this request.
187 | super(-1);
188 | }
189 |
190 | @Override
191 | protected long run() throws RemoteException {
192 | Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
193 | Bundle response = mService.sendBillingRequest(request);
194 | int responseCode = response.getInt(Consts.BILLING_RESPONSE_RESPONSE_CODE);
195 | if (Consts.DEBUG) {
196 | Log.i(TAG, "CheckBillingSupported response code: " +
197 | ResponseCode.valueOf(responseCode));
198 | }
199 | boolean billingSupported = (responseCode == ResponseCode.RESULT_OK.ordinal());
200 | ResponseHandler.checkBillingSupportedResponse(billingSupported);
201 | return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
202 | }
203 | }
204 |
205 | /**
206 | * Wrapper class that requests a purchase.
207 | */
208 | class RequestPurchase extends BillingRequest {
209 | public final String mProductId;
210 | public final String mDeveloperPayload;
211 |
212 | public RequestPurchase(String itemId) {
213 | this(itemId, null);
214 | }
215 |
216 | public RequestPurchase(String itemId, String developerPayload) {
217 | // This object is never created as a side effect of starting this
218 | // service so we pass -1 as the startId to indicate that we should
219 | // not stop this service after executing this request.
220 | super(-1);
221 | mProductId = itemId;
222 | mDeveloperPayload = developerPayload;
223 | }
224 |
225 | @Override
226 | protected long run() throws RemoteException {
227 | Bundle request = makeRequestBundle("REQUEST_PURCHASE");
228 | request.putString(Consts.BILLING_REQUEST_ITEM_ID, mProductId);
229 | // Note that the developer payload is optional.
230 | if (mDeveloperPayload != null) {
231 | request.putString(Consts.BILLING_REQUEST_DEVELOPER_PAYLOAD, mDeveloperPayload);
232 | }
233 | Bundle response = mService.sendBillingRequest(request);
234 | PendingIntent pendingIntent
235 | = response.getParcelable(Consts.BILLING_RESPONSE_PURCHASE_INTENT);
236 | if (pendingIntent == null) {
237 | Log.e(TAG, "Error with requestPurchase");
238 | return Consts.BILLING_RESPONSE_INVALID_REQUEST_ID;
239 | }
240 |
241 | Intent intent = new Intent();
242 | ResponseHandler.buyPageIntentResponse(pendingIntent, intent);
243 | return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
244 | Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
245 | }
246 |
247 | @Override
248 | protected void responseCodeReceived(ResponseCode responseCode) {
249 | ResponseHandler.responseCodeReceived(BillingService.this, this, responseCode);
250 | }
251 | }
252 |
253 | /**
254 | * Wrapper class that confirms a list of notifications to the server.
255 | */
256 | class ConfirmNotifications extends BillingRequest {
257 | final String[] mNotifyIds;
258 |
259 | public ConfirmNotifications(int startId, String[] notifyIds) {
260 | super(startId);
261 | mNotifyIds = notifyIds;
262 | }
263 |
264 | @Override
265 | protected long run() throws RemoteException {
266 | Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
267 | request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
268 | Bundle response = mService.sendBillingRequest(request);
269 | logResponseCode("confirmNotifications", response);
270 | return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
271 | Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
272 | }
273 | }
274 |
275 | /**
276 | * Wrapper class that sends a GET_PURCHASE_INFORMATION message to the server.
277 | */
278 | class GetPurchaseInformation extends BillingRequest {
279 | long mNonce;
280 | final String[] mNotifyIds;
281 |
282 | public GetPurchaseInformation(int startId, String[] notifyIds) {
283 | super(startId);
284 | mNotifyIds = notifyIds;
285 | }
286 |
287 | @Override
288 | protected long run() throws RemoteException {
289 | mNonce = Security.generateNonce();
290 |
291 | Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");
292 | request.putLong(Consts.BILLING_REQUEST_NONCE, mNonce);
293 | request.putStringArray(Consts.BILLING_REQUEST_NOTIFY_IDS, mNotifyIds);
294 | Bundle response = mService.sendBillingRequest(request);
295 | logResponseCode("getPurchaseInformation", response);
296 | return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
297 | Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
298 | }
299 |
300 | @Override
301 | protected void onRemoteException(RemoteException e) {
302 | super.onRemoteException(e);
303 | Security.removeNonce(mNonce);
304 | }
305 | }
306 |
307 | public BillingService() {
308 | super();
309 | }
310 |
311 | public void setContext(Context context) {
312 | attachBaseContext(context);
313 | }
314 |
315 | /**
316 | * We don't support binding to this service, only starting the service.
317 | */
318 | @Override
319 | public IBinder onBind(Intent intent) {
320 | return null;
321 | }
322 |
323 | @Override
324 | public void onStart(Intent intent, int startId) {
325 | handleCommand(intent, startId);
326 | }
327 |
328 | /**
329 | * The {@link BillingReceiver} sends messages to this service using intents.
330 | * Each intent has an action and some extra arguments specific to that action.
331 | * @param intent the intent containing one of the supported actions
332 | * @param startId an identifier for the invocation instance of this service
333 | */
334 | public void handleCommand(Intent intent, int startId) {
335 | String action = intent.getAction();
336 | if (Consts.DEBUG) {
337 | Log.i(TAG, "handleCommand() action: " + action);
338 | }
339 | if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
340 | String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID);
341 | confirmNotifications(startId, notifyIds);
342 | } else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
343 | String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
344 | getPurchaseInformation(startId, new String[] { notifyId });
345 | } else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
346 | String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
347 | String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
348 | purchaseStateChanged(startId, signedData, signature);
349 | } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
350 | long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
351 | int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
352 | ResponseCode.RESULT_ERROR.ordinal());
353 | ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
354 | checkResponseCode(requestId, responseCode);
355 | }
356 | }
357 |
358 | /**
359 | * Binds to the MarketBillingService and returns true if the bind
360 | * succeeded.
361 | * @return true if the bind succeeded; false otherwise
362 | */
363 | private boolean bindToMarketBillingService() {
364 | try {
365 | if (Consts.DEBUG) {
366 | Log.i(TAG, "binding to Market billing service");
367 | }
368 | boolean bindResult = bindService(
369 | new Intent(Consts.MARKET_BILLING_SERVICE_ACTION),
370 | this, // ServiceConnection.
371 | Context.BIND_AUTO_CREATE);
372 |
373 | if (bindResult) {
374 | return true;
375 | } else {
376 | Log.e(TAG, "Could not bind to service.");
377 | }
378 | } catch (SecurityException e) {
379 | Log.e(TAG, "Security exception: " + e);
380 | //error: billing_security_exception_error
381 | }
382 | return false;
383 | }
384 |
385 | /**
386 | * Checks if in-app billing is supported.
387 | * @return true if supported; false otherwise
388 | */
389 | public boolean checkBillingSupported() {
390 | return new CheckBillingSupported().runRequest();
391 | }
392 |
393 | /**
394 | * Requests that the given item be offered to the user for purchase. When
395 | * the purchase succeeds (or is canceled) the {@link BillingReceiver}
396 | * receives an intent with the action {@link Consts#ACTION_NOTIFY}.
397 | * Returns false if there was an error trying to connect to Android Market.
398 | * @param productId an identifier for the item being offered for purchase
399 | * @param developerPayload a payload that is associated with a given
400 | * purchase, if null, no payload is sent
401 | * @return false if there was an error connecting to Android Market
402 | */
403 | public boolean requestPurchase(String productId, String developerPayload) {
404 | return new RequestPurchase(productId, developerPayload).runRequest();
405 | }
406 |
407 | /**
408 | * Confirms receipt of a purchase state change. Each {@code notifyId} is
409 | * an opaque identifier that came from the server. This method sends those
410 | * identifiers back to the MarketBillingService, which ACKs them to the
411 | * server. Returns false if there was an error trying to connect to the
412 | * MarketBillingService.
413 | * @param startId an identifier for the invocation instance of this service
414 | * @param notifyIds a list of opaque identifiers associated with purchase
415 | * state changes.
416 | * @return false if there was an error connecting to Market
417 | */
418 | private boolean confirmNotifications(int startId, String[] notifyIds) {
419 | return new ConfirmNotifications(startId, notifyIds).runRequest();
420 | }
421 |
422 | /**
423 | * Gets the purchase information. This message includes a list of
424 | * notification IDs sent to us by Android Market, which we include in
425 | * our request. The server responds with the purchase information,
426 | * encoded as a JSON string, and sends that to the {@link BillingReceiver}
427 | * in an intent with the action {@link Consts#ACTION_PURCHASE_STATE_CHANGED}.
428 | * Returns false if there was an error trying to connect to the MarketBillingService.
429 | *
430 | * @param startId an identifier for the invocation instance of this service
431 | * @param notifyIds a list of opaque identifiers associated with purchase
432 | * state changes
433 | * @return false if there was an error connecting to Android Market
434 | */
435 | private boolean getPurchaseInformation(int startId, String[] notifyIds) {
436 | return new GetPurchaseInformation(startId, notifyIds).runRequest();
437 | }
438 |
439 | /**
440 | * Verifies that the data was signed with the given signature, and calls
441 | * {@link ResponseHandler#purchaseResponse(Context, PurchaseState, String, String, long)}
442 | * for each verified purchase.
443 | * @param startId an identifier for the invocation instance of this service
444 | * @param signedData the signed JSON string (signed, not encrypted)
445 | * @param signature the signature for the data, signed with the private key
446 | */
447 | private void purchaseStateChanged(int startId, String signedData, String signature) {
448 | ArrayList purchases;
449 | purchases = Security.verifyPurchase(signedData, signature);
450 | if (purchases == null) {
451 | return;
452 | }
453 |
454 | ArrayList notifyList = new ArrayList();
455 | for (VerifiedPurchase vp : purchases) {
456 | if (vp.notificationId != null) {
457 | notifyList.add(vp.notificationId);
458 | }
459 | ResponseHandler.purchaseResponse(this, vp.purchaseState, vp.productId,
460 | vp.orderId, vp.purchaseTime, vp.developerPayload);
461 | }
462 | if (!notifyList.isEmpty()) {
463 | String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
464 | confirmNotifications(startId, notifyIds);
465 | }
466 | }
467 |
468 | /**
469 | * This is called when we receive a response code from Android Market for a request
470 | * that we made. This is used for reporting various errors and for
471 | * acknowledging that an order was sent to the server. This is NOT used
472 | * for any purchase state changes. All purchase state changes are received
473 | * in the {@link BillingReceiver} and passed to this service, where they are
474 | * handled in {@link #purchaseStateChanged(int, String, String)}.
475 | * @param requestId a number that identifies a request, assigned at the
476 | * time the request was made to Android Market
477 | * @param responseCode a response code from Android Market to indicate the state
478 | * of the request
479 | */
480 | private void checkResponseCode(long requestId, ResponseCode responseCode) {
481 | BillingRequest request = mSentRequests.get(requestId);
482 | if (request != null) {
483 | if (Consts.DEBUG) {
484 | Log.d(TAG, request.getClass().getSimpleName() + ": " + responseCode);
485 | }
486 | request.responseCodeReceived(responseCode);
487 | }
488 | mSentRequests.remove(requestId);
489 | }
490 |
491 | /**
492 | * Runs any pending requests that are waiting for a connection to the
493 | * service to be established. This runs in the main UI thread.
494 | */
495 | private void runPendingRequests() {
496 | int maxStartId = -1;
497 | BillingRequest request;
498 | while ((request = mPendingRequests.peek()) != null) {
499 | if (request.runIfConnected()) {
500 | // Remove the request
501 | mPendingRequests.remove();
502 |
503 | // Remember the largest startId, which is the most recent
504 | // request to start this service.
505 | if (maxStartId < request.getStartId()) {
506 | maxStartId = request.getStartId();
507 | }
508 | } else {
509 | // The service crashed, so restart it. Note that this leaves
510 | // the current request on the queue.
511 | bindToMarketBillingService();
512 | return;
513 | }
514 | }
515 |
516 | // If we get here then all the requests ran successfully. If maxStartId
517 | // is not -1, then one of the requests started the service, so we can
518 | // stop it now.
519 | if (maxStartId >= 0) {
520 | if (Consts.DEBUG) {
521 | Log.i(TAG, "stopping service, startId: " + maxStartId);
522 | }
523 | stopSelf(maxStartId);
524 | }
525 | }
526 |
527 | /**
528 | * This is called when we are connected to the MarketBillingService.
529 | * This runs in the main UI thread.
530 | */
531 | public void onServiceConnected(ComponentName name, IBinder service) {
532 | if (Consts.DEBUG) {
533 | Log.d(TAG, "Billing service connected");
534 | }
535 | mService = IMarketBillingService.Stub.asInterface(service);
536 | runPendingRequests();
537 | }
538 |
539 | /**
540 | * This is called when we are disconnected from the MarketBillingService.
541 | */
542 | public void onServiceDisconnected(ComponentName name) {
543 | Log.w(TAG, "Billing service disconnected");
544 | mService = null;
545 | }
546 |
547 | /**
548 | * Unbinds from the MarketBillingService. Call this when the application
549 | * terminates to avoid leaking a ServiceConnection.
550 | */
551 | public void unbind() {
552 | try {
553 | unbindService(this);
554 | } catch (IllegalArgumentException e) {
555 | // This might happen if the service was disconnected
556 | }
557 | }
558 | }
559 |
--------------------------------------------------------------------------------