/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.apps.iosched.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.support.v4.app.Fragment;
import android.support.v4.app.ListFragment;
import android.support.v7.app.ActionBarActivity;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.analytics.tracking.android.EasyTracker;
import com.google.android.apps.iosched.R;
import com.google.android.apps.iosched.provider.ScheduleContract;
import com.google.android.apps.iosched.sync.SyncHelper;
import com.google.android.apps.iosched.util.AccountUtils;
import com.google.android.apps.iosched.util.PrefUtils;
import com.google.android.apps.iosched.util.WiFiUtils;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.plus.PlusClient;
import com.google.android.gms.plus.model.people.PersonBuffer;
import java.util.Arrays;
import java.util.List;
import static com.google.android.apps.iosched.util.LogUtils.LOGE;
import static com.google.android.apps.iosched.util.LogUtils.LOGW;
import static com.google.android.apps.iosched.util.LogUtils.makeLogTag;
public class AccountActivity extends ActionBarActivity
implements AccountUtils.AuthenticateCallback, GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener, PlusClient.OnPeopleLoadedListener {
private static final String TAG = makeLogTag(AccountActivity.class);
public static final String EXTRA_FINISH_INTENT
= "com.google.android.iosched.extra.FINISH_INTENT";
private static final int SETUP_ATTENDEE = 1;
private static final int SETUP_WIFI = 2;
private static final String KEY_CHOSEN_ACCOUNT = "chosen_account";
private static final int REQUEST_AUTHENTICATE = 100;
private static final int REQUEST_RECOVER_FROM_AUTH_ERROR = 101;
private static final int REQUEST_RECOVER_FROM_PLAY_SERVICES_ERROR = 102;
private static final int REQUEST_PLAY_SERVICES_ERROR_DIALOG = 103;
private static final String POST_AUTH_CATEGORY
= "com.google.android.iosched.category.POST_AUTH";
private Account mChosenAccount;
private Intent mFinishIntent;
private boolean mCancelAuth = false;
private boolean mAuthInProgress = false;
private boolean mAuthProgressFragmentResumed = false;
private boolean mCanRemoveAuthProgressFragment = false;
private PlusClient mPlusClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_letterboxed_when_large);
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_FINISH_INTENT)) {
mFinishIntent = intent.getParcelableExtra(EXTRA_FINISH_INTENT);
}
if (savedInstanceState == null) {
if (!AccountUtils.isAuthenticated(this)) {
getSupportFragmentManager().beginTransaction()
.add(R.id.root_container, new SignInMainFragment(), "signin_main")
.commit();
} else {
mChosenAccount = new Account(AccountUtils.getChosenAccountName(this), "com.google");
getSupportFragmentManager().beginTransaction()
.add(R.id.root_container,
SignInSetupFragment.makeFragment(SETUP_ATTENDEE), "setup_attendee")
.commit();
}
} else {
String accountName = savedInstanceState.getString(KEY_CHOSEN_ACCOUNT);
if (accountName != null) {
mChosenAccount = new Account(accountName, "com.google");
mPlusClient = (new PlusClient.Builder(this, this, this))
.setAccountName(accountName)
.setScopes(AccountUtils.AUTH_SCOPES)
.build();
}
}
}
@Override
public void onRecoverableException(final int code) {
runOnUiThread(new Runnable() {
@Override
public void run() {
final Dialog d = GooglePlayServicesUtil.getErrorDialog(
code,
AccountActivity.this,
REQUEST_RECOVER_FROM_PLAY_SERVICES_ERROR);
d.show();
}
});
}
@Override
public void onUnRecoverableException(final String errorMessage) {
LOGW(TAG, "Encountered unrecoverable exception: " + errorMessage);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (mChosenAccount != null)
outState.putString(KEY_CHOSEN_ACCOUNT, mChosenAccount.name);
super.onSaveInstanceState(outState);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_AUTHENTICATE ||
requestCode == REQUEST_RECOVER_FROM_AUTH_ERROR ||
requestCode == REQUEST_PLAY_SERVICES_ERROR_DIALOG) {
if (resultCode == RESULT_OK) {
if (mPlusClient != null) mPlusClient.connect();
} else {
if (mAuthProgressFragmentResumed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
getSupportFragmentManager().popBackStack();
}
});
} else {
mCanRemoveAuthProgressFragment = true;
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onStop() {
super.onStop();
if (mAuthInProgress) mCancelAuth = true;
if (mPlusClient != null)
mPlusClient.disconnect();
}
@Override
public void onPeopleLoaded(ConnectionResult status, PersonBuffer personBuffer,
String nextPageToken) {
if (status.isSuccess()) {
if (personBuffer != null && personBuffer.getCount() > 0) {
// Set the profile id
AccountUtils.setPlusProfileId(this, personBuffer.get(0).getId());
}
} else {
LOGE(TAG, "Got " + status.getErrorCode() + ". Could not load plus profile.");
}
}
public static class SignInMainFragment extends Fragment {
public SignInMainFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.fragment_login_main, container, false);
TextView descriptionTextView = (TextView) rootView.findViewById(
R.id.sign_in_description);
descriptionTextView.setText(Html.fromHtml(getString(R.string.description_sign_in_main)));
SignInButton signinButtonView = (SignInButton) rootView.findViewById(R.id.sign_in_button);
signinButtonView.setSize(SignInButton.SIZE_WIDE);
signinButtonView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.root_container, new ChooseAccountFragment(),
"choose_account")
.addToBackStack("signin_main")
.commit();
}
});
return rootView;
}
}
public static class SignInSetupFragment extends ListFragment {
private static final String ARG_SETUP_ID = "setupId";
private int mDescriptionHeaderResId = 0;
private int mDescriptionBodyResId = 0;
private int mSelectionResId = 0;
private int mSetupId;
private static final int ATCONF_DIMEN_INDEX = 1;
public SignInSetupFragment() {}
public static Fragment makeFragment(int setupId) {
Bundle args = new Bundle();
args.putInt(ARG_SETUP_ID, setupId);
SignInSetupFragment fragment = new SignInSetupFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mSetupId = getArguments().getInt(ARG_SETUP_ID);
switch (mSetupId) {
case SETUP_ATTENDEE:
mDescriptionHeaderResId = R.string.description_setup_attendee_mode_header;
mDescriptionBodyResId = R.string.description_setup_attendee_mode_body;
mSelectionResId = R.array.selection_setup_attendee;
break;
case SETUP_WIFI:
mDescriptionHeaderResId = R.string.description_setup_wifi_header;
mDescriptionBodyResId = R.string.description_setup_wifi_body;
mSelectionResId = R.array.selection_setup_wifi;
break;
default:
throw new IllegalArgumentException("Undefined setup id.");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.fragment_login_setup, container, false);
final TextView descriptionView = (TextView) rootView.findViewById(
R.id.login_setup_desc);
descriptionView.setText(Html.fromHtml(getString(mDescriptionHeaderResId) +
getString(mDescriptionBodyResId)));
return rootView;
}
@Override
public void onResume() {
super.onResume();
setListAdapter(
new ArrayAdapter<String> (getActivity(),
R.layout.list_item_login_option,
getResources().getStringArray(mSelectionResId)));
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
final Activity activity = getActivity();
if (mSetupId == SETUP_ATTENDEE) {
if (position == 0) {
// Attendee is at the conference. If WiFi AP isn't set already, go to
// the WiFi set up screen. Otherwise, set up is done.
PrefUtils.setAttendeeAtVenue(activity, true);
PrefUtils.setUsingLocalTime(activity, false);
// If WiFi has already been configured, set up is complete. Otherwise,
// show the WiFi AP configuration screen.
//if (WiFiUtils.shouldInstallWiFi(activity)) {
if (WiFiUtils.shouldBypassWiFiSetup(activity)) {
((AccountActivity)activity).finishSetup();
} else {
getFragmentManager().beginTransaction()
.replace(R.id.root_container,
SignInSetupFragment.makeFragment(SETUP_WIFI), "setup_wifi")
.addToBackStack("setup_attendee")
.commit();
}
EasyTracker.getTracker()
.setCustomDimension(ATCONF_DIMEN_INDEX,"conference attendee");
} else if (position == 1) {
// Attendee is remote. Set up is done.
PrefUtils.setAttendeeAtVenue(activity, false);
PrefUtils.setUsingLocalTime(activity, true);
EasyTracker.getTracker()
.setCustomDimension(ATCONF_DIMEN_INDEX,"remote attendee");
((AccountActivity)activity).finishSetup();
}
} else if (mSetupId == SETUP_WIFI) {
if (position == 0) {
WiFiUtils.setWiFiConfigStatus(activity, WiFiUtils.WIFI_CONFIG_REQUESTED);
}
// Done with set up.
((AccountActivity)activity).finishSetup();
}
}
}
public static class ChooseAccountFragment extends ListFragment {
public ChooseAccountFragment() {
}
@Override
public void onResume() {
super.onResume();
reloadAccountList();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(
R.layout.fragment_login_choose_account, container, false);
TextView descriptionView = (TextView) rootView.findViewById(R.id.choose_account_intro);
descriptionView.setText(Html.fromHtml(getString(R.string.description_choose_account)));
return rootView;
}
private AccountListAdapter mAccountListAdapter;
private void reloadAccountList() {
if (mAccountListAdapter != null) {
mAccountListAdapter = null;
}
AccountManager am = AccountManager.get(getActivity());
Account[] accounts = am.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
mAccountListAdapter = new AccountListAdapter(getActivity(), Arrays.asList(accounts));
setListAdapter(mAccountListAdapter);
}
private class AccountListAdapter extends ArrayAdapter<Account> {
private static final int LAYOUT_RESOURCE = R.layout.list_item_login_option;
public AccountListAdapter(Context context, List<Account> accounts) {
super(context, LAYOUT_RESOURCE, accounts);
}
private class ViewHolder {
TextView text1;
}
@Override
public int getCount() {
return super.getCount() + 1;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = getLayoutInflater(null).inflate(LAYOUT_RESOURCE, null);
holder = new ViewHolder();
holder.text1 = (TextView) convertView.findViewById(android.R.id.text1);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (position == getCount() - 1) {
holder.text1.setText(R.string.description_add_account);
} else {
final Account account = getItem(position);
if (account != null) {
holder.text1.setText(account.name);
} else {
holder.text1.setText("");
}
}
return convertView;
}
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if (position == mAccountListAdapter.getCount() - 1) {
Intent addAccountIntent = new Intent(Settings.ACTION_ADD_ACCOUNT);
addAccountIntent.putExtra(Settings.EXTRA_AUTHORITIES,
new String[]{ScheduleContract.CONTENT_AUTHORITY});
startActivity(addAccountIntent);
return;
}
AccountActivity activity = (AccountActivity) getActivity();
ConnectivityManager cm = (ConnectivityManager)
activity.getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork == null || !activeNetwork.isConnected()) {
Toast.makeText(activity, R.string.no_connection_cant_login,
Toast.LENGTH_SHORT).show();
return;
}
activity.mCancelAuth = false;
activity.mChosenAccount = mAccountListAdapter.getItem(position);
activity.getSupportFragmentManager().beginTransaction()
.replace(R.id.root_container, new AuthProgressFragment(), "loading")
.addToBackStack("choose_account")
.commit();
PlusClient.Builder builder = new PlusClient.Builder(activity, activity, activity);
activity.mPlusClient = builder
.setScopes(AccountUtils.AUTH_SCOPES)
.setAccountName(activity.mChosenAccount.name).build();
activity.mPlusClient.connect();
}
}
public static class AuthProgressFragment extends Fragment {
private static final int TRY_AGAIN_DELAY_MILLIS = 7 * 1000; // 7 seconds
private final Handler mHandler = new Handler();
public AuthProgressFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_login_loading,
container, false);
final View takingAWhilePanel = rootView.findViewById(R.id.taking_a_while_panel);
final View tryAgainButton = rootView.findViewById(R.id.retry_button);
tryAgainButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getFragmentManager().popBackStack();
}
});
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!isAdded()) {
return;
}
if (AccountUtils.isAuthenticated(getActivity())) {
((AccountActivity) getActivity()).onAuthTokenAvailable();
return;
}
takingAWhilePanel.setVisibility(View.VISIBLE);
}
}, TRY_AGAIN_DELAY_MILLIS);
return rootView;
}
@Override
public void onPause() {
super.onPause();
((AccountActivity) getActivity()).mAuthProgressFragmentResumed = false;
}
@Override
public void onResume() {
super.onResume();
((AccountActivity) getActivity()).mAuthProgressFragmentResumed = true;
if (((AccountActivity) getActivity()).mCanRemoveAuthProgressFragment) {
((AccountActivity) getActivity()).mCanRemoveAuthProgressFragment = false;
getFragmentManager().popBackStack();
}
}
@Override
public void onDetach() {
super.onDetach();
((AccountActivity) getActivity()).mCancelAuth = true;
}
}
private void tryAuthenticate() {
// Authenticate through the Google Play OAuth client.
mAuthInProgress = true;
AccountUtils.tryAuthenticate(this, this, mChosenAccount.name,
REQUEST_RECOVER_FROM_AUTH_ERROR);
}
@Override
public boolean shouldCancelAuthentication() {
return mCancelAuth;
}
@Override
public void onAuthTokenAvailable() {
// Cancel progress fragment.
// Create set up fragment.
mAuthInProgress = false;
if (mAuthProgressFragmentResumed) {
getSupportFragmentManager().popBackStack();
getSupportFragmentManager().beginTransaction()
.replace(R.id.root_container,
SignInSetupFragment.makeFragment(SETUP_ATTENDEE), "setup_attendee")
.addToBackStack("signin_main")
.commit();
}
}
private void finishSetup() {
ContentResolver.setIsSyncable(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, 1);
ContentResolver.setSyncAutomatically(mChosenAccount, ScheduleContract.CONTENT_AUTHORITY, true);
SyncHelper.requestManualSync(mChosenAccount);
PrefUtils.markSetupDone(this);
if (mFinishIntent != null) {
// Ensure the finish intent is unique within the task. Otherwise, if the task was
// started with this intent, and it finishes like it should, then startActivity on
// the intent again won't work.
mFinishIntent.addCategory(POST_AUTH_CATEGORY);
startActivity(mFinishIntent);
}
finish();
}
// Google Plus client callbacks.
@Override
public void onConnected(Bundle connectionHint) {
// It is possible that the authenticated account doesn't have a profile.
mPlusClient.loadPeople(this, "me");
tryAuthenticate();
}
@Override
public void onDisconnected() {
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
if (connectionResult.hasResolution()) {
try {
connectionResult.startResolutionForResult(this,
REQUEST_RECOVER_FROM_AUTH_ERROR);
} catch (IntentSender.SendIntentException e) {
LOGE(TAG, "Internal error encountered: " + e.getMessage());
}
return;
}
final int errorCode = connectionResult.getErrorCode();
if (GooglePlayServicesUtil.isUserRecoverableError(errorCode)) {
GooglePlayServicesUtil.getErrorDialog(errorCode, this,
REQUEST_PLAY_SERVICES_ERROR_DIALOG).show();
}
}
}