Since Android 4.2.2 it's possible to run Google Services on the Android Emulator. I'm currently making an Android app and made a test project to see if I can get Google+ sign-in and sign-out to work.
I've followed the following tutorial: http://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/
With extra info used from the following tutorials/sites:
This generated the following code:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testproject_gmaillogin"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<activity
android:name="com.example.testproject_gmaillogin.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">TestProject_GmailLogin</string>
<string name="action_settings">Settings</string>
<string name="profile_pic_description">Google Profile Picture</string>
<string name="btn_logout_from_google">Logout from Google</string>
<string name="btn_revoke_access">Revoke Access</string>
</resources>
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity" >
<LinearLayout
android:id="@+id/profile_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="horizontal"
android:weightSum="3"
android:visibility="gone">
<ImageView
android:id="@+id/img_profile_pic"
android:contentDescription="@string/profile_pic_description"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical"
android:layout_weight="2" >
<TextView
android:id="@+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="20sp" />
<TextView
android:id="@+id/txt_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
<com.google.android.gms.common.SignInButton
android:id="@+id/btn_sign_in"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"/>
<Button
android:id="@+id/btn_sign_out"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/btn_logout_from_google"
android:visibility="gone"
android:layout_marginBottom="10dp"/>
<Button
android:id="@+id/btn_revoke_access"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/btn_revoke_access"
android:visibility="gone" />
</LinearLayout>
MainActivity.java:
package com.example.testproject_gmaillogin;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.model.people.Person;
import android.support.v7.app.ActionBarActivity;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnClickListener
{
// Logcat tag
private static final String TAG = "MainActivity";
// Profile pix image size in pixels
private static final int PROFILE_PIC_SIZE = 400;
// Request code used to invoke sign in user interactions
private static final int RC_SIGN_IN = 0;
// Client used to interact with Google APIs
private GoogleApiClient mGoogleApiClient;
// A flag indicating that a PendingIntent is in progress and prevents
// us from starting further intents
private boolean mIntentInProgress;
// Track whether the sign-in button has been clicked so that we know to resolve
// all issues preventing sign-in without waiting
private boolean mSignInClicked;
// Store the connection result from onConnectionFailed callbacks so that we can
// resolve them when the user clicks sign-in
private ConnectionResult mConnectionResult;
// The used UI-elements
private SignInButton btnSignIn;
private Button btnSignOut, btnRevokeAccess;
private ImageView imgProfilePic;
private TextView txtName, txtEmail;
private LinearLayout profileLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the UI-elements
btnSignIn = (SignInButton) findViewById(R.id.btn_sign_in);
btnSignOut = (Button) findViewById(R.id.btn_sign_out);
btnRevokeAccess = (Button) findViewById(R.id.btn_revoke_access);
imgProfilePic = (ImageView) findViewById(R.id.img_profile_pic);
txtName = (TextView) findViewById(R.id.txt_name);
txtEmail = (TextView) findViewById(R.id.txt_email);
profileLayout = (LinearLayout) findViewById(R.id.profile_layout);
// Set the Button onClick-listeners
btnSignIn.setOnClickListener(this);
btnSignOut.setOnClickListener(this);
btnRevokeAccess.setOnClickListener(this);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API, null)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.build();
}
@Override
protected void onStart(){
super.onStart();
mGoogleApiClient.connect();
}
@Override
protected void onStop(){
super.onStop();
if(mGoogleApiClient.isConnected())
mGoogleApiClient.disconnect();
}
@Override
public void onClick(View view){
switch(view.getId()){
case R.id.btn_sign_in:
signInWithGPlus();
break;
case R.id.btn_sign_out:
signOutFromGPlus();
break;
case R.id.btn_revoke_access:
revokeGPlusAccess();
break;
}
}
@Override
public void onConnectionFailed(ConnectionResult result) {
if(!result.hasResolution()){
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show();
return;
}
if(!mIntentInProgress){
// Store the ConnectionResult so that we can use it later when the user clicks 'sign-in'
mConnectionResult = result;
if(mSignInClicked)
// The user has already clicked 'sign-in' so we attempt to resolve all
// errors until the user is signed in, or they cancel
resolveSignInErrors();
}
}
@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent){
if(requestCode == RC_SIGN_IN && responseCode == RESULT_OK)
SignInClicked = true;
mIntentInProgress = false;
if(!mGoogleApiClient.isConnecting())
mGoogleApiClient.connect();
}
}
@Override
public void onConnected(Bundle connectionHint) {
mSignInClicked = false;
Toast.makeText(this, "User is connected!", Toast.LENGTH_LONG).show();
// Get all the user's information
getProfileInformation();
// Update the UI after sign-in
updateUI(true);
}
@Override
public void onConnectionSuspended(int cause){
mGoogleApiClient.connect();
updateUI(false);
}
// Updating the UI, showing/hiding buttons and profile layout
private void updateUI(boolean isSignedIn){
if(isSignedIn){
btnSignIn.setVisibility(View.GONE);
btnSignOut.setVisibility(View.VISIBLE);
btnRevokeAccess.setVisibility(View.VISIBLE);
profileLayout.setVisibility(View.VISIBLE);
}
else{
btnSignIn.setVisibility(View.VISIBLE);
btnSignOut.setVisibility(View.GONE);
btnRevokeAccess.setVisibility(View.GONE);
profileLayout.setVisibility(View.GONE);
}
}
// Sign-in into Google
private void signInWithGPlus(){
if(!mGoogleApiClient.isConnecting()){
mSignInClicked = true;
resolveSignInErrors();
}
}
// Method to resolve any sign-in errors
private void resolveSignInErrors(){
if(mConnectionResult.hasResolution()){
try{
mIntentInProgress = true;
//Toast.makeText(this, "Resolving Sign-in Errors", Toast.LENGTH_SHORT).show();
mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
}
catch(SendIntentException e){
// The intent was cancelled before it was sent. Return to the default
// state and attempt to connect to get an updated ConnectionResult
mIntentInProgress = false;
mGoogleApiClient.connect();
}
}
}
// Fetching the user's infromation name, email, profile pic
private void getProfileInformation(){
try{
if(Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null){
Person currentPerson = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);
String personName = currentPerson.getDisplayName();
String personPhotoUrl = currentPerson.getImage().getUrl();
String personGooglePlusProfile = currentPerson.getUrl();
String personEmail = Plus.AccountApi.getAccountName(mGoogleApiClient);
Log.e(TAG, "Name: " + personName + ", "
+ "plusProfile: " + personGooglePlusProfile + ", "
+ "email: " + personEmail + ", "
+ "image: " + personPhotoUrl);
txtName.setText(personName);
txtEmail.setText(personEmail);
// by default the profile url gives 50x50 px image,
// but we can replace the value with whatever dimension we
// want by replacing sz=X
personPhotoUrl = personPhotoUrl.substring(0, personPhotoUrl.length() - 2)
+ PROFILE_PIC_SIZE;
new LoadProfileImage(imgProfilePic).execute(personPhotoUrl);
}
else{
Toast.makeText(getApplicationContext(), "Person information is null", Toast.LENGTH_LONG).show();
}
}
catch(Exception ex){
ex.printStackTrace();
}
}
// Sign-out from Google
private void signOutFromGPlus(){
if(mGoogleApiClient.isConnected()){
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
mGoogleApiClient.disconnect();
mGoogleApiClient.connect();
updateUI(false);
}
}
// Revoking access from Google
private void revokeGPlusAccess(){
if(mGoogleApiClient.isConnected()){
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient)
.setResultCallback(new ResultCallback<Status>(){
@Override
public void onResult(Status s){
Log.e(TAG, "User access revoked!");
mGoogleApiClient.connect();
updateUI(false);
}
});
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings)
return true;
return super.onOptionsItemSelected(item);
}
}
LoadProfileImage.java:
package com.example.testproject_gmaillogin;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;
/**
* Background async task to load user profile picture from url
**/
public class LoadProfileImage extends AsyncTask<String, Void, Bitmap> {
private ImageView bmImage;
public LoadProfileImage(ImageView bmImage){
this.bmImage = bmImage;
}
@Override
protected Bitmap doInBackground(String... urls){
String urlDisplay = urls[0];
Bitmap mIcon11 = null;
try{
InputStream in = new java.net.URL(urlDisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
}
catch(Exception ex){
Log.e("Error", ex.getMessage());
ex.printStackTrace();
}
return mIcon11;
}
@Override
protected void onPostExecute(Bitmap result){
bmImage.setImageBitmap(result);
}
}
The other steps I did were:
At https://console.developers.google.com/project I've created a project with:
Google+ API on:
And a Client ID created with the correct SHA1 and exact same namespace as the project:
At Eclipse:
I've installed the google-play-services library:
And added it to the project:
I've also created an Emulator with version 4.4.2:
But when I run the app I get the following error, and this error keeps popping up when I click on the button:
Anyone has any idea where it goes wrong? Thanks in advance for the responses.
Go to your device's Settings --> Applications --> Tab "All" --> Google Play services and press the Disable button. It may pop up a dialog asking for a confirmation, in which case just answer OK. I have Android 4.0.
Ok, after trying some things it turned out I had one last option not correctly checked, which wasn't mentioned anywhere in the tutorial(s)..
Instead of Android 4.4.2 as Project Build Target & Emulator Target, I've selected Google APIs 4.4.2. Now I don't get the error of Google Play services anymore.
I do get a NullPointerException though, but at least I can continue.. ;)
EDIT: Fixed the NullPointerException and modified the code in my original post. Everything works as it should now and I hope this post helps other people with the same (or other) errors using google play services sign-in using an Android Emulator.
Try using the Goole APIs API level 19 as emulator target instead of normal API level 19.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With