├── README.md
├── src
└── main
│ ├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ ├── ic_github.png
│ │ └── ic_launcher.png
│ ├── values
│ │ ├── loaders.xml
│ │ ├── styles.xml
│ │ ├── configs.xml
│ │ ├── dimens.xml
│ │ └── strings.xml
│ ├── xml
│ │ └── github_authenticator.xml
│ └── layout
│ │ ├── ac_single_frame.xml
│ │ └── fmt_login_form.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── elegion
│ │ ├── GitHubApp.java
│ │ ├── account
│ │ ├── GitHubAuthenticatorService.java
│ │ ├── GitHubAccount.java
│ │ └── GitHubAuthenticator.java
│ │ ├── utils
│ │ └── IOUtils.java
│ │ ├── activity
│ │ ├── LoginActivity.java
│ │ └── AccountListActivity.java
│ │ ├── fragment
│ │ ├── AccountList.java
│ │ └── LoginForm.java
│ │ └── loader
│ │ └── AuthTokenLoader.java
│ └── AndroidManifest.xml
├── .gitignore
└── proguard-rules.txt
/README.md:
--------------------------------------------------------------------------------
1 | ghsync
2 | ======
3 |
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elegion/ghsync/HEAD/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elegion/ghsync/HEAD/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elegion/ghsync/HEAD/src/main/res/drawable-xxhdpi/ic_github.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elegion/ghsync/HEAD/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/values/loaders.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/res/values/configs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | https://api.github.com/authorizations/clients/%1$s
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 | 300dp
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/xml/github_authenticator.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/src/main/res/layout/ac_single_frame.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/GitHubApp.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * @author Daniel Serdyukov
7 | */
8 | public class GitHubApp extends Application {
9 |
10 | public static final String CLIENT_ID = "0b5207d1529638a96f04";
11 |
12 | public static final String CLIENT_SECRET = "53a2f1d310e9703ecc1f58a3b7cf61ee478593ba";
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | GitHubSync
5 | GitHub
6 | Логин
7 | Пароль
8 | Войти
9 | Аккаунт уже существует
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/**
13 | gen/**
14 | out/**
15 | gen-external-apklibs/**
16 |
17 | # Local configuration file (sdk path, etc)
18 | local.properties
19 |
20 | # IntellijJ IDEA
21 | *.iml
22 | *.ipr
23 | *.iws
24 | .idea/**
25 | classes/**
26 | target/**
27 |
28 | # Mac OS
29 | .DS_Store
30 |
31 | # Gradle
32 | gradle-app.setting
33 | .gradletasknamecache
34 | .gradle/**
35 | build/**
36 |
37 | # Proguard
38 | proguard/**
39 | /.project
40 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/account/GitHubAuthenticatorService.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.account;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 |
7 | /**
8 | * @author Daniel Serdyukov
9 | */
10 | public class GitHubAuthenticatorService extends Service {
11 |
12 | private GitHubAuthenticator mAuthenticator;
13 |
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 | mAuthenticator = new GitHubAuthenticator(getApplicationContext());
18 | }
19 |
20 | @Override
21 | public IBinder onBind(Intent intent) {
22 | return mAuthenticator.getIBinder();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/account/GitHubAccount.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.account;
2 |
3 | import android.accounts.Account;
4 | import android.os.Parcel;
5 |
6 | /**
7 | * @author Daniel Serdyukov
8 | */
9 | public class GitHubAccount extends Account {
10 |
11 | public static final String TYPE = "com.github.elegion";
12 |
13 | public static final String TOKEN_FULL_ACCESS = "com.github.elegion.TOKEN_FULL_ACCESS";
14 |
15 | public static final String KEY_PASSWORD = "com.github.elegion.KEY_PASSWORD";
16 |
17 | public GitHubAccount(Parcel in) {
18 | super(in);
19 | }
20 |
21 | public GitHubAccount(String name) {
22 | super(name, TYPE);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the ProGuard
5 | # include property in project.properties.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/utils/IOUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.utils;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.Closeable;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.io.InputStreamReader;
9 | import java.io.Reader;
10 | import java.nio.charset.Charset;
11 |
12 | /**
13 | * @author Daniel Serdyukov
14 | */
15 | public final class IOUtils {
16 |
17 | public static final int EOF = -1;
18 |
19 | public static final int BUFFER_SIZE = 64 * 1024;
20 |
21 | private IOUtils() {
22 | }
23 |
24 | public static void closeQuietly(Closeable closeable) {
25 | try {
26 | closeable.close();
27 | } catch (IOException e) {
28 | Log.wtf(IOUtils.class.getSimpleName(), e);
29 | }
30 | }
31 |
32 | public static String toString(InputStream is) throws IOException {
33 | final StringBuilder result = new StringBuilder();
34 | final Reader reader = new InputStreamReader(is, Charset.defaultCharset());
35 | final char[] buffer = new char[BUFFER_SIZE];
36 | try {
37 | int bytes;
38 | while ((bytes = reader.read(buffer)) != EOF) {
39 | result.append(buffer, 0, bytes);
40 | }
41 | } finally {
42 | closeQuietly(reader);
43 | }
44 | return result.toString();
45 | }
46 |
47 | public static String toStringQuietly(InputStream is) {
48 | try {
49 | return toString(is);
50 | } catch (IOException e) {
51 | return "";
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/activity/LoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.activity;
2 |
3 | import android.accounts.Account;
4 | import android.accounts.AccountAuthenticatorActivity;
5 | import android.accounts.AccountManager;
6 | import android.os.Bundle;
7 |
8 | import com.github.elegion.R;
9 | import com.github.elegion.fragment.LoginForm;
10 |
11 | /**
12 | * @author Daniel Serdyukov
13 | */
14 | public class LoginActivity extends AccountAuthenticatorActivity {
15 |
16 | public static final String EXTRA_TOKEN_TYPE = "com.github.elegion.EXTRA_TOKEN_TYPE";
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.ac_single_frame);
22 | if (savedInstanceState == null) {
23 | getFragmentManager()
24 | .beginTransaction()
25 | .replace(R.id.frame1, new LoginForm())
26 | .commit();
27 | }
28 | }
29 |
30 | public void onTokenReceived(Account account, String password, String token) {
31 | final AccountManager am = AccountManager.get(this);
32 | final Bundle result = new Bundle();
33 | if (am.addAccountExplicitly(account, password, new Bundle())) {
34 | result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
35 | result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
36 | result.putString(AccountManager.KEY_AUTHTOKEN, token);
37 | am.setAuthToken(account, account.type, token);
38 | } else {
39 | result.putString(AccountManager.KEY_ERROR_MESSAGE, getString(R.string.account_already_exists));
40 | }
41 | setAccountAuthenticatorResult(result);
42 | setResult(RESULT_OK);
43 | finish();
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/res/layout/fmt_login_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
28 |
29 |
39 |
40 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/activity/AccountListActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.activity;
2 |
3 | import android.accounts.AccountManager;
4 | import android.accounts.AccountManagerCallback;
5 | import android.accounts.AccountManagerFuture;
6 | import android.accounts.AuthenticatorException;
7 | import android.accounts.OperationCanceledException;
8 | import android.app.Activity;
9 | import android.os.Bundle;
10 |
11 | import com.github.elegion.R;
12 | import com.github.elegion.account.GitHubAccount;
13 | import com.github.elegion.fragment.AccountList;
14 |
15 | import java.io.IOException;
16 |
17 | /**
18 | * @author Daniel Serdyukov
19 | */
20 | public class AccountListActivity extends Activity {
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.ac_single_frame);
26 | final AccountManager am = AccountManager.get(this);
27 | if (am.getAccountsByType(GitHubAccount.TYPE).length == 0) {
28 | addNewAccount(am);
29 | }
30 | if (savedInstanceState == null) {
31 | getFragmentManager()
32 | .beginTransaction()
33 | .add(R.id.frame1, new AccountList())
34 | .commit();
35 | }
36 | }
37 |
38 | private void addNewAccount(AccountManager am) {
39 | am.addAccount(GitHubAccount.TYPE, GitHubAccount.TOKEN_FULL_ACCESS, null, null, this,
40 | new AccountManagerCallback() {
41 | @Override
42 | public void run(AccountManagerFuture future) {
43 | try {
44 | future.getResult();
45 | } catch (OperationCanceledException | IOException | AuthenticatorException e) {
46 | AccountListActivity.this.finish();
47 | }
48 | }
49 | }, null
50 | );
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/fragment/AccountList.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.fragment;
2 |
3 | import android.accounts.Account;
4 | import android.accounts.AccountManager;
5 | import android.accounts.AccountManagerCallback;
6 | import android.accounts.AccountManagerFuture;
7 | import android.accounts.AuthenticatorException;
8 | import android.accounts.OnAccountsUpdateListener;
9 | import android.accounts.OperationCanceledException;
10 | import android.app.ListFragment;
11 | import android.os.Bundle;
12 | import android.os.Handler;
13 | import android.text.TextUtils;
14 | import android.util.Log;
15 | import android.view.View;
16 | import android.widget.ArrayAdapter;
17 | import android.widget.ListView;
18 |
19 | import com.github.elegion.account.GitHubAccount;
20 |
21 | import java.io.IOException;
22 |
23 | /**
24 | * @author Daniel Serdyukov
25 | */
26 | public class AccountList extends ListFragment implements OnAccountsUpdateListener {
27 |
28 | private final Handler mHandler = new Handler();
29 |
30 | private AccountManager mAccountManager;
31 |
32 | private ArrayAdapter mListAdapter;
33 |
34 | @Override
35 | public void onActivityCreated(Bundle savedInstanceState) {
36 | super.onActivityCreated(savedInstanceState);
37 | mAccountManager = AccountManager.get(getActivity());
38 | mListAdapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1);
39 | setListAdapter(mListAdapter);
40 | }
41 |
42 | @Override
43 | public void onResume() {
44 | super.onResume();
45 | mAccountManager.addOnAccountsUpdatedListener(this, mHandler, true);
46 | }
47 |
48 | @Override
49 | public void onPause() {
50 | mAccountManager.removeOnAccountsUpdatedListener(this);
51 | super.onPause();
52 | }
53 |
54 | @Override
55 | public void onAccountsUpdated(Account[] accounts) {
56 | mListAdapter.setNotifyOnChange(false);
57 | mListAdapter.clear();
58 | for (final Account account : accounts) {
59 | if (TextUtils.equals(account.type, GitHubAccount.TYPE)) {
60 | mListAdapter.add(account);
61 | }
62 | }
63 | mListAdapter.setNotifyOnChange(true);
64 | mListAdapter.notifyDataSetChanged();
65 | }
66 |
67 | @Override
68 | public void onListItemClick(ListView listView, View view, int position, long id) {
69 | final Account account = mListAdapter.getItem(position);
70 | mAccountManager.getAuthToken(account, GitHubAccount.TOKEN_FULL_ACCESS, new Bundle(), true,
71 | new AccountManagerCallback() {
72 | @Override
73 | public void run(AccountManagerFuture future) {
74 | try {
75 | final Bundle result = future.getResult();
76 | Log.d(AccountList.class.getSimpleName(), result.getString(AccountManager.KEY_AUTHTOKEN));
77 | } catch (OperationCanceledException | IOException | AuthenticatorException e) {
78 | Log.e(AccountList.class.getSimpleName(), e.getMessage(), e);
79 | }
80 | }
81 | }, null
82 | );
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/fragment/LoginForm.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.fragment;
2 |
3 | import android.app.Fragment;
4 | import android.app.LoaderManager;
5 | import android.content.Loader;
6 | import android.os.Bundle;
7 | import android.text.TextUtils;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.Button;
12 | import android.widget.EditText;
13 |
14 | import com.github.elegion.R;
15 | import com.github.elegion.account.GitHubAccount;
16 | import com.github.elegion.activity.LoginActivity;
17 | import com.github.elegion.loader.AuthTokenLoader;
18 |
19 | /**
20 | * @author Daniel Serdyukov
21 | */
22 | public class LoginForm extends Fragment implements LoaderManager.LoaderCallbacks, View.OnClickListener {
23 |
24 | private EditText mLogin;
25 |
26 | private EditText mPassword;
27 |
28 | private Button mSignInButton;
29 |
30 | @Override
31 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
32 | final View view = inflater.inflate(R.layout.fmt_login_form, container, false);
33 | mLogin = (EditText) view.findViewById(R.id.login);
34 | mPassword = (EditText) view.findViewById(R.id.password);
35 | mSignInButton = (Button) view.findViewById(R.id.btn_sign_in);
36 | return view;
37 | }
38 |
39 | @Override
40 | public void onActivityCreated(Bundle savedInstanceState) {
41 | super.onActivityCreated(savedInstanceState);
42 | }
43 |
44 | @Override
45 | public void onResume() {
46 | super.onResume();
47 | mSignInButton.setOnClickListener(this);
48 | }
49 |
50 | @Override
51 | public void onPause() {
52 | mSignInButton.setOnClickListener(null);
53 | super.onPause();
54 | }
55 |
56 | @Override
57 | public void onClick(View v) {
58 | if (v == mSignInButton) {
59 | if (TextUtils.isEmpty(mLogin.getText())) {
60 | mLogin.setError(getString(R.string.login));
61 | } else if (TextUtils.isEmpty(mPassword.getText())) {
62 | mPassword.setError(getString(R.string.password));
63 | } else {
64 | getLoaderManager().restartLoader(R.id.auth_token_loader, null, this);
65 | }
66 | }
67 | }
68 |
69 | @Override
70 | public Loader onCreateLoader(int id, Bundle args) {
71 | if (id == R.id.auth_token_loader) {
72 | return new AuthTokenLoader(
73 | getActivity().getApplicationContext(),
74 | mLogin.getText().toString(),
75 | mPassword.getText().toString()
76 | );
77 | }
78 | return null;
79 | }
80 |
81 | @Override
82 | public void onLoadFinished(Loader loader, String token) {
83 | if (loader.getId() == R.id.auth_token_loader && !TextUtils.isEmpty(token)) {
84 | ((LoginActivity) getActivity()).onTokenReceived(
85 | new GitHubAccount(mLogin.getText().toString()),
86 | mPassword.getText().toString(), token
87 | );
88 | }
89 | }
90 |
91 | @Override
92 | public void onLoaderReset(Loader loader) {
93 |
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/account/GitHubAuthenticator.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.account;
2 |
3 | import android.accounts.AbstractAccountAuthenticator;
4 | import android.accounts.Account;
5 | import android.accounts.AccountAuthenticatorResponse;
6 | import android.accounts.AccountManager;
7 | import android.accounts.NetworkErrorException;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.os.Bundle;
11 | import android.text.TextUtils;
12 |
13 | import com.github.elegion.activity.LoginActivity;
14 | import com.github.elegion.loader.AuthTokenLoader;
15 |
16 | /**
17 | * @author Daniel Serdyukov
18 | */
19 | public class GitHubAuthenticator extends AbstractAccountAuthenticator {
20 |
21 | private final Context mContext;
22 |
23 | public GitHubAuthenticator(Context context) {
24 | super(context);
25 | mContext = context;
26 | }
27 |
28 | @Override
29 | public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
30 | return null;
31 | }
32 |
33 | @Override
34 | public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
35 | String[] requiredFeatures, Bundle options)
36 | throws NetworkErrorException {
37 | final Intent intent = new Intent(mContext, LoginActivity.class);
38 | intent.putExtra(LoginActivity.EXTRA_TOKEN_TYPE, accountType);
39 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
40 | final Bundle bundle = new Bundle();
41 | if (options != null) {
42 | bundle.putAll(options);
43 | }
44 | bundle.putParcelable(AccountManager.KEY_INTENT, intent);
45 | return bundle;
46 | }
47 |
48 | @Override
49 | public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
50 | throws NetworkErrorException {
51 | return null;
52 | }
53 |
54 | @Override
55 | public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType,
56 | Bundle options) throws NetworkErrorException {
57 | final Bundle result = new Bundle();
58 | final AccountManager am = AccountManager.get(mContext.getApplicationContext());
59 | String authToken = am.peekAuthToken(account, authTokenType);
60 | if (TextUtils.isEmpty(authToken)) {
61 | final String password = am.getPassword(account);
62 | if (!TextUtils.isEmpty(password)) {
63 | authToken = AuthTokenLoader.signIn(mContext, account.name, password);
64 | }
65 | }
66 | if (!TextUtils.isEmpty(authToken)) {
67 | result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
68 | result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
69 | result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
70 | } else {
71 | final Intent intent = new Intent(mContext, LoginActivity.class);
72 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
73 | intent.putExtra(LoginActivity.EXTRA_TOKEN_TYPE, authTokenType);
74 | final Bundle bundle = new Bundle();
75 | bundle.putParcelable(AccountManager.KEY_INTENT, intent);
76 | }
77 | return result;
78 | }
79 |
80 | @Override
81 | public String getAuthTokenLabel(String authTokenType) {
82 | return null;
83 | }
84 |
85 | @Override
86 | public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType,
87 | Bundle options) throws NetworkErrorException {
88 | return null;
89 | }
90 |
91 | @Override
92 | public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)
93 | throws NetworkErrorException {
94 | return null;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/github/elegion/loader/AuthTokenLoader.java:
--------------------------------------------------------------------------------
1 | package com.github.elegion.loader;
2 |
3 | import android.content.AsyncTaskLoader;
4 | import android.content.Context;
5 | import android.text.TextUtils;
6 | import android.util.Base64;
7 | import android.util.Log;
8 |
9 | import com.github.elegion.GitHubApp;
10 | import com.github.elegion.R;
11 | import com.github.elegion.utils.IOUtils;
12 |
13 | import org.json.JSONArray;
14 | import org.json.JSONException;
15 | import org.json.JSONObject;
16 |
17 | import java.io.BufferedInputStream;
18 | import java.io.BufferedOutputStream;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.io.OutputStream;
22 | import java.net.HttpURLConnection;
23 | import java.net.URL;
24 | import java.util.Arrays;
25 |
26 | /**
27 | * @author Daniel Serdyukov
28 | */
29 | public class AuthTokenLoader extends AsyncTaskLoader {
30 |
31 | private final String mObtainTokenUrl;
32 |
33 | private final String mLogin;
34 |
35 | private final String mPassword;
36 |
37 | private String mAuthToken;
38 |
39 | public AuthTokenLoader(Context context, String login, String password) {
40 | super(context);
41 | mObtainTokenUrl = context.getString(R.string.github_obtain_token_url, GitHubApp.CLIENT_ID);
42 | mLogin = login;
43 | mPassword = password;
44 | }
45 |
46 | public static String signIn(Context context, String login, String password) {
47 | try {
48 | return new AuthTokenLoader(context, login, password).signIn();
49 | } catch (IOException e) {
50 | Log.e(AuthTokenLoader.class.getSimpleName(), e.getMessage(), e);
51 | }
52 | return null;
53 | }
54 |
55 | @Override
56 | protected void onStartLoading() {
57 | if (TextUtils.isEmpty(mAuthToken)) {
58 | forceLoad();
59 | } else {
60 | deliverResult(mAuthToken);
61 | }
62 | }
63 |
64 | @Override
65 | public void deliverResult(String data) {
66 | mAuthToken = data;
67 | super.deliverResult(data);
68 | }
69 |
70 | @Override
71 | public String loadInBackground() {
72 | try {
73 | return signIn();
74 | } catch (IOException e) {
75 | Log.e(AuthTokenLoader.class.getSimpleName(), e.getMessage(), e);
76 | }
77 | return null;
78 | }
79 |
80 | private String signIn() throws IOException {
81 | final HttpURLConnection cn = (HttpURLConnection) new URL(mObtainTokenUrl).openConnection();
82 | cn.setRequestMethod("PUT");
83 | cn.addRequestProperty("Accept", "application/json");
84 | cn.addRequestProperty("Authorization", "Basic " +
85 | Base64.encodeToString((mLogin + ":" + mPassword).getBytes(), Base64.DEFAULT));
86 | sendBody(cn);
87 | return readToken(cn);
88 | }
89 |
90 | private void sendBody(HttpURLConnection cn) throws IOException {
91 | final JSONObject body = new JSONObject();
92 | try {
93 | body.put("client_secret", GitHubApp.CLIENT_SECRET);
94 | body.put("scopes", new JSONArray(Arrays.asList("repo")));
95 | final byte[] data = body.toString().getBytes();
96 | cn.setDoOutput(true);
97 | cn.setFixedLengthStreamingMode(data.length);
98 | cn.setRequestProperty("Content-Type", "application/json");
99 | final OutputStream out = new BufferedOutputStream(cn.getOutputStream());
100 | try {
101 | out.write(data);
102 | } finally {
103 | IOUtils.closeQuietly(out);
104 | }
105 | } catch (JSONException e) {
106 | Log.e(AuthTokenLoader.class.getSimpleName(), e.getMessage(), e);
107 | }
108 | }
109 |
110 | private String readToken(HttpURLConnection cn) throws IOException {
111 | final InputStream in = new BufferedInputStream(cn.getInputStream());
112 | try {
113 | final JSONObject json = new JSONObject(IOUtils.toStringQuietly(in));
114 | if (json.has("token")) {
115 | return json.getString("token");
116 | }
117 | } catch (JSONException e) {
118 | Log.e(AuthTokenLoader.class.getSimpleName(), e.getMessage(), e);
119 | } finally {
120 | IOUtils.closeQuietly(in);
121 | }
122 | return null;
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------