mobisocial.musubi.ui.fragments.AccountLinkDialog.java Source code

Java tutorial

Introduction

Here is the source code for mobisocial.musubi.ui.fragments.AccountLinkDialog.java

Source

/*
 * Copyright 2012 The Stanford MobiSocial Laboratory
 *
 * 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 mobisocial.musubi.ui.fragments;

import gnu.trove.set.TLongSet;
import gnu.trove.set.hash.TLongHashSet;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import mobisocial.crypto.IBHashedIdentity.Authority;
import mobisocial.crypto.IBIdentity;
import mobisocial.metrics.MusubiMetrics;
import mobisocial.musubi.App;
import mobisocial.musubi.R;
import mobisocial.musubi.facebook.SessionStore;
import mobisocial.musubi.model.MDevice;
import mobisocial.musubi.model.MFeed;
import mobisocial.musubi.model.MIdentity;
import mobisocial.musubi.model.MMyAccount;
import mobisocial.musubi.model.MPendingIdentity;
import mobisocial.musubi.model.helpers.DeviceManager;
import mobisocial.musubi.model.helpers.FeedManager;
import mobisocial.musubi.model.helpers.IdentitiesManager;
import mobisocial.musubi.model.helpers.MyAccountManager;
import mobisocial.musubi.model.helpers.PendingIdentityManager;
import mobisocial.musubi.service.AddressBookUpdateHandler;
import mobisocial.musubi.service.MusubiService;
import mobisocial.musubi.service.WizardStepHandler;
import mobisocial.musubi.social.FacebookFriendFetcher;
import mobisocial.musubi.ui.MusubiBaseActivity;
import mobisocial.musubi.ui.SettingsActivity;
import mobisocial.musubi.ui.fragments.AccountLinkDialog.AccountLooperThread.Job;
import mobisocial.musubi.ui.util.UiUtil;
import mobisocial.musubi.util.CommonLayouts;
import mobisocial.musubi.util.InstrumentedActivity;

import org.json.JSONException;
import org.json.JSONObject;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.SupportActivity;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.facebook.android.DialogError;
import com.facebook.android.Facebook;
import com.facebook.android.Facebook.ServiceListener;
import com.facebook.android.FacebookError;
import com.facebook.android.Util;

public class AccountLinkDialog extends DialogFragment {
    final static String TAG = "AccountLinkDialog";
    final boolean DBG = MusubiBaseActivity.DBG;

    public static final String ACCOUNT_TYPE_FACEBOOK = "com.facebook.auth.login";
    public static final String ACCOUNT_TYPE_GOOGLE = "com.google";
    public static final String ACCOUNT_TYPE_PHONE = "mobisocial.musubi.phone";

    static final int MSG_CONNECT_GOOGLE = 1;
    static final int MSG_CONNECT_FB = 2;
    static final int MSG_ADD_TO_DATABASE = 3;
    static final int MSG_CONNECT_PHONE = 4;

    public static final String GOOGLE_OAUTH_SCOPE = "oauth2:https://www.google.com/m8/feeds/";
    public static final String FACEBOOK_APP_ID = "111111111111";
    public static final String[] FACEBOOK_PERMISSIONS = new String[] { "read_friendlists", "email",
            "offline_access", "publish_stream" };

    private static final int REQUEST_GOOGLE_ACCOUNT = 97;
    private static final int REQUEST_FACEBOOK = 98;
    private static final int REQUEST_GOOGLE_AUTHENTICATE = 99;
    private static final int REQUEST_PHONE_NUMBER = 100;
    private static final String EXTRA_ACCOUNT = "account";

    private static final String TEXT_CONNECTED = "Connected!";
    private static final String TEXT_CHECKING = "Checking status...";
    private static final String TEXT_ERROR_CONNECTING = "Failed to connect.";
    private static final String TEXT_UNVERIFIED = "Verification Required.";

    private static final int DISPLAYED_SERVICES = 2;

    private static final int SHORTEST_PHONE_NUMBER = 6;
    private static final int LONGEST_PHONE_NUMBER = 14;

    // TODO: better support for facebook/google synchronisity issues.
    private Activity mActivity;
    private MyAccountManager mAccountManager;
    private ListView mAccountList;
    private AccountAdapter mAccountAdapter;
    private String mPendingAccountName = null;
    private String mPendingAccountType = null;

    private static AccountLooperThread sAccountLooperThread;

    enum AccountStatus {
        PENDING, CONNECTED, ERROR, UNVERIFIED
    };

    public static AccountLinkDialog newInstance() {
        AccountLinkDialog frag = new AccountLinkDialog();
        Bundle args = new Bundle();
        frag.setArguments(args);
        return frag;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setStyle(STYLE_NORMAL, R.style.Theme_D1dialog);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mPendingAccountType != null && mPendingAccountName != null
                && mPendingAccountType.equals(ACCOUNT_TYPE_GOOGLE)) {
            String accountName = mPendingAccountName;
            mPendingAccountName = null;
            mPendingAccountType = null;
            tryGoogleAccount(mActivity, accountName);
        }
    }

    @Override
    public void onAttach(SupportActivity activity) {
        super.onAttach(activity);
        mActivity = activity.asActivity();
        SQLiteOpenHelper databaseSource = App.getDatabaseSource(mActivity);
        mAccountManager = new MyAccountManager(databaseSource);
        if (sAccountLooperThread == null) {
            sAccountLooperThread = new AccountLooperThread();
            sAccountLooperThread.start();
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        LinearLayout window = new LinearLayout(mActivity);
        window.setLayoutParams(CommonLayouts.FULL_SCREEN);
        window.setOrientation(LinearLayout.VERTICAL);

        LinearLayout socialBox = new LinearLayout(mActivity);
        socialBox.setLayoutParams(CommonLayouts.FULL_WIDTH);
        socialBox.setOrientation(LinearLayout.HORIZONTAL);
        socialBox.setWeightSum(1.0f * DISPLAYED_SERVICES);

        /** Google **/
        ImageButton google = new ImageButton(mActivity);
        google.setImageResource(R.drawable.google);
        google.setOnClickListener(mGoogleClickListener);
        google.setLayoutParams(
                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f));
        google.setAdjustViewBounds(true);
        socialBox.addView(google);

        /** Facebook **/
        ImageButton facebook = new ImageButton(mActivity);
        facebook.setImageResource(R.drawable.facebook);
        facebook.setOnClickListener(mFacebookClickListener);
        facebook.setLayoutParams(
                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f));
        facebook.setAdjustViewBounds(true);
        socialBox.addView(facebook);

        /** Phone Number **/
        ImageButton phone = new ImageButton(mActivity);
        phone.setImageResource(R.drawable.phone);
        phone.setOnClickListener(mPhoneClickListener);
        phone.setLayoutParams(
                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f));
        phone.setAdjustViewBounds(true);
        //socialBox.addView(phone);

        /** List of known accounts **/
        TextView chooseService = new TextView(mActivity);
        chooseService.setText("Choose a service to connect.");
        chooseService.setVisibility(View.GONE);
        chooseService.setLayoutParams(CommonLayouts.FULL_SCREEN);
        chooseService.setTextSize(20);
        mAccountAdapter = new AccountAdapter(getActivity());
        mAccountList = new ListView(getActivity());
        mAccountList.setAdapter(mAccountAdapter);
        mAccountList.setPadding(6, 10, 6, 0);
        mAccountList.setLayoutParams(CommonLayouts.FULL_SCREEN);
        mAccountList.setEmptyView(chooseService);

        /** Put it together **/
        window.addView(socialBox);
        window.addView(mAccountList);
        window.addView(chooseService);

        initialize();

        return window;
    }

    void initialize() {
        MMyAccount[] accounts = mAccountManager.getClaimedAccounts(ACCOUNT_TYPE_GOOGLE);
        for (MMyAccount account : accounts) {
            mAccountAdapter.add(account);

            Message m = sAccountLooperThread.obtainMessage();
            m.what = MSG_CONNECT_GOOGLE;
            Job job = new AccountLooperThread.Job();
            job.mAccount = account.accountName_;
            job.mDialog = this;
            m.obj = job;
            sAccountLooperThread.sendMessage(m);
        }

        accounts = mAccountManager.getClaimedAccounts(ACCOUNT_TYPE_FACEBOOK);
        for (MMyAccount account : accounts) {
            mAccountAdapter.add(account);

            Message m = sAccountLooperThread.obtainMessage();
            m.what = MSG_CONNECT_FB;
            Job job = new AccountLooperThread.Job();
            job.mId = account.id_;
            job.mDialog = this;
            m.obj = job;
            sAccountLooperThread.sendMessage(m);
        }

        accounts = mAccountManager.getMyAccounts(ACCOUNT_TYPE_PHONE);
        for (MMyAccount account : accounts) {
            mAccountAdapter.add(account);

            Message m = sAccountLooperThread.obtainMessage();
            m.what = MSG_CONNECT_PHONE;
            Job job = new AccountLooperThread.Job();
            job.mId = account.id_;
            job.mAccount = account.accountName_;
            job.mDialog = this;
            m.obj = job;
            sAccountLooperThread.sendMessage(m);
        }
    }

    public static class AccountDetails {
        public String principal;
        public String accountName;
        public String accountType;
        public boolean owned;

        public AccountDetails(String principal, String accountName, String accountType, boolean owned) {
            this.principal = principal;
            this.accountName = accountName;
            this.accountType = accountType;
            this.owned = owned;
        }
    }

    /**
     * Adds an account to the local database. Must not be called on the main thread.
     */
    public static MMyAccount addAccountToDatabase(Activity activity, AccountDetails accountDetails) {
        SQLiteOpenHelper databaseSource = App.getDatabaseSource(activity);
        IdentitiesManager im = new IdentitiesManager(databaseSource);
        MyAccountManager am = new MyAccountManager(databaseSource);
        DeviceManager dm = new DeviceManager(databaseSource);
        FeedManager fm = new FeedManager(databaseSource);

        String accountType = accountDetails.accountType;
        String accountName = accountDetails.accountName;
        String principal = accountDetails.principal;
        boolean owned = accountDetails.owned;
        IBIdentity ibid;
        if (accountType.equals(ACCOUNT_TYPE_GOOGLE)) {
            ibid = new IBIdentity(Authority.Email, principal, 0);
        } else if (accountType.equals(ACCOUNT_TYPE_FACEBOOK)) {
            ibid = new IBIdentity(Authority.Facebook, principal, 0);
        } else if (accountType.equals(ACCOUNT_TYPE_PHONE)) {
            ibid = new IBIdentity(Authority.PhoneNumber, principal, 0);
        } else {
            throw new RuntimeException("Unsupported account type " + accountType);
        }

        SQLiteDatabase db = databaseSource.getWritableDatabase();
        db.beginTransaction();
        try {
            // Ensure identity in the database
            MIdentity id = im.getIdentityForIBHashedIdentity(ibid);
            //don't repeatedly add profile broadcast groups or do any
            //of this processing if the account is already owned.
            if (id != null && id.owned_) {
                return null;
            }

            MIdentity original = im.getOwnedIdentities().get(0);
            //if this identity wasnt already in the contact book, we need to update it
            if (id == null) {
                id = new MIdentity();
                populateMyNewIdentity(activity, principal, im, ibid, id, original, owned);
                im.insertIdentity(id);
            } else {
                populateMyNewIdentity(activity, principal, im, ibid, id, original, owned);
                im.updateIdentity(id);
            }

            im.updateMyProfileName(activity, id.musubiName_, false);
            im.updateMyProfileThumbnail(activity, id.musubiThumbnail_, false);

            // Ensure account entry exists
            MMyAccount account = am.lookupAccount(accountName, accountType);
            if (account == null) {
                //create the account
                account = new MMyAccount();
                account.accountName_ = accountName;
                account.accountType_ = accountType;
                account.identityId_ = id.id_;
                am.insertAccount(account);
            } else {
                account.identityId_ = id.id_;
                am.updateAccount(account);
            }

            // For accounts linked to identities that are not yet owned,
            // skip further initialization
            if (owned) {
                MDevice dev = dm.getDeviceForName(id.id_, dm.getLocalDeviceName());
                // Ensure device exists
                if (dev == null) {
                    dev = new MDevice();
                    dev.deviceName_ = dm.getLocalDeviceName();
                    dev.identityId_ = id.id_;
                    dm.insertDevice(dev);
                }
                //this feed will contain all members who should receive
                //a profile for the account because of a friend introduction
                MFeed provisional = new MFeed();
                provisional.name_ = MFeed.PROVISONAL_WHITELIST_FEED_NAME;
                provisional.type_ = MFeed.FeedType.ASYMMETRIC;
                fm.insertFeed(provisional);
                //XXX
                //TODO: in other places in the code, we should be pruning the
                //provisional whitelist feed as people become whitelisted..

                //these get inserted for owned identities to allow profile
                //broadcasts to go out
                MMyAccount provAccount = new MMyAccount();
                provAccount.accountName_ = MMyAccount.PROVISIONAL_WHITELIST_ACCOUNT;
                provAccount.accountType_ = MMyAccount.INTERNAL_ACCOUNT_TYPE;
                provAccount.identityId_ = id.id_;
                provAccount.feedId_ = provisional.id_;
                am.insertAccount(provAccount);

                //this feed will contain all members who should receive
                //a profile for the account because they are whitelisted
                //and contacted you on one of your accounts.
                MFeed accountBroadcastFeed = new MFeed();
                accountBroadcastFeed.name_ = MFeed.LOCAL_WHITELIST_FEED_NAME;
                accountBroadcastFeed.type_ = MFeed.FeedType.ASYMMETRIC;
                fm.insertFeed(accountBroadcastFeed);

                MMyAccount localAccount = new MMyAccount();
                localAccount.accountName_ = MMyAccount.LOCAL_WHITELIST_ACCOUNT;
                localAccount.accountType_ = MMyAccount.INTERNAL_ACCOUNT_TYPE;
                localAccount.identityId_ = id.id_;
                localAccount.feedId_ = accountBroadcastFeed.id_;
                am.insertAccount(localAccount);

                db.setTransactionSuccessful();

                ContentResolver resolver = activity.getContentResolver();
                // Notify interested services (identity available makes AMQP wake up for example)
                resolver.notifyChange(MusubiService.OWNED_IDENTITY_AVAILABLE, null);
                resolver.notifyChange(MusubiService.MY_PROFILE_UPDATED, null);

                // Makes key update wake up
                resolver.notifyChange(MusubiService.AUTH_TOKEN_REFRESH, null);
                WizardStepHandler.accomplishTask(activity, WizardStepHandler.TASK_LINK_ACCOUNT);

                App.getUsageMetrics(activity).report(MusubiMetrics.ACCOUNT_CONNECTED, account.accountType_);
            } else {
                db.setTransactionSuccessful();
            }
            return account;
        } finally {
            db.endTransaction();
        }
    }

    private static void populateMyNewIdentity(Activity activity, String accountName, IdentitiesManager im,
            IBIdentity ibid, MIdentity id, MIdentity original, boolean owned) {
        //its ours and we are on the network
        id.claimed_ = true;
        id.owned_ = owned;
        id.whitelisted_ = true;
        id.hasSentEmail_ = true;
        //set up the identity data
        id.principal_ = accountName;
        id.type_ = ibid.authority_;
        id.principalHash_ = ibid.hashed_;
        id.principalShortHash_ = mobisocial.musubi.util.Util.shortHash(ibid.hashed_);
        if (owned) {
            //mark us for one way push
            id.sentProfileVersion_ = 1;
            //clone the profile fields
            id.musubiName_ = original.musubiName_;
            id.musubiThumbnail_ = im.getMusubiThumbnail(original);
            id.receivedProfileVersion_ = original.receivedProfileVersion_;
            //force some kind of name to be set
            if (id.type_ == Authority.Facebook) {
                Facebook facebook = getFacebookInstance(activity);
                FacebookFriendFetcher fetcher = new FacebookFriendFetcher(facebook);
                String name = fetcher.getLoggedinUserName();
                byte[] thumbnail = fetcher.getLoggedinUserPhoto();
                if (name != null) {
                    id.name_ = name;
                    if (id.musubiName_ == null) {
                        id.musubiName_ = name;
                    }
                }
                if (thumbnail != null) {
                    id.thumbnail_ = thumbnail;
                    if (id.musubiThumbnail_ == null) {
                        id.musubiThumbnail_ = thumbnail;
                    }
                }
            } else {
                if (id.musubiName_ == null) {
                    //use the real name extracted from facebook or the local contact
                    //book if possible
                    if (id.musubiName_ == null) {
                        id.musubiName_ = id.name_;
                    }
                    //otherwise just make up something cute
                    if (id.musubiName_ == null) {
                        id.musubiName_ = UiUtil.randomFunName();
                    }
                }
                if (id.musubiThumbnail_ == null) {
                    id.musubiThumbnail_ = id.thumbnail_;
                }
            }
        }
    }

    View.OnClickListener mGoogleClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Account[] accounts = AccountManager.get(mActivity).getAccountsByType(ACCOUNT_TYPE_GOOGLE);
            if (accounts == null) {
                accounts = new Account[0];
            }
            ((InstrumentedActivity) mActivity)
                    .showDialog(GoogleAccountPickerDialog.newInstance(AccountLinkDialog.this, accounts));
        }
    };

    static Facebook facebook = new Facebook(FACEBOOK_APP_ID);

    public static Facebook getFacebookInstance(Context context) {
        // Load the current instance if available
        SessionStore.restore(facebook, context);
        return facebook;
    }

    /*
     * Refresh the current known Facebook token asynchronously.
     */
    public static void refreshFacebookToken(final Context context) {
        new Thread() {
            @Override
            public void run() {
                // Get a new instance if the current one is null
                final Facebook facebook = getFacebookInstance(context);
                // Extend the token as needed
                facebook.extendAccessTokenIfNeeded(context, new ServiceListener() {
                    @Override
                    public void onComplete(Bundle values) {
                        // If the token retrieval was successful, report it and save the state
                        long expiration = values.getLong(Facebook.EXPIRES);
                        Log.i(TAG, "New Facebook token expiration time: " + expiration);
                        SessionStore.save(facebook, context);
                    }

                    @Override
                    public void onFacebookError(FacebookError e) {
                        Log.i(TAG, "Facebook API error", e);
                    }

                    @Override
                    public void onError(Error e) {
                        Log.w(TAG, "Error on call to Facebook", e);
                    }
                });
            }
        }.start();
    }

    public void postActivityToFeed() {
        SharedPreferences p = mActivity.getSharedPreferences(SettingsActivity.PREFS_NAME, 0);
        if (p.getBoolean(SettingsActivity.PREF_ALREADY_SAW_FACEBOOK_POST, false)) {
            return;
        }
        p.edit().putBoolean(SettingsActivity.PREF_ALREADY_SAW_FACEBOOK_POST, true).commit();
        Bundle post = new Bundle();
        post.putString("picture",
                "https://lh5.ggpht.com/hRTJJv7H9dpLXhHTTqiiNY2DD2wWO0hZFWEWPv1g-WArcUYLsWk-aQYUS0UgZfVIqtXm=w124");
        post.putString("link", "https://market.android.com/details?id=mobisocial.musubi");
        post.putString("caption", "Musubi");
        post.putString("description", "I'm using Musubi, a social network without the Cloud for smartphones.");
        Facebook facebook = getFacebookInstance(mActivity);
        facebook.dialog(mActivity, "feed", post, new Facebook.DialogListener() {

            @Override
            public void onComplete(Bundle values) {
                Log.i(TAG, values.toString());
            }

            @Override
            public void onFacebookError(FacebookError e) {
                Log.e(TAG, e.toString());
            }

            @Override
            public void onError(DialogError e) {
                Log.e(TAG, e.toString());
            }

            @Override
            public void onCancel() {
                Log.i(TAG, "User canceled post to Facebook.");
            }

        });
    }

    /**
     * Returns the currently-active Facebook token for the local user,
     * or null if none available.
     */
    public static String getActiveFacebookToken(Context context) {
        Facebook facebook = getFacebookInstance(context);
        if (facebook.isSessionValid()) {
            return facebook.getAccessToken();
        }
        return null;
    }

    /**
     * Returns a current token for the given Google account, or
     * null if a token isn't available without user interaction.
     */
    public static String silentBlockForGoogleToken(Context context, String accountName) throws IOException {
        Account account = new Account(accountName, ACCOUNT_TYPE_GOOGLE);
        AccountManager accountManager = AccountManager.get(context);
        // Need to get cached token, invalidate it, then get the token again
        String token = blockForCachedGoogleToken(context, account, accountManager);
        if (token != null) {
            accountManager.invalidateAuthToken(ACCOUNT_TYPE_GOOGLE, token);
        }
        token = blockForCachedGoogleToken(context, account, accountManager);
        return token;
    }

    private static String blockForCachedGoogleToken(Context context, Account account, AccountManager accountManager)
            throws IOException {
        AccountManagerFuture<Bundle> future = accountManager.getAuthToken(account, GOOGLE_OAUTH_SCOPE, true, null,
                null);
        if (future != null) {
            try {
                Bundle result = future.getResult();
                if (result.containsKey(AccountManager.KEY_AUTHTOKEN)) {
                    String cachedGoogleToken = result.getString(AccountManager.KEY_AUTHTOKEN);
                    return cachedGoogleToken;
                }
            } catch (IOException e) {
                throw e;
            } catch (Exception e) {
            }
        }
        return null;
    }

    private AccountManagerFuture<Bundle> tryGoogleAccount(Context context, String accountName) {
        if (accountName == null) {
            Log.e(TAG, "No selected Google account.");
            return null;
        }

        Account account = new Account(accountName, ACCOUNT_TYPE_GOOGLE);
        AccountManager accountManager = AccountManager.get(context);
        return accountManager.getAuthToken(account, GOOGLE_OAUTH_SCOPE, true,
                new GoogleAccountManagerCallback(account), null);
    }

    class GoogleAccountManagerCallback implements AccountManagerCallback<Bundle> {
        private Account mAccount;

        public GoogleAccountManagerCallback(Account account) {
            mAccount = account;
        }

        @Override
        public void run(AccountManagerFuture<Bundle> future) {
            String authToken = null;
            //callback can happen after the dialog is gone...
            try {
                Bundle bundle = future.getResult();
                authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                if (authToken != null) {
                    Message m = sAccountLooperThread.obtainMessage();
                    m.what = MSG_ADD_TO_DATABASE;
                    Job job = new AccountLooperThread.Job();
                    job.mDialog = AccountLinkDialog.this;
                    job.mDetails = new AccountDetails(mAccount.name, mAccount.name, mAccount.type, true);
                    m.obj = job;
                    sAccountLooperThread.sendMessage(m);
                } else if (bundle.containsKey(AccountManager.KEY_INTENT)) {
                    Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
                    intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
                    mPendingAccountType = mAccount.type;
                    mPendingAccountName = mAccount.name;
                    startActivityForResult(intent, REQUEST_GOOGLE_AUTHENTICATE);
                } else {
                    // handle errors in one block
                    throw new Exception();
                }
            } catch (Exception e) {
                if (mAccount.type != null) {
                    toast("Failed to connect.");
                    mAccountAdapter.setAccountStatus(mAccount.name, mAccount.type, AccountStatus.ERROR);
                } else {
                    toast("Failed to connect " + mAccount.name + ".");
                }

                Log.i(TAG, "Invalidating auth token from error " + authToken);
                try {
                    AccountManager accountManager = AccountManager.get(mActivity);
                    accountManager.invalidateAuthToken(mAccount.type, authToken);
                } catch (Exception e2) {
                }
            }
        }
    };

    View.OnClickListener mFacebookClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Facebook facebook = getFacebookInstance(mActivity);
            if (!facebook.isSessionValid()) {
                facebook.authorize(mActivity, FACEBOOK_PERMISSIONS, REQUEST_FACEBOOK, mFacebookCallback);
            }
        }
    };

    Facebook.DialogListener mFacebookCallback = new Facebook.DialogListener() {
        @Override
        public void onFacebookError(FacebookError e) {
            Log.e(TAG, "Facebook error", e);
            toast("Error connecting to Facebook.");
        }

        @Override
        public void onError(DialogError e) {
            Log.e(TAG, "error", e);
            toast("Error connecting to Facebook.");
        }

        class FacebookLoadMyProfileTask extends AsyncTask<Void, Void, Void> {
            final Facebook facebook = getFacebookInstance(mActivity);
            Bundle mValues;
            Throwable mError = null;

            FacebookLoadMyProfileTask(Bundle values) {
                mValues = values;
            }

            @Override
            protected Void doInBackground(Void... params) {
                facebook.setAccessToken(mValues.getString(Facebook.TOKEN));
                facebook.setAccessExpiresIn(mValues.getString(Facebook.EXPIRES));
                try {
                    JSONObject json = Util.parseJson(facebook.request("me"));
                    String userId = json.getString("id");
                    String accountName = json.getString("email");
                    //String name = json.getString("name");
                    Log.d(TAG, "Facebook success");
                    SessionStore.save(facebook, mActivity);
                    Message m = sAccountLooperThread.obtainMessage();
                    m.what = MSG_ADD_TO_DATABASE;
                    Job job = new AccountLooperThread.Job();
                    job.mDialog = AccountLinkDialog.this;
                    job.mDetails = new AccountDetails(userId, accountName, ACCOUNT_TYPE_FACEBOOK, true);
                    m.obj = job;
                    sAccountLooperThread.sendMessage(m);
                    mActivity.getContentResolver().notifyChange(MusubiService.FACEBOOK_FRIEND_REFRESH, null);
                } catch (JSONException e) {
                    Log.e(TAG, "JSONException", e);
                } catch (Throwable e) {
                    Log.e(TAG, "Failed to log in with facebook", e);
                    mError = e;
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void result) {
                if (mError != null)
                    toast("Couldn't connect to Facebook.");
                else
                    postActivityToFeed();
            };
        }

        @Override
        public void onComplete(final Bundle values) {
            new FacebookLoadMyProfileTask(values).execute();
        }

        @Override
        public void onCancel() {
            Log.i(TAG, "user cancelled facebook auth");
        }
    };

    View.OnClickListener mPhoneClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // Show the phone number from the API if there is a valid one
            TelephonyManager tMgr = (TelephonyManager) mActivity.getSystemService(Context.TELEPHONY_SERVICE);
            Set<Account> accounts = new HashSet<Account>();
            String phoneNumber = tMgr.getLine1Number();
            if (phoneNumber != null && validatePhoneNumber(phoneNumber) != null) {
                Log.d(TAG, "Phone number: " + phoneNumber);
                accounts.add(new Account(validatePhoneNumber(phoneNumber), ACCOUNT_TYPE_PHONE));
            }
            // Also show any previously claimed phone numbers
            MMyAccount[] claimedAccounts = mAccountManager.getClaimedAccounts(ACCOUNT_TYPE_PHONE);
            for (MMyAccount acc : claimedAccounts) {
                accounts.add(new Account(acc.accountName_, acc.accountType_));
            }
            ((InstrumentedActivity) mActivity)
                    .showDialog(PhoneNumberPickerDialog.newInstance(AccountLinkDialog.this, accounts));
        }
    };

    public static class PhoneNumberPickerDialog extends DialogFragment implements DialogInterface.OnClickListener {
        private static final String ENTER_ALTERNATE_NUMBER = "Enter a phone number";
        Activity mActivity;
        AccountLinkDialog mTarget;

        public static PhoneNumberPickerDialog newInstance(AccountLinkDialog target, Set<Account> accounts) {
            PhoneNumberPickerDialog frag = new PhoneNumberPickerDialog();
            Bundle args = new Bundle();
            String[] accountNames = new String[accounts.size() + 1];
            int i = 0;
            for (Account a : accounts) {
                accountNames[i++] = a.name;
            }
            accountNames[i++] = ENTER_ALTERNATE_NUMBER;
            args.putStringArray("accountNames", accountNames);
            frag.setArguments(args);
            frag.setTargetFragment(target, REQUEST_PHONE_NUMBER);
            return frag;
        }

        public PhoneNumberPickerDialog() {
            super();
        }

        @Override
        public void onAttach(SupportActivity activity) {
            super.onAttach(activity);
            mActivity = activity.asActivity();
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(mActivity).setTitle("Connect Phone Number")
                    .setItems(getArguments().getStringArray("accountNames"), this).create();
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {
            String account = getArguments().getStringArray("accountNames")[which];
            if (account.equals(ENTER_ALTERNATE_NUMBER)) {
                // TODO: this dialog comes back, but it shouldn't
                dismiss();
                ((InstrumentedActivity) mActivity)
                        .showDialog(EnterPhoneNumberDialog.newInstance((AccountLinkDialog) getTargetFragment()));
            } else {
                Intent data = new Intent();
                data.putExtra(AccountManager.KEY_ACCOUNT_NAME, account);
                getTargetFragment().onActivityResult(REQUEST_PHONE_NUMBER, Activity.RESULT_OK, data);
                dismiss();
            }
        }
    }

    public static class EnterPhoneNumberDialog extends DialogFragment implements View.OnClickListener {
        Activity mActivity;
        Dialog mDialog;

        public static EnterPhoneNumberDialog newInstance(AccountLinkDialog target) {
            EnterPhoneNumberDialog frag = new EnterPhoneNumberDialog();
            frag.setTargetFragment(target, REQUEST_PHONE_NUMBER);
            return frag;
        }

        public EnterPhoneNumberDialog() {
            super();
        }

        @Override
        public void onAttach(SupportActivity activity) {
            super.onAttach(activity);
            mActivity = activity.asActivity();
            Dialog numberDlg = new Dialog(mActivity);
            numberDlg.setContentView(R.layout.phone_number_dialog);
            numberDlg.setTitle("Enter a Phone Number");
            Button go = (Button) numberDlg.findViewById(R.id.button1);
            go.setOnClickListener(this);
            mDialog = numberDlg;
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return mDialog;
        }

        @Override
        public void onClick(View v) {
            EditText country = (EditText) mDialog.findViewById(R.id.editText1);
            EditText primary = (EditText) mDialog.findViewById(R.id.editText2);
            String number = country.getText().toString() + primary.getText().toString();
            Log.d(TAG, "Entered number: " + number);
            if (number != "") {
                Intent data = new Intent();
                data.putExtra(AccountManager.KEY_ACCOUNT_NAME, number);
                getTargetFragment().onActivityResult(REQUEST_PHONE_NUMBER, Activity.RESULT_OK, data);
            }
            dismiss();
        }
    }

    public static class GoogleAccountPickerDialog extends DialogFragment
            implements DialogInterface.OnClickListener {
        private static final String REGISTER_AN_ACCOUNT = "Use other email address via Google";
        private static final String ADD_AN_ACCOUNT = "Add a Google account";

        public static GoogleAccountPickerDialog newInstance(AccountLinkDialog target, Account[] accounts) {
            GoogleAccountPickerDialog frag = new GoogleAccountPickerDialog();
            Bundle args = new Bundle();
            String[] accountNames = new String[accounts.length + 2];
            int i = 0;
            for (Account a : accounts) {
                accountNames[i++] = a.name;
            }
            accountNames[i++] = ADD_AN_ACCOUNT;
            accountNames[i++] = REGISTER_AN_ACCOUNT;
            args.putStringArray("accountNames", accountNames);
            frag.setArguments(args);
            frag.setTargetFragment(target, REQUEST_GOOGLE_ACCOUNT);
            return frag;
        }

        public GoogleAccountPickerDialog() {

        }

        Activity mActivity;

        @Override
        public void onAttach(SupportActivity activity) {
            super.onAttach(activity);
            mActivity = activity.asActivity();
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(mActivity).setTitle("Connect Account")
                    .setItems(getArguments().getStringArray("accountNames"), this).create();
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {
            String account = getArguments().getStringArray("accountNames")[which];
            if (account.equals(ADD_AN_ACCOUNT)) {
                try {
                    Intent intent = new Intent();
                    intent.setClassName("com.google.android.gsf",
                            "com.google.android.gsf.login.AccountIntroActivity");
                    mActivity.startActivity(intent);
                    dismiss();
                    return;
                } catch (Throwable t) {
                }
                try {
                    Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
                    mActivity.startActivity(intent);
                    dismiss();
                    return;
                } catch (Throwable t) {
                }
                Toast.makeText(mActivity, "Failed to invoke Google account services", Toast.LENGTH_SHORT).show();
                dismiss();
                return;
            } else if (account.equals(REGISTER_AN_ACCOUNT)) {
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://accounts.google.com/NewAccount"));
                mActivity.startActivity(intent);
                dismiss();
                return;
            } else {
                Intent data = new Intent();
                data.putExtra(EXTRA_ACCOUNT, account);
                getTargetFragment().onActivityResult(REQUEST_GOOGLE_ACCOUNT, Activity.RESULT_OK, data);
            }
        }
    }

    /*
     * In some cases, the user may want Musubi to resend text verification
     */
    public static class RetryPhoneDialog extends DialogFragment {
        Set<MPendingIdentity> mPendingIdents;
        PendingIdentityManager mManager;
        Activity mActivity;

        public static RetryPhoneDialog newInstance(PendingIdentityManager manager,
                Set<MPendingIdentity> pendingIdents) {
            RetryPhoneDialog d = new RetryPhoneDialog(manager, pendingIdents);
            return d;
        }

        public RetryPhoneDialog(PendingIdentityManager manager, Set<MPendingIdentity> pendingIdents) {
            super();
            mPendingIdents = pendingIdents;
        }

        @Override
        public void onAttach(SupportActivity activity) {
            super.onAttach(activity);
            mActivity = activity.asActivity();
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(mActivity).setTitle("Resend Verification")
                    .setMessage("Would you like Musubi to resend the verification text?")
                    .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            for (MPendingIdentity pendingIdent : mPendingIdents) {
                                if (pendingIdent.notified_) {
                                    pendingIdent.notified_ = false;
                                    mManager.updateIdentity(pendingIdent);
                                }
                            }
                            mActivity.getContentResolver().notifyChange(MusubiService.AUTH_TOKEN_REFRESH, null);
                            dismiss();
                        }
                    }).setNegativeButton("No", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dismiss();
                        }
                    }).create();
        }
    }

    private String validatePhoneNumber(String original) {
        // Strip non-numeric characters and leading zeros
        original.replaceAll("[^\\d]", "");
        long numerical = Long.parseLong(original);

        // Check to make sure the length is feasible
        String converted = Long.toString(numerical);
        if (converted.length() < SHORTEST_PHONE_NUMBER || converted.length() > LONGEST_PHONE_NUMBER) {
            return null;
        } else {
            return "+" + converted;
        }
    }

    private void setupPhoneAccount(String phoneNumber) {
        // See if this phone number is known, and add it if it isn't
        Log.d(TAG, "Setting up " + phoneNumber);
        IBIdentity toAdd = new IBIdentity(Authority.PhoneNumber, phoneNumber, 0);
        SQLiteOpenHelper databaseSource = App.getDatabaseSource(mActivity);
        IdentitiesManager im = new IdentitiesManager(databaseSource);
        MIdentity id = im.getIdentityForIBHashedIdentity(toAdd);
        if (id == null) {
            id = new MIdentity();
            id.claimed_ = false;
            id.owned_ = false;
            id.whitelisted_ = true;
            id.hasSentEmail_ = true;
            //set up the identity data
            id.principal_ = toAdd.principal_;
            id.type_ = toAdd.authority_;
            id.principalHash_ = toAdd.hashed_;
            id.principalShortHash_ = mobisocial.musubi.util.Util.shortHash(toAdd.hashed_);
            im.insertIdentity(id);
        }

        // Get the corresponding pending identity
        PendingIdentityManager pManager = new PendingIdentityManager(databaseSource);
        Set<MPendingIdentity> pendingIdents = pManager.lookupIdentities(id.id_);

        if (!id.owned_) {
            // Add this account so that it can be tracked
            Message m = sAccountLooperThread.obtainMessage();
            m.what = MSG_ADD_TO_DATABASE;
            Job job = new AccountLooperThread.Job();
            job.mDialog = AccountLinkDialog.this;
            job.mDetails = new AccountDetails(toAdd.principal_, toAdd.principal_, ACCOUNT_TYPE_PHONE, id.owned_);
            m.obj = job;
            sAccountLooperThread.sendMessage(m);

            MPendingIdentity pendingIdent = pManager.lookupIdentity(id.id_, toAdd.temporalFrame_);
            if (pendingIdent == null) {
                pendingIdent = pManager.fillPendingIdentity(id.id_, toAdd.temporalFrame_);
                pManager.insertIdentity(pendingIdent);
            }
            pendingIdents.add(pendingIdent);
        }

        boolean anyUnnotified = false;
        for (MPendingIdentity pident : pendingIdents) {
            if (!pident.notified_) {
                anyUnnotified = true;
            }
        }

        // If any of these are unnotified, ask to resend
        if (!anyUnnotified) {
            ((InstrumentedActivity) mActivity).showDialog(RetryPhoneDialog.newInstance(pManager, pendingIdents));
        } else {
            // Start the verification process
            mActivity.getContentResolver().notifyChange(MusubiService.AUTH_TOKEN_REFRESH, null);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (data == null) {
            return;
        }
        switch (requestCode) {
        case REQUEST_GOOGLE_ACCOUNT:
            if (resultCode == Activity.RESULT_OK) {
                String account = data.getStringExtra(EXTRA_ACCOUNT);
                tryGoogleAccount(mActivity, account);
            }
            break;
        case REQUEST_GOOGLE_AUTHENTICATE:
            //this doesnt really seem to be called because of the account manager having a
            //weird api
            if (resultCode == Activity.RESULT_OK) {
                String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                tryGoogleAccount(mActivity, accountName);
            }
            break;
        case REQUEST_FACEBOOK:
            Log.d(TAG, "Authorizing Facebook callback");
            Facebook facebook = getFacebookInstance(mActivity);
            facebook.authorizeCallback(requestCode, resultCode, data);
            break;
        case REQUEST_PHONE_NUMBER:
            if (resultCode == Activity.RESULT_OK) {
                String phoneNumber = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                phoneNumber = validatePhoneNumber(phoneNumber);
                setupPhoneAccount(phoneNumber);
            }
            break;
        }
    }

    static class AccountLooperThread extends Thread {
        private Handler mHandler;

        static class Job {
            public long mId;
            protected AccountDetails mDetails;
            AccountLinkDialog mDialog;
            String mAccount;
        }

        public void run() {
            Looper.prepare();

            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    final Job job = (Job) msg.obj;
                    switch (msg.what) {
                    case MSG_CONNECT_GOOGLE:
                        String account = job.mAccount;
                        String token = null;
                        try {
                            token = silentBlockForGoogleToken(job.mDialog.mActivity, account);
                        } catch (IOException e) {
                            Log.i(TAG, "Could not get a Google token likely due to a network error");
                        }
                        AccountStatus status = (token == null) ? AccountStatus.ERROR : AccountStatus.CONNECTED;
                        job.mDialog.mAccountAdapter.setAccountStatus(account, ACCOUNT_TYPE_GOOGLE, status);
                        break;
                    case MSG_CONNECT_FB:
                        try {
                            // Make sure we can connect, but we don't care about the result.
                            Facebook facebook = getFacebookInstance(job.mDialog.mActivity);
                            Util.parseJson(facebook.request("me"));
                            job.mDialog.mAccountAdapter.setAccountStatus(job.mId, AccountStatus.CONNECTED);
                        } catch (Throwable e) {
                            Log.e(TAG, "silent facebook error", e);
                            job.mDialog.mAccountAdapter.setAccountStatus(job.mId, AccountStatus.ERROR);
                        }
                        break;
                    case MSG_CONNECT_PHONE:
                        SQLiteOpenHelper databaseSource = App.getDatabaseSource(job.mDialog.mActivity);
                        IdentitiesManager im = new IdentitiesManager(databaseSource);
                        MIdentity mid = im.getIdentityForIBHashedIdentity(
                                new IBIdentity(Authority.PhoneNumber, job.mAccount, 0));
                        if (mid != null) {
                            PendingIdentityManager pManager = new PendingIdentityManager(databaseSource);
                            int unnotified = pManager.getUnnotifiedIdentities(mid.id_).size();
                            if (mid.owned_ && unnotified == 0) {
                                job.mDialog.mAccountAdapter.setAccountStatus(job.mId, AccountStatus.CONNECTED);
                            } else if (unnotified == 0) {
                                job.mDialog.mAccountAdapter.setAccountStatus(job.mId, AccountStatus.ERROR);
                            } else {
                                job.mDialog.mAccountAdapter.setAccountStatus(job.mId, AccountStatus.UNVERIFIED);
                            }
                        }
                        break;
                    case MSG_ADD_TO_DATABASE:
                        final Activity activity = job.mDialog.mActivity;
                        final boolean previouslyOwned = new IdentitiesManager(App.getDatabaseSource(activity))
                                .hasConnectedAccounts();
                        final AccountDetails details = job.mDetails;
                        final MMyAccount dbRow = AccountLinkDialog.addAccountToDatabase(activity, details);
                        // Update ui
                        if (dbRow != null) {
                            activity.runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    job.mDialog.mAccountAdapter.add(dbRow);
                                    if (details.owned) {
                                        job.mDialog.mAccountAdapter.setAccountStatus(dbRow.id_,
                                                AccountStatus.CONNECTED);
                                    } else {
                                        // In case an account is added without knowing if we
                                        // can get user keys for it
                                        job.mDialog.mAccountAdapter.setAccountStatus(dbRow.id_,
                                                AccountStatus.UNVERIFIED);
                                    }

                                    if (!previouslyOwned) {
                                        new AddressBookUpdateHandler.AddressBookImportTask(activity).execute();
                                    }
                                }
                            });
                        }
                    }
                }
            };

            synchronized (this) {
                notify();
            }

            Looper.loop();
        }

        public Message obtainMessage() {
            while (mHandler == null) {
                try {
                    // watch for startup race condition
                    synchronized (this) {
                        wait(50);
                    }
                } catch (InterruptedException e) {
                }
            }
            return mHandler.obtainMessage();
        }

        public void sendMessage(Message msg) {
            mHandler.sendMessage(msg);
        }
    }

    class AccountAdapter extends ArrayAdapter<MMyAccount> {
        final Context mContext;
        final Map<Long, AccountStatus> mAccountStatus;
        final TLongSet mKnownAccounts;

        public AccountAdapter(Context context) {
            super(context, android.R.layout.simple_list_item_1);
            mContext = context;
            mAccountStatus = new HashMap<Long, AccountStatus>();
            mKnownAccounts = new TLongHashSet();
        }

        @Override
        public View getView(int position, View view, ViewGroup parent) {
            if (view == null) {
                view = getAccountView();
            }

            MMyAccount account = getItem(position);
            int iconResource = R.drawable.icon;
            if (ACCOUNT_TYPE_GOOGLE.equals(account.accountType_)) {
                iconResource = R.drawable.google;
            } else if (ACCOUNT_TYPE_FACEBOOK.equals(account.accountType_)) {
                iconResource = R.drawable.facebook;
            }
            ((ImageView) view.findViewById(R.id.icon)).setImageResource(iconResource);
            ((TextView) view.findViewById(R.id.text)).setText(account.accountName_);
            String status = statusForAccount(account.id_);
            ((TextView) view.findViewById(R.id.status)).setText(status);
            return view;
        }

        View getAccountView() {
            LinearLayout frame = new LinearLayout(mContext);
            frame.setOrientation(LinearLayout.HORIZONTAL);

            ImageView icon = new ImageView(mContext);
            int size = 60;
            icon.setLayoutParams(new LinearLayout.LayoutParams(size, size));
            icon.setId(R.id.icon);
            icon.setPadding(3, 6, 6, 0);

            LinearLayout accountView = new LinearLayout(mContext);
            accountView.setOrientation(LinearLayout.VERTICAL);

            TextView label = new TextView(mContext);
            label.setTextSize(20);
            label.setId(R.id.text);
            accountView.addView(label);

            TextView status = new TextView(mContext);
            status.setTextSize(14);
            status.setId(R.id.status);
            accountView.addView(status);

            frame.addView(icon);
            frame.addView(accountView);
            return frame;
        }

        public synchronized void setAccountStatus(long accountId, AccountStatus status) {
            mAccountStatus.put(accountId, status);
            mActivity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    notifyDataSetChanged();
                }
            });
        }

        @Override
        public synchronized void add(MMyAccount account) {
            if (!mKnownAccounts.contains(account.id_)) {
                mKnownAccounts.add(account.id_);
                super.add(account);
            }
        };

        public synchronized void setAccountStatus(String accountName, String accountType, AccountStatus status) {
            MMyAccount account = mAccountManager.lookupAccount(accountName, accountType);
            if (account == null) {
                Log.e(TAG, "Could not find account " + accountName + "/" + accountType);
                return;
            }
            setAccountStatus(account.id_, status);
        }

        String statusForAccount(long accountId) {
            AccountStatus val = mAccountStatus.get(accountId);
            if (val == null) {
                return TEXT_CHECKING;
            }
            switch (val) {
            case CONNECTED:
                return TEXT_CONNECTED;
            case ERROR:
                return TEXT_ERROR_CONNECTING;
            case UNVERIFIED:
                return TEXT_UNVERIFIED;
            case PENDING:
            default:
                return TEXT_CHECKING;
            }
        }
    }

    void toast(final String text) {
        Toast.makeText(mActivity, text, Toast.LENGTH_SHORT).show();
    }
}