Hey guys im trying to add an account with androids accountManager, I keep getting this stack trace below.
Guys I cant post all of my code cause I break the limit that Stack Overflow allows me to enter. so ill only post snippets of code you request cause there is WAY more code for this. Sorry for the messy code im just messing around with it till I can get it to work then ill clean it up.
FATAL EXCEPTION: main
Process: com.example.rapid.rapid, PID: 6168
java.lang.SecurityException: uid 10335 cannot explicitly add accounts of type: com.example.rapid.rapid
at android.os.Parcel.readException(Parcel.java:1620)
at android.os.Parcel.readException(Parcel.java:1573)
at android.accounts.IAccountManager$Stub$Proxy.addAccountExplicitly(IAccountManager.java:890)
at android.accounts.AccountManager.addAccountExplicitly(AccountManager.java:716)
at com.example.rapid.rapid.LoginActivity$1$1.onResponse(LoginActivity.java:174)
at com.example.rapid.rapid.LoginActivity$1$1.onResponse(LoginActivity.java:140)
at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:60)
at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:30)
at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7237)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
LoginActivity.java
public class LoginActivity extends Activity {
private static final String TAG = "LoginActivity";
public final static String ARG_ACCOUNT_TYPE = "com.example.rapid.rapid";
public final static String ARG_AUTH_TYPE = "AUTH_TYPE";
public final static String ARG_ACCOUNT_NAME = "com.example.rapid.rapid";
public final static String ARG_IS_ADDING_NEW_ACCOUNT = "IS_ADDING_ACCOUNT";
public static final String KEY_ERROR_MESSAGE = "ERR_MSG";
public final static String PARAM_USER_PASS = "USER_PASS";
private static final int REQUEST_SIGNUP = 0;
private AccountManager mAccountManager;
public static final String ACCOUNT_TYPE = "com.example.rapid.rapid";
private static final String CONTENT_AUTHORITY = "com.example.rapid.rapid";
private static final String PREF_SETUP_COMPLETE = "setup_complete";
private static final long SYNC_FREQUENCY = 60 * 60; // 1 hour (in seconds)
private String mAuthTokenType;
private boolean mInvalidate;
private AlertDialog mAlertDialog;
#InjectView(R.id.loginEmailWrapper)
TextInputLayout _loginEmailWrapper;
#InjectView(R.id.loginPasswordWrapper)
TextInputLayout _loginPasswordWrapper;
#InjectView(R.id.loginEmailInput)
EditText _loginEmailInput;
#InjectView(R.id.loginPasswordInput)
EditText _loginPasswordInput;
#InjectView(R.id.loginPasswordVisibility)
ImageView _loginPasswordVisibility;
#InjectView(R.id.btn_login)
Button _loginButton;
#InjectView(R.id.link_signup)
TextView _signupLink;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*Uncomment this to make this screen of the app fullscreen.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);*/
setContentView(R.layout.activity_login);
ButterKnife.inject(this);
mAccountManager = AccountManager.get(this);
boolean setupComplete = PreferenceManager
.getDefaultSharedPreferences(this.getApplicationContext()).getBoolean(PREF_SETUP_COMPLETE, false);
String accountName = getIntent().getStringExtra(ARG_ACCOUNT_NAME);
mAuthTokenType = getIntent().getStringExtra(ARG_AUTH_TYPE);
if (mAuthTokenType == null)
mAuthTokenType = AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS;
if (accountName != null) {
_loginEmailInput.setText(accountName);
}
_loginButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Begin Login process...");
showAccountPicker(mAuthTokenType, false);
if (!validate()) {
onLoginFailed();
return;
}
final String email = _loginEmailInput.getText().toString();
final String password = _loginPasswordInput.getText().toString();
final String accountType = getIntent().getStringExtra(ARG_ACCOUNT_TYPE);
_loginButton.setEnabled(false);
final ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this,
R.style.Theme_IAPTheme);
progressDialog.setIndeterminate(true);
progressDialog.setMessage("Authenticating...");
progressDialog.show();
// Response received from the server
Response.Listener<String> responseListener = new Response.Listener<String>() {
#Override
public void onResponse(String response) {
String authtoken = null;
boolean newAccount = false;
try {
Log.i("tagconvertstr", "[" + response + "]");
JSONObject jsonResponse = new JSONObject(response);
boolean success = jsonResponse.getBoolean("success");
if (success) {
String trainer_name = jsonResponse.getString("trainer_name");
authtoken = jsonResponse.getString("token");
//String name = jsonResponse.getString("name");
//Intent intent = new Intent(LoginActivity.this, UserHomeActivity.class);
//intent.putExtra("name", name);
//intent.putExtra("username", username);
//LoginActivity.this.startActivity(intent);
//Intent intent = new Intent(LoginActivity.this, UserHomeActivity.class);
//LoginActivity.this.startActivity(intent);
//startActivityForResult(intent, 1);
String accountName = AccountManager.KEY_ACCOUNT_NAME;
String accountPassword = password;
//final Account account = new Account(email, "com.example.rapid.rapid");
if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, true)) {
Log.d("rapid", TAG + "> finishLogin > addAccountExplicitly");
authtoken = AccountManager.KEY_AUTHTOKEN;
String authtokenType = mAuthTokenType;
Account account = rapidAuthenticatorService.GetAccount(ACCOUNT_TYPE);
AccountManager accountManager =
(AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
if (accountManager.addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(
account, CONTENT_AUTHORITY, new Bundle(),SYNC_FREQUENCY);
newAccount = true;
}
if (newAccount) {
TriggerRefresh();
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit()
.putBoolean(PREF_SETUP_COMPLETE, true).commit();
}
Log.d("rapid", TAG + "> ALL SETUP!");
// Creating the account on the device and setting the auth token we got
// (Not setting the auth token will cause another call to the server to authenticate the user)
//mAccountManager.addAccountExplicitly(account, null, null);
//mAccountManager.setAuthToken(account, authtokenType, authtoken);
} else {
Log.d("rapid", TAG + "> finishLogin > setPassword");
//mAccountManager.setPassword(account, accountPassword);
Log.d("rapid", TAG + "> done setting account password");
}
//setAccountAuthenticatorResult(intent.getExtras());
//setResult(RESULT_OK, intent);
Toast.makeText(getBaseContext(), "Login Successful", Toast.LENGTH_LONG).show();
Intent intent = new Intent(LoginActivity.this, UserHomeActivity.class);
intent.putExtra("trainer_name", trainer_name);
startActivity(intent);
} else {
progressDialog.dismiss();
onLoginFailed();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
};
LoginRequest loginRequest = new LoginRequest(email, password, responseListener);
RequestQueue queue = Volley.newRequestQueue(LoginActivity.this);
queue.add(loginRequest);
}
});
_loginPasswordInput.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//_registerPasswordVisibility.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE);
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
//_registerPasswordVisibility.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE);
//_trainerNameWrapper.setBackgroundColor(Color.parseColor("#0000ff"));
}
});
_loginPasswordVisibility.setOnTouchListener(mPasswordVisibleTouchListener);
_signupLink.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Start the Signup activity
Intent intent = new Intent(getApplicationContext(), RegisterActivity.class);
startActivityForResult(intent, REQUEST_SIGNUP);
}
});
}
public static void TriggerRefresh() {
Bundle b = new Bundle();
// Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
rapidAuthenticatorService.GetAccount(ACCOUNT_TYPE), // Sync account
CONTENT_AUTHORITY, // Content authority
b); // Extras
}
private View.OnTouchListener mPasswordVisibleTouchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
final boolean isOutsideView = event.getX() < 0 ||
event.getX() > v.getWidth() ||
event.getY() < 0 ||
event.getY() > v.getHeight();
// change input type will reset cursor position, so we want to save it
final int cursor = _loginPasswordInput.getSelectionStart();
if (isOutsideView || MotionEvent.ACTION_UP == event.getAction())
_loginPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD);
else
_loginPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
_loginPasswordInput.setSelection(cursor);
return true;
}
};
/**
* Show all the accounts registered on the account manager. Request an auth token upon user select.
*
* #param authTokenType
*/
private void showAccountPicker(final String authTokenType, final boolean invalidate) {
mInvalidate = invalidate;
final Account availableAccounts[] = mAccountManager.getAccountsByType(AccountGeneral.ACCOUNT_TYPE);
if (availableAccounts.length == 0) {
Toast.makeText(this, "No accounts", Toast.LENGTH_SHORT).show();
} else {
String name[] = new String[availableAccounts.length];
for (int i = 0; i < availableAccounts.length; i++) {
name[i] = availableAccounts[i].name;
}
// Account picker
mAlertDialog = new AlertDialog.Builder(this).setTitle("Pick Account").setAdapter(new ArrayAdapter<String>(getBaseContext(), android.R.layout.simple_list_item_1, name), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if (invalidate)
invalidateAuthToken(availableAccounts[which], authTokenType);
else
getExistingAccountAuthToken(availableAccounts[which], authTokenType);
}
}).create();
mAlertDialog.show();
}
}
/**
* Get the auth token for an existing account on the AccountManager
*
* #param account
* #param authTokenType
*/
private void getExistingAccountAuthToken(Account account, String authTokenType) {
final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account, authTokenType, null, this, null, null);
new Thread(new Runnable() {
#Override
public void run() {
try {
Bundle bnd = future.getResult();
final String authtoken = bnd.getString(AccountManager.KEY_AUTHTOKEN);
showMessage((authtoken != null) ? "SUCCESS!\ntoken: " + authtoken : "FAIL");
Log.d("udinic", "GetToken Bundle is " + bnd);
} catch (Exception e) {
e.printStackTrace();
showMessage(e.getMessage());
}
}
}).start();
}
/**
* Invalidates the auth token for the account
*
* #param account
* #param authTokenType
*/
private void invalidateAuthToken(final Account account, String authTokenType) {
final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account, authTokenType, null, this, null, null);
new Thread(new Runnable() {
#Override
public void run() {
try {
Bundle bnd = future.getResult();
final String authtoken = bnd.getString(AccountManager.KEY_AUTHTOKEN);
mAccountManager.invalidateAuthToken(account.type, authtoken);
showMessage(account.name + " invalidated");
} catch (Exception e) {
e.printStackTrace();
showMessage(e.getMessage());
}
}
}).start();
}
/**
* Get an auth token for the account.
* If not exist - add it and then return its auth token.
* If one exist - return its auth token.
* If more than one exists - show a picker and return the select account's auth token.
*
* #param accountType
* #param authTokenType
*/
private void getTokenForAccountCreateIfNeeded(String accountType, String authTokenType) {
final AccountManagerFuture<Bundle> future = mAccountManager.getAuthTokenByFeatures(accountType, authTokenType, null, this, null, null,
new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> future) {
Bundle bnd = null;
try {
bnd = future.getResult();
final String authtoken = bnd.getString(AccountManager.KEY_AUTHTOKEN);
showMessage(((authtoken != null) ? "SUCCESS!\ntoken: " + authtoken : "FAIL"));
Log.d("udinic", "GetTokenForAccount Bundle is " + bnd);
} catch (Exception e) {
e.printStackTrace();
showMessage(e.getMessage());
}
}
}
, null);
}
private void showMessage(final String msg) {
if (TextUtils.isEmpty(msg))
return;
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getBaseContext(), msg, Toast.LENGTH_SHORT).show();
}
});
}
}
rapidAuthenticator.java
public class rapidAuthenticator extends AbstractAccountAuthenticator {
private String TAG = "rapidAuthenticator";
private final Context mContext;
public rapidAuthenticator(Context context) {
super(context);
// I hate you! Google - set mContext as protected!
this.mContext = context;
}
#Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
Log.d("rapid", TAG + "> addAccount");
final Intent intent = new Intent(mContext, LoginActivity.class);
intent.putExtra(LoginActivity.ARG_ACCOUNT_TYPE, accountType);
intent.putExtra(LoginActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(LoginActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
Log.d("udinic", TAG + "> getAuthToken");
// If the caller requested an authToken type we don't support, then
// return an error
if (!authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY) && !authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
return result;
}
// Extract the username and password from the Account Manager, and ask
// the server for an appropriate AuthToken.
final AccountManager am = AccountManager.get(mContext);
String authToken = am.peekAuthToken(account, authTokenType);
Log.d("udinic", TAG + "> peekAuthToken returned - " + authToken);
// Lets give another try to authenticate the user
if (TextUtils.isEmpty(authToken)) {
final String password = am.getPassword(account);
if (password != null) {
try {
Log.d("udinic", TAG + "> re-authenticating with the existing password");
authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// If we get an authToken - we return it
if (!TextUtils.isEmpty(authToken)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return result;
}
// If we get here, then we couldn't access the user's password - so we
// need to re-prompt them for their credentials. We do that by creating
// an intent to display our AuthenticatorActivity.
final Intent intent = new Intent(mContext, LoginActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(LoginActivity.ARG_ACCOUNT_TYPE, account.type);
intent.putExtra(LoginActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(LoginActivity.ARG_ACCOUNT_NAME, account.name);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
#Override
public String getAuthTokenLabel(String authTokenType) {
if (AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS.equals(authTokenType))
return AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS_LABEL;
else if (AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY.equals(authTokenType))
return AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY_LABEL;
else
return authTokenType + " (Label)";
}
#Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
final Bundle result = new Bundle();
result.putBoolean(KEY_BOOLEAN_RESULT, false);
return result;
}
#Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
#Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
return null;
}
#Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}
}
Manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rapid.rapid">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<application>
<service android:name="com.example.rapid.rapid.rapidAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="#xml/authenticator" />
</service>
</application>
</manifest>
Authenticator.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.example.rapid.rapid"
android:icon="#drawable/logo"
android:smallIcon="#drawable/logo"
android:label="rapid"
android:accountPreferences="#xml/prefs"/>
</selector>
As exception says, caller uid is different than the authenticator's uid. To add a account explicitly, caller and authenticator's uid should be same.
This should be same as your app id, i.e package name.
android:accountType="com.example.rapid.rapid"
Android Developer Documentation
This method requires the caller to have a signature match with the
authenticator that owns the specified account.
I had a similar problem, but got it fixed after I restarted the device. It might help to try it out.
Related
here's my problem :
I have a programme that need a password to work so I thought I could make an AccountManager. I took the code from this tutorial and it works just fine : I have a new account in setting->accounts !
BUT to test if it was safe I did another programme (lets call it Prog2 and the firt one Prog1) with the same code and supprise, I have full access to the password of the account I created with Prog2 with Prog1.
I know that it's possible to secure the account cause I tried with google and facebook accounts and I could not have access to their password.
Here's my code :
PS : if at the end of a String there is a '1' it's because I changed it in Prog1 and not in Prog2 to test if these variable had some effect
Authenticator
public class Authenticator extends AbstractAccountAuthenticator {
private String TAG = "CoderzHeavenAuthenticator";
private final Context mContext;
public Authenticator(Context context) {
super(context);
// I hate you! Google - set mContext as protected!
this.mContext = context;
}
#Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
Log.d("CoderzHeaven", TAG + "> addAccount");
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(AuthenticatorActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
Log.d("CoderzHeaven", TAG + "> getAuthToken");
// If the caller requested an authToken type we don't support, then
// return an error
if (!authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY) && !authTokenType.equals(AUTHTOKEN_TYPE_FULL_ACCESS)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
return result;
}
// Extract the username and password from the Account Manager, and ask
// the server for an appropriate AuthToken.
final AccountManager am = AccountManager.get(mContext);
String authToken = am.peekAuthToken(account, authTokenType);
Log.d("CoderzHeaven", TAG + "> peekAuthToken returned - " + authToken);
// Lets give another try to authenticate the user
if (TextUtils.isEmpty(authToken)) {
final String password = am.getPassword(account);
if (password != null) {
try {
Log.d("CoderzHeaven", TAG + "> re-authenticating with the existing password");
//authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// If we get an authToken - we return it
if (!TextUtils.isEmpty(authToken)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return result;
}
// If we get here, then we couldn't access the user's password - so we
// need to re-prompt them for their credentials. We do that by creating
// an intent to display our AuthenticatorActivity.
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);
intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_NAME, account.name);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
#Override
public String getAuthTokenLabel(String authTokenType) {
if (AUTHTOKEN_TYPE_FULL_ACCESS.equals(authTokenType))
return AUTHTOKEN_TYPE_FULL_ACCESS_LABEL;
else if (AUTHTOKEN_TYPE_READ_ONLY.equals(authTokenType))
return AUTHTOKEN_TYPE_READ_ONLY_LABEL;
else
return authTokenType + " (Label)";
}
#Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
final Bundle result = new Bundle();
result.putBoolean(KEY_BOOLEAN_RESULT, false);
return result;
}
#Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}
#Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
return null;
}
#Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}
}
AuthenticatorService
public class AuthenticatorService extends Service {
private Authenticator authenticator;
public AuthenticatorService() {
super();
}
public IBinder onBind(Intent intent) {
IBinder ret = null;
if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT))
ret = getAuthenticator().getIBinder();
return ret;
}
private Authenticator getAuthenticator() {
if (authenticator == null)
authenticator = new Authenticator(this);
return authenticator;
}
}
AuthenticatorActivity
public class AuthenticatorActivity extends AccountAuthenticatorActivity implements OnClickListener{
public final static String ARG_ACCOUNT_TYPE = "ACCOUNT_TYPE1";
public final static String ARG_AUTH_TYPE = "AUTH_TYPE1";
public final static String ARG_ACCOUNT_NAME = "ACCOUNT_NAME1";
public final static String ARG_IS_ADDING_NEW_ACCOUNT = "IS_ADDING_ACCOUNT1";
public static final String KEY_ERROR_MESSAGE = "ERR_MSG1";
public final static String PARAM_USER_PASS = "USER_PASS1";
private final String TAG = this.getClass().getSimpleName();
private AccountManager mAccountManager;
private String mAuthTokenType;
String authtoken = "12345678910"; // this
String password = "1234510";
String accountName;
public Account findAccount(String accountName) {
for (Account account : mAccountManager.getAccounts())
if (TextUtils.equals(account.name, accountName) && TextUtils.equals(account.type, getString(R.string.auth_type))) {
System.out.println("FOUND");
return account;
}
return null;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_login);
Log.d(TAG, "onCreate");
mAccountManager = AccountManager.get(getBaseContext());
// If this is a first time adding, then this will be null
accountName = getIntent().getStringExtra(ARG_ACCOUNT_NAME);
mAuthTokenType = getIntent().getStringExtra(ARG_AUTH_TYPE);
if (mAuthTokenType == null)
mAuthTokenType = getString(R.string.auth_type);
findAccount(accountName);
System.out.println(mAuthTokenType + ", accountName : " + accountName);
((Button)findViewById(R.id.submit)).setOnClickListener(this);
}
void userSignIn() {
// You should probably call your server with user credentials and get
// the authentication token here.
// For demo, I have hard-coded it.
authtoken = "12345678910";
accountName = ((EditText) findViewById(R.id.accountName)).getText().toString().trim();
password = ((EditText) findViewById(R.id.accountPassword)).getText().toString().trim();
if (accountName.length() > 0) {
Bundle data = new Bundle();
data.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
data.putString(AccountManager.KEY_ACCOUNT_TYPE, mAuthTokenType);
data.putString(AccountManager.KEY_AUTHTOKEN, authtoken);
data.putString(PARAM_USER_PASS, password);
// Some extra data about the user
Bundle userData = new Bundle();
userData.putString("UserID", "25");
data.putBundle(AccountManager.KEY_USERDATA, userData);
//Make it an intent to be passed back to the Android Authenticator
final Intent res = new Intent();
res.putExtras(data);
//Create the new account with Account Name and TYPE
final Account account = new Account(accountName, mAuthTokenType);
//Add the account to the Android System
if (mAccountManager.addAccountExplicitly(account, password, userData)) {
// worked
Log.d(TAG, "Account added");
mAccountManager.setAuthToken(account, mAuthTokenType, authtoken);
setAccountAuthenticatorResult(data);
setResult(RESULT_OK, res);
finish();
} else {
// guess not
Log.d(TAG, "Account NOT added");
}
}
}
#Override
public void onClick(View v) {
userSignIn();
}
}
AccountGeneral
public class AccountGeneral {
/**
* Account name
*/
public static final String ACCOUNT_NAME = "CoderzHeaven1";
/**
* Auth token types
*/
public static final String AUTHTOKEN_TYPE_READ_ONLY = "Read only1";
public static final String AUTHTOKEN_TYPE_READ_ONLY_LABEL = "Read only access to an CoderzHeaven account1";
public static final String AUTHTOKEN_TYPE_FULL_ACCESS = "Full access1";
public static final String AUTHTOKEN_TYPE_FULL_ACCESS_LABEL = "Full access to an CoderzHeaven account1";
}
As the Google documentation tell,AccountManager is not an encryption service.
See here
It's betcome an isue with rooted device. Or "you should store a cryptographically secure token that would be of limited use to an attacker" (from Google documentation)
Read this too
EDIT :
If you have access with your Program2 it's because you use (or not at all) the same keystore to sign your apk. An application with the same signature can access to the accountManager field
I very bad understand the authentication process on Android. I write a custom authenticator:
public class Authenticator extends AbstractAccountAuthenticator {
private final Context mContext;
public Authenticator(Context context) {
super(context);
mContext = context;
}
#Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType,
String authTokenType,
String[] requiredFeatures,
Bundle options) throws NetworkErrorException {
final Intent intent = new Intent(mContext, AuthActivity.class);
intent.putExtra(AuthActivity.KEY_ACCOUNT_TYPE, accountType);
intent.putExtra(AuthActivity.KEY_AUTH_TYPE, authTokenType);
intent.putExtra(AuthActivity.KEY_ADDING_NEW_ACCOUNT, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
#Override
public Bundle getAuthToken(AccountAuthenticatorResponse response,
Account account,
String authTokenType,
Bundle options) throws NetworkErrorException {
final AccountManager am = AccountManager.get(mContext);
String authToken = am.peekAuthToken(account, authTokenType);
if (TextUtils.isEmpty(authToken)) {
final String password = am.getPassword(account);
if (password != null) {
//authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);
}
} else {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
return result;
}
final Intent intent = new Intent(mContext, AuthActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
intent.putExtra(AuthActivity.KEY_ACCOUNT_TYPE, account.type);
intent.putExtra(AuthActivity.KEY_AUTH_TYPE, authTokenType);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
}
I didn't write stub methods.
After I try to register a new account:
AccountManager accountManager = AccountManager.get(MainActivity.this);
Account account = new Account("username", vcs.getAccountType());
accountManager.getAuthToken(account, "oauth", null, MainActivity.this,
new AccountManagerCallback<Bundle>() {
#Override
public void run(AccountManagerFuture<Bundle> future) {}
},
new Handler(new Handler.Callback() {
#Override
public boolean handleMessage(Message msg) {
return false;
}
}));
All works and I got token from server but... How to return this token in the account manager from Authentication activity?
It's my AuthActivity code:
public class AuthActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = AuthActivity.class.getSimpleName();
public static final String KEY_ACCOUNT_TYPE = "account_type";
public static final String KEY_AUTH_TYPE = "auth_type";
public static final String KEY_ADDING_NEW_ACCOUNT = "new_account";
public VCS mB = new B();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(mB.getAccessRequest()));
startActivity(browserIntent);
}
#Override
public void onClick(View v) {
}
#Override
protected void onNewIntent (Intent intent) {
Uri uri = intent.getData();
if (uri != null) {
Log.d(TAG, uri.toString());
}
}
}
In onNewIntent I get auth token.
what is the procedure to login throw twitter api1.1.
i have used old api that will show me twitter connection failed because of api 1 is deprecated.
private final TwDialogListener mTwLoginDialogListener = new TwDialogListener()
{
public void onComplete(String value)
{
getTwitterDetail();
}
public void onError(String value) {
Toast.makeText(LoginActivity.this, "Twitter connection failed", Toast.LENGTH_LONG).show();
}
};
LOG
{"errors": [{"message": "The Twitter REST API v1 is no longer active.
Please migrate to API v1.1. https://dev.twitter.com/docs/api/1.1/overview.", "code": 68}]}
As you see from error log API v.1 is no longer active and everybody must migrate to v1.1. In API v1.1. you must log in via OAUTH to get connected. So you also have to register your app on dev.twitter.com.
You can find below example here
public class Main extends Activity{
public static final String TAG = Main.class.getSimpleName();
public static final String TWITTER_OAUTH_REQUEST_TOKEN_ENDPOINT = "..."; //cannot share more then 2 lins, sorry
public static final String TWITTER_OAUTH_ACCESS_TOKEN_ENDPOINT = "...";
public static final String TWITTER_OAUTH_AUTHORIZE_ENDPOINT = "...";
private CommonsHttpOAuthProvider commonsHttpOAuthProvider;
private CommonsHttpOAuthConsumer commonsHttpOAuthConsumer;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
commonsHttpOAuthProvider = new CommonsHttpOAuthProvider(TWITTER_OAUTH_REQUEST_TOKEN_ENDPOINT,
TWITTER_OAUTH_ACCESS_TOKEN_ENDPOINT, TWITTER_OAUTH_AUTHORIZE_ENDPOINT);
commonsHttpOAuthConsumer = new CommonsHttpOAuthConsumer(getString(R.string.twitter_oauth_consumer_key),
getString(R.string.twitter_oauth_consumer_secret));
commonsHttpOAuthProvider.setOAuth10a(true);
TwDialog dialog = new TwDialog(this, commonsHttpOAuthProvider, commonsHttpOAuthConsumer,
dialogListener, R.drawable.android);
dialog.show();
}
private Twitter.DialogListener dialogListener = new Twitter.DialogListener() {
public void onComplete(Bundle values) {
String secretToken = values.getString("secret_token");
Log.i(TAG,"secret_token=" + secretToken);
String accessToken = values.getString("access_token");
Log.i(TAG,"access_token=" + accessToken);
new Tweeter(accessToken,secretToken).tweet(
"Tweet from sample Android OAuth app. unique code: " + System.currentTimeMillis());
}
public void onTwitterError(TwitterError e) { Log.e(TAG,"onTwitterError called for TwitterDialog",
new Exception(e)); }
public void onError(DialogError e) { Log.e(TAG,"onError called for TwitterDialog", new Exception(e)); }
public void onCancel() { Log.e(TAG,"onCancel"); }
};
public static final Pattern ID_PATTERN = Pattern.compile(".*?\"id_str\":\"(\\d*)\".*");
public static final Pattern SCREEN_NAME_PATTERN = Pattern.compile(".*?\"screen_name\":\"([^\"]*).*");
public class Tweeter {
protected CommonsHttpOAuthConsumer oAuthConsumer;
public Tweeter(String accessToken, String secretToken) {
oAuthConsumer = new CommonsHttpOAuthConsumer(getString(R.string.twitter_oauth_consumer_key),
getString(R.string.twitter_oauth_consumer_secret));
oAuthConsumer.setTokenWithSecret(accessToken, secretToken);
}
public boolean tweet(String message) {
if (message == null && message.length() > 140) {
throw new IllegalArgumentException("message cannot be null and must be less than 140 chars");
}
// create a request that requires authentication
try {
HttpClient httpClient = new DefaultHttpClient();
Uri.Builder builder = new Uri.Builder();
builder.appendPath("statuses").appendPath("update.json")
.appendQueryParameter("status", message);
Uri man = builder.build();
HttpPost post = new HttpPost("http://twitter.com" + man.toString());
oAuthConsumer.sign(post);
HttpResponse resp = httpClient.execute(post);
String jsonResponseStr = convertStreamToString(resp.getEntity().getContent());
Log.i(TAG,"response: " + jsonResponseStr);
String id = getFirstMatch(ID_PATTERN,jsonResponseStr);
Log.i(TAG,"id: " + id);
String screenName = getFirstMatch(SCREEN_NAME_PATTERN,jsonResponseStr);
Log.i(TAG,"screen name: " + screenName);
final String url = MessageFormat.format("https://twitter.com/#!/{0}/status/{1}",screenName,id);
Log.i(TAG,"url: " + url);
Runnable runnable = new Runnable() {
public void run() {
((TextView)Main.this.findViewById(R.id.textView)).setText("Tweeted: " + url);
}
};
Main.this.runOnUiThread(runnable);
return resp.getStatusLine().getStatusCode() == 200;
} catch (Exception e) {
Log.e(TAG,"trying to tweet: " + message, e);
return false;
}
}
}
public static String convertStreamToString(java.io.InputStream is) {
try {
return new java.util.Scanner(is).useDelimiter("\\A").next();
} catch (java.util.NoSuchElementException e) {
return "";
}
}
public static String getFirstMatch(Pattern pattern, String str){
Matcher matcher = pattern.matcher(str);
if(matcher.matches()){
return matcher.group(1);
}
return null;
}
I use this sample to login Twitter, post status and photo. I used it for a long time. Now Twitter requires upgrading from Twitter API 1.0 to Twitter API 1.1. What do I have to do to upgrade it? I tried to replace the old lib with this lib and there is no problem so far but I'm scared of I didn't do the change completely.
You must log in via OAUTH (https://dev.twitter.com/docs/auth/using-oauth) to get connected. So you also have to register your app on dev.twitter.com.
You can find below example here https://github.com/browep/Android-OAuth-Twitter-Example.
public class Main extends Activity{
public static final String TAG = Main.class.getSimpleName();
public static final String TWITTER_OAUTH_REQUEST_TOKEN_ENDPOINT = "..."; //cannot share more then 2 lins, sorry
public static final String TWITTER_OAUTH_ACCESS_TOKEN_ENDPOINT = "...";
public static final String TWITTER_OAUTH_AUTHORIZE_ENDPOINT = "...";
private CommonsHttpOAuthProvider commonsHttpOAuthProvider;
private CommonsHttpOAuthConsumer commonsHttpOAuthConsumer;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
commonsHttpOAuthProvider = new CommonsHttpOAuthProvider(TWITTER_OAUTH_REQUEST_TOKEN_ENDPOINT,
TWITTER_OAUTH_ACCESS_TOKEN_ENDPOINT, TWITTER_OAUTH_AUTHORIZE_ENDPOINT);
commonsHttpOAuthConsumer = new CommonsHttpOAuthConsumer(getString(R.string.twitter_oauth_consumer_key),
getString(R.string.twitter_oauth_consumer_secret));
commonsHttpOAuthProvider.setOAuth10a(true);
TwDialog dialog = new TwDialog(this, commonsHttpOAuthProvider, commonsHttpOAuthConsumer,
dialogListener, R.drawable.android);
dialog.show();
}
private Twitter.DialogListener dialogListener = new Twitter.DialogListener() {
public void onComplete(Bundle values) {
String secretToken = values.getString("secret_token");
Log.i(TAG,"secret_token=" + secretToken);
String accessToken = values.getString("access_token");
Log.i(TAG,"access_token=" + accessToken);
new Tweeter(accessToken,secretToken).tweet(
"Tweet from sample Android OAuth app. unique code: " + System.currentTimeMillis());
}
public void onTwitterError(TwitterError e) { Log.e(TAG,"onTwitterError called for TwitterDialog",
new Exception(e)); }
public void onError(DialogError e) { Log.e(TAG,"onError called for TwitterDialog", new Exception(e)); }
public void onCancel() { Log.e(TAG,"onCancel"); }
};
public static final Pattern ID_PATTERN = Pattern.compile(".*?\"id_str\":\"(\\d*)\".*");
public static final Pattern SCREEN_NAME_PATTERN = Pattern.compile(".*?\"screen_name\":\"([^\"]*).*");
public class Tweeter {
protected CommonsHttpOAuthConsumer oAuthConsumer;
public Tweeter(String accessToken, String secretToken) {
oAuthConsumer = new CommonsHttpOAuthConsumer(getString(R.string.twitter_oauth_consumer_key),
getString(R.string.twitter_oauth_consumer_secret));
oAuthConsumer.setTokenWithSecret(accessToken, secretToken);
}
public boolean tweet(String message) {
if (message == null && message.length() > 140) {
throw new IllegalArgumentException("message cannot be null and must be less than 140 chars");
}
// create a request that requires authentication
try {
HttpClient httpClient = new DefaultHttpClient();
Uri.Builder builder = new Uri.Builder();
builder.appendPath("statuses").appendPath("update.json")
.appendQueryParameter("status", message);
Uri man = builder.build();
HttpPost post = new HttpPost("http://twitter.com" + man.toString());
oAuthConsumer.sign(post);
HttpResponse resp = httpClient.execute(post);
String jsonResponseStr = convertStreamToString(resp.getEntity().getContent());
Log.i(TAG,"response: " + jsonResponseStr);
String id = getFirstMatch(ID_PATTERN,jsonResponseStr);
Log.i(TAG,"id: " + id);
String screenName = getFirstMatch(SCREEN_NAME_PATTERN,jsonResponseStr);
Log.i(TAG,"screen name: " + screenName);
final String url = MessageFormat.format("https://twitter.com/#!/{0}/status/{1}",screenName,id);
Log.i(TAG,"url: " + url);
Runnable runnable = new Runnable() {
public void run() {
((TextView)Main.this.findViewById(R.id.textView)).setText("Tweeted: " + url);
}
};
Main.this.runOnUiThread(runnable);
return resp.getStatusLine().getStatusCode() == 200;
} catch (Exception e) {
Log.e(TAG,"trying to tweet: " + message, e);
return false;
}
}
}
public static String convertStreamToString(java.io.InputStream is) {
try {
return new java.util.Scanner(is).useDelimiter("\\A").next();
} catch (java.util.NoSuchElementException e) {
return "";
}
}
public static String getFirstMatch(Pattern pattern, String str){
Matcher matcher = pattern.matcher(str);
if(matcher.matches()){
return matcher.group(1);
}
return null;
}
I have a problem with google account OAuth 2 tokens.
We need token for access account information (numeric id, email, user name)
After request getAuthToken(account, SCOPE, options, mContext, getAuthTokenCallback, null) in AccountManager, token is not available for access to account information.
Response of HTTP request https://www.googleapis.com/plus/v1/people/me (header "Authorization: OAuth ya29.AHES6ZSuMvL3FoxqXfevYevWyEmTPOE1HXW7_Tj6l3UAN-2J7kTs0-I")
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit Exceeded. Please sign up",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit Exceeded. Please sign up"
}
}
Why this error hapens?
Previously works with two types AuthSub tokens separated by spaces.(SCOPE_OLD_PERMITIONS)
Now it not works & causes java.io.IOException
How can I get valid token?
This is request for get token:
TCGoogleAccountsManager mng = new TCGoogleAccountsManager(this);
mng.requestAccountOAuthToken(this, acc);
сlass that helps get token:
public class TCGoogleAccountsManager {
private static final String CLIENT_SECRET = ...;
private static final String CLIENT_ID = ...;
private static final String SCOPE_CONTACTS_API = "cp";
private static final String SCOPE_ANDROID_API = "android";
private static final String SCOPE_GOOGPE_PLUS = "oauth2:https://www.googleapis.com/auth/plus.me";
private static final String SCOPE_MY_INFO = "oauth2:https://www.googleapis.com/auth/userinfo.email";
private static final String SCOPE_OLD_PERMITIONS = "oauth2:https://www-opensocial.googleusercontent.com/api/people/ oauth2:https://www.googleapis.com/auth/userinfo#email";
private static final String SCOPE = SCOPE_GOOGPE_PLUS;
private static final String COM_GOOGLE = "com.google";
private AccountManager mManager;
private OnGetOAuthTokenRequestCompletedListener mTokenRequestListener;
public TCGoogleAccountsManager(Context ctx) {
mManager = AccountManager.get(ctx.getApplicationContext());
mTokenRequestListener = new GoogleTokenListener(
ctx.getApplicationContext());
}
public int getAccountsNumber() {
return mManager.getAccountsByType(COM_GOOGLE).length;
}
public Account[] getGoogleAccounts() {
return mManager.getAccountsByType(COM_GOOGLE);
}
public Account getGoogleAccountByName(String name) {
Account foundAcc = null;
if (name != null && !name.equals("")) {
Account[] googleAccounts = mManager.getAccountsByType(COM_GOOGLE);
for (int i = 0; i < googleAccounts.length; i++) {
if (name.equals(googleAccounts[i].name)) {
foundAcc = googleAccounts[i];
break;
}
}
}
return foundAcc;
}
public Account getGoogleAccount(int index) {
return getGoogleAccounts()[index];
}
public void requestAccountOAuthToken(Activity mContext, Account account) {
try {
final Bundle options = new Bundle();
options.putString("client_id", CLIENT_ID);
options.putString("client_secret", CLIENT_SECRET);
mManager.getAuthToken(account, SCOPE, options, mContext,
getAuthTokenCallback, null);
} catch (Exception e) {
e.printStackTrace();
}
}
private AccountManagerCallback<Bundle> getAuthTokenCallback = new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
final Bundle result = future.getResult();
final String accountName = result
.getString(AccountManager.KEY_ACCOUNT_NAME);
final String authToken = result
.getString(AccountManager.KEY_AUTHTOKEN);
boolean success = (accountName != null && authToken != null);
if (!success) {
if (mTokenRequestListener != null) {
mTokenRequestListener.onRequestCompleted(false,
accountName, authToken);
}
} else {
// refresh token. We need fresh token.
mManager.invalidateAuthToken(COM_GOOGLE, authToken);
mManager.getAuthToken(getGoogleAccountByName(accountName),
SCOPE, false, getAuthTokenCallbackInvalidated, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
private AccountManagerCallback<Bundle> getAuthTokenCallbackInvalidated = new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
final Bundle result = future.getResult();
final String accountName = result
.getString(AccountManager.KEY_ACCOUNT_NAME);
final String authToken = result
.getString(AccountManager.KEY_AUTHTOKEN);
boolean success = (accountName != null && authToken != null);
if (mTokenRequestListener != null)
mTokenRequestListener.onRequestCompleted(success,
accountName, authToken);
} catch (Exception e) {
e.printStackTrace();
}
}
};
}
Thanks and Regards.
You need a client_id to use with OAuth2, which you can obtain by registering your application at https://code.google.com/apis/console