com.xorcode.andtweet.TwitterUser.java Source code

Java tutorial

Introduction

Here is the source code for com.xorcode.andtweet.TwitterUser.java

Source

/**
 * Copyright (C) 2010-2011 yvolk (Yuri Volkov), http://yurivolkov.com
 *
 * 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.xorcode.andtweet;

import static android.content.Context.MODE_PRIVATE;

import android.content.SharedPreferences;
import android.util.Log;

import java.io.File;
import java.net.SocketTimeoutException;
import java.util.regex.Pattern;
import java.util.Vector;

import com.xorcode.andtweet.data.MyPreferences;
import com.xorcode.andtweet.net.Connection;
import com.xorcode.andtweet.net.ConnectionAuthenticationException;
import com.xorcode.andtweet.net.ConnectionCredentialsOfOtherUserException;
import com.xorcode.andtweet.net.ConnectionException;
import com.xorcode.andtweet.net.ConnectionOAuth;
import com.xorcode.andtweet.net.ConnectionUnavailableException;
import com.xorcode.andtweet.util.MyLog;
import com.xorcode.andtweet.util.SharedPreferencesUtil;

import org.json.JSONObject;

/**
 * The object holds Twitter User's specific information including connection
 * TODO: Implement different data (tweets and their counters...) for different
 * Users.
 * 
 * @author Yuri Volkov
 */
public class TwitterUser {
    private static final String TAG = TwitterUser.class.getSimpleName();

    /**
     * Prefix of the user's Preferences file
     */
    public static final String FILE_PREFIX = "user_";

    /**
     * This is same name that is used in Twitter login
     */
    private String mUsername = "";

    /**
     * Is this object temporal?
     * true - in a case this user was not _ever_ authenticated
     */
    private boolean mIsTemporal = true;

    /**
     * Was this user authenticated last time _current_ credentials were verified?
     * CredentialsVerified.NEVER - after changes of "credentials": password/OAuth...
     */
    private CredentialsVerified mCredentialsVerified = CredentialsVerified.NEVER;

    private String mPrefsFileName = "";

    /**
     * Is this user authenticated with OAuth?
     */
    private boolean mOAuth = true;
    /**
     * This is defined by tweet server
     * Starting from 2010-09 twitter.com allows OAuth only
     */
    private boolean mCanChangeOAuth = false;

    private Connection mConnection = null;

    /**
     * NEVER - means that User was never successfully authenticated with current credentials,
     *      this is why we reset to state to NEVER every time credentials were changed.
     */
    public enum CredentialsVerified {
        NEVER, FAILED, SUCCEEDED;

        /*
         * Methods to persist in SharedPreferences
         */
        private static final String KEY = "credentials_verified";

        public static CredentialsVerified load(SharedPreferences sp) {
            int ind = sp.getInt(KEY, NEVER.ordinal());
            CredentialsVerified cv = CredentialsVerified.values()[ind];
            return cv;
        }

        public void save(SharedPreferences sp) {
            synchronized (sp) {
                SharedPreferences.Editor editor = sp.edit();
                put(editor);
                editor.commit();
            }
        }

        public void put(SharedPreferences.Editor editor) {
            editor.putInt(KEY, ordinal());
        }
    }

    public boolean getCredentialsPresent() {
        return getConnection().getCredentialsPresent(getSharedPreferences());
    }

    public CredentialsVerified getCredentialsVerified() {
        return mCredentialsVerified;
    }

    public void setCredentialsVerified(CredentialsVerified cv) {
        mCredentialsVerified = cv;
        mCredentialsVerified.save(getSharedPreferences());
    }

    public void saveAuthInformation(String token, String secret) {
        if (isOAuth()) {
            ConnectionOAuth conn = ((ConnectionOAuth) getConnection());
            conn.saveAuthInformation(getSharedPreferences(), token, secret);
        } else {
            Log.e(TAG, "saveAuthInformation is for OAuth only!");
        }
    }

    /**
     * Forget everything in order to reread from the sources if it will be needed
     */
    public static void forget() {
        mTu = null;
    }

    /**
     * Get current user instance
     * 
     * @param Context
     * @return TwitterUser
     */
    public static TwitterUser getTwitterUser() {
        return getTwitterUser(null, false);
    }

    /**
     * Get user instance based on supplied twitter_username. Globally stored User
     * preferences are being set for the user, 
     * including oauth, password. 
     * New User is being created if user with such twitter_username
     * didn't exist.
     * 
     * @param Context
     * @return TwitterUser
     */
    public static TwitterUser getAddEditTwitterUser(String username) {
        return getTwitterUser(username, true);
    }

    /**
     * Get (stored) user instance by explicitly provided username. This user
     * becomes current user (Global SharedPreferences are being updated).
     * 
     * @param Context
     * @param username in Twitter
     * @return TwitterUser
     */
    public static TwitterUser getTwitterUser(String username) {
        return getTwitterUser(username, false);
    }

    /** 
     * Array of TwitterUser objects
     */
    private static Vector<TwitterUser> mTu = null;

    /**
     * Get list of all Users, including temporary (never authenticated) user
     * for the purpose of using these "accounts" elsewhere. Value of
     * {@link #getCredentialsVerified()} is the main differentiator.
     * 
     * @param context
     * @return Array of users
     */
    public static TwitterUser[] list() {
        if (mTu == null) {
            Log.e(TAG, "Was not initialized");
            return null;
        } else {
            return mTu.toArray(new TwitterUser[mTu.size()]);
        }
    }

    /**
     * How many authenticated users are the list of accounts?
     * @return count
     */
    public static int countOfAuthenticatedUsers() {
        int count = 0;
        int ind = -1;

        for (ind = 0; ind < mTu.size(); ind++) {
            if (!mTu.elementAt(ind).isTemporal()) {
                count += 1;
                break;
            }
        }
        return count;
    }

    /**
     * Initialize internal static memory 
     * Initialize User's list if it wasn't initialized yet.
     * 
     * @param context
     */
    public static void initialize() {
        if (mTu == null) {
            mTu = new Vector<TwitterUser>();

            // Currently we don't hold user's list anywhere
            // So let's search user's files
            java.io.File prefsdir = new File(SharedPreferencesUtil.prefsDirectory(MyPreferences.getContext()));
            java.io.File files[] = prefsdir.listFiles();
            if (files != null) {
                for (int ind = 0; ind < files.length; ind++) {
                    if (files[ind].getName().startsWith(FILE_PREFIX)) {
                        String username = files[ind].getName().substring(FILE_PREFIX.length());
                        int indExtension = username.indexOf(".");
                        if (indExtension >= 0) {
                            username = username.substring(0, indExtension);
                        }
                        TwitterUser tu = new TwitterUser(username);
                        mTu.add(tu);
                    }
                }
            }
            MyLog.v(TAG, "User's list initialized, " + mTu.size() + " users");
        } else {
            MyLog.v(TAG, "Already initialized, " + mTu.size() + " users");
        }
    }

    /**
     * @param username
     * @return Name without path and extension
     */
    public static String prefsFileNameForUser(String username) {
        username = fixUsername(username);
        String fileName = FILE_PREFIX + username;
        return fileName;
    }

    /**
     * Factory of TwitterUser-s
     * 
     * @param Context
     * @param username in Twitter
     * @param copyGlobal globally stored User preferences are used, including:
     *            Username, OAuth, password. New User will be created if didn't
     *            exist yet.
     * @return TwitterUser - existed or newly created
     */
    private static TwitterUser getTwitterUser(String username, boolean copyGlobal) {
        // Find TwitterUser object for this user
        boolean found = false;
        int ind = -1;
        int indTemp = -1;
        TwitterUser tu = null;

        username = fixUsername(username);
        if (copyGlobal || (username.length() == 0)) {
            SharedPreferences dsp = MyPreferences.getDefaultSharedPreferences();
            username = fixUsername(dsp.getString(MyPreferences.KEY_TWITTER_USERNAME, ""));
        }
        for (ind = 0; ind < mTu.size(); ind++) {
            if (mTu.elementAt(ind).getUsername().compareTo(username) == 0) {
                found = true;
                break;
            }
            if (mTu.elementAt(ind).isTemporal()) {
                indTemp = ind;
            }
        }
        if (!found && indTemp >= 0) {
            // Let's don't keep more than one Temporary (never authenticated)
            // users. So delete previous User who wasn't ever authenticated.
            String tempUser = mTu.elementAt(indTemp).getUsername();
            delete(tempUser);
        }
        if (found) {
            tu = mTu.elementAt(ind);
            // AndTweetService.v(TAG, "User '" + tu.getUsername() + "' was found");
        } else {
            tu = new TwitterUser(username);
            MyLog.v(TAG, "New user '" + tu.getUsername() + "' was created");
            mTu.add(tu);
        }
        if (copyGlobal) {
            tu.copyGlobal();
        }
        return tu;
    }

    /**
     * Delete everything about the user
     * 
     * @return Was the User deleted?
     */
    public static boolean delete(String username) {
        boolean isDeleted = false;

        username = fixUsername(username);
        if (mTu == null) {
            Log.e(TAG, "delete: Was not initialized.");
        } else {
            // Delete the User's object from the list
            int ind = -1;
            boolean found = false;
            for (ind = 0; ind < mTu.size(); ind++) {
                if (mTu.elementAt(ind).getUsername().compareTo(username) == 0) {
                    found = true;
                    break;
                }
            }
            if (found) {
                TwitterUser tu = mTu.get(ind);
                tu.deleteData();

                // And delete the object from the list
                mTu.removeElementAt(ind);

                isDeleted = true;
            }
        }
        return isDeleted;
    }

    private static String fixUsername(String username) {
        if (username == null) {
            username = "";
        }
        username = username.trim();
        if (!isUsernameValid(username)) {
            username = "";
        }
        return username;
    }

    /**
     * @param context
     * @param username
     */
    private TwitterUser(String username) {
        username = fixUsername(username);
        // Try to find saved User data
        mPrefsFileName = prefsFileNameForUser(username);
        boolean isNewUser = !SharedPreferencesUtil.exists(MyPreferences.getContext(), mPrefsFileName);
        setUsername(username, isNewUser);
        if (!isNewUser) {
            // Load stored data for the User
            SharedPreferences sp = getSharedPreferences();
            mIsTemporal = !sp.getBoolean(MyPreferences.KEY_IS_NOT_TEMPORAL, false);
            if (mIsTemporal) {

            }
            mCredentialsVerified = CredentialsVerified.load(sp);
            mOAuth = sp.getBoolean(MyPreferences.KEY_OAUTH, true);
        }
    }

    /**
     * @return the mUsername
     */
    public String getUsername() {
        return mUsername;
    }

    /**
     * @return SharedPreferences of this User
     */
    public SharedPreferences getSharedPreferences() {
        SharedPreferences sp = null;
        if (mPrefsFileName.length() > 0) {
            try {
                sp = MyPreferences.getSharedPreferences(mPrefsFileName, MODE_PRIVATE);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "Cound't get preferences '" + mPrefsFileName + "'");
                sp = null;
            }
        }
        return sp;
    }

    /**
     * Set Username for the User who was first time authenticated
     * Remember that the User was ever authenticated 
     * 
     * @param username - new Username to set.
     */
    private boolean setUsernameAuthenticated(String username) {
        username = fixUsername(username);
        String newPrefsFileName = prefsFileNameForUser(username);
        boolean ok = false;

        if (isTemporal()) {
            // Do we really need to change it?
            ok = (mPrefsFileName.compareTo(newPrefsFileName) == 0);
            if (!ok) {
                mConnection = null;
                ok = SharedPreferencesUtil.rename(MyPreferences.getContext(), mPrefsFileName, newPrefsFileName);

                if (ok) {
                    mPrefsFileName = newPrefsFileName;
                }
                if (ok) {
                    // Now we know the name of this User!
                    setUsername(username, true);
                }
            }
            if (ok) {
                mIsTemporal = false;
                getSharedPreferences().edit().putBoolean(MyPreferences.KEY_IS_NOT_TEMPORAL, true).commit();
            }
        }
        return ok;
    }

    /**
     * Sets Username for this object only, doesn't change "Current user" of the Application
     * @param username
     * @param isNewUser true if we are creating new user
     */
    private void setUsername(String username, boolean isNewUser) {
        username = fixUsername(username);
        if (username.compareTo(mUsername) != 0) {
            mConnection = null;
            mUsername = username;
            if (isNewUser) {
                getSharedPreferences().edit().putString(MyPreferences.KEY_TWITTER_USERNAME, mUsername).commit();
                // TODO: global method:
                getSharedPreferences().edit()
                        .putLong(MyPreferences.KEY_PREFERENCES_CHANGE_TIME, java.lang.System.currentTimeMillis())
                        .commit();

            }
        }
    }

    /**
     * Is this object - temporal 
     *   (i.e. the Account that was never authenticated with any credentials)
     * 
     * @return
     */
    public boolean isTemporal() {
        return mIsTemporal;
    }

    /**
     * Copy global (DefaultShared) preferences to this User's properties
     */
    private void copyGlobal() {
        SharedPreferences dsp = MyPreferences.getDefaultSharedPreferences();

        // Retrieve new values before changes so they won't be overridden
        boolean oauth = dsp.getBoolean(MyPreferences.KEY_OAUTH, true);
        String password = dsp.getString(MyPreferences.KEY_TWITTER_PASSWORD, "");

        // Make changes
        setOAuth(oauth);
        setPassword(password);
    }

    private static boolean isUsernameValid(String username) {
        boolean ok = false;
        if (username != null && (username.length() > 0)) {
            ok = Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)]+", username);
            if (!ok && MyLog.isLoggable(TAG, Log.INFO)) {
                Log.i(TAG, "The Username is not valid: \"" + username + "\"");
            }
        }
        return ok;
    }

    /**
     * Delete all User's data
     * 
     * @param for temporal only
     * @return
     */
    private boolean deleteData() {
        boolean isDeleted = false;

        if (!isTemporal()) {
            // TODO: Delete databases for this User

        }
        if (mPrefsFileName.length() > 0) {
            // Old preferences file may be deleted, if it exists...
            isDeleted = SharedPreferencesUtil.delete(MyPreferences.getContext(), mPrefsFileName);
        }

        return isDeleted;
    }

    /**
     * @param context
     * @return instance of Connection subtype for the User
     */
    public Connection getConnection() {
        if (mConnection == null) {
            mConnection = Connection.getConnection(getSharedPreferences(), mOAuth);
        }
        return mConnection;
    }

    /**
     * Clear Authentication information
     * 
     * @param context
     */
    public void clearAuthInformation() {
        setCredentialsVerified(CredentialsVerified.NEVER);
        this.getConnection().clearAuthInformation(getSharedPreferences());
    }

    /**
     * @return the mOAuth
     */
    public boolean isOAuth() {
        return mOAuth;
    }

    /**
     * @param oAuth to set
     */
    private void setOAuth(boolean oauth) {
        if (mOAuth != oauth) {
            setCredentialsVerified(CredentialsVerified.NEVER);
            // So the Connection object may be reinitialized
            mConnection = null;
            mOAuth = oauth;
            getSharedPreferences().edit().putBoolean(MyPreferences.KEY_OAUTH, oauth).commit();
            // Propagate the changes to the global properties
            MyPreferences.getDefaultSharedPreferences().edit().putBoolean(MyPreferences.KEY_OAUTH, mOAuth).commit();
        }
    }

    /**
     * Password was moved to the connection object because it is needed there
     * 
     * @param password
     */
    private void setPassword(String password) {
        if (password.compareTo(getConnection().getPassword()) != 0) {
            setCredentialsVerified(CredentialsVerified.NEVER);
            getConnection().setPassword(getSharedPreferences(), password);
            // Propagate the changes to the global properties
            MyPreferences.getDefaultSharedPreferences().edit()
                    .putString(MyPreferences.KEY_TWITTER_PASSWORD, getConnection().getPassword()).commit();
        }
    }

    public String getPassword() {
        return getConnection().getPassword();
    }

    /**
     * Verify the user's credentials. Returns true if authentication was
     * successful
     * 
     * @see CredentialsVerified
     * @param reVerify Verify even if it was verified already
     * @return boolean
     * @throws ConnectionException
     * @throws ConnectionUnavailableException
     * @throws ConnectionAuthenticationException
     * @throws SocketTimeoutException
     * @throws ConnectionCredentialsOfOtherUserException
     */
    public boolean verifyCredentials(boolean reVerify) throws ConnectionException, ConnectionUnavailableException,
            ConnectionAuthenticationException, SocketTimeoutException, ConnectionCredentialsOfOtherUserException {
        boolean ok = false;
        if (!reVerify) {
            if (getCredentialsVerified() == CredentialsVerified.SUCCEEDED) {
                ok = true;
            }
        }
        if (!ok) {
            JSONObject jso = null;
            try {
                jso = getConnection().verifyCredentials();
                ok = (jso != null);
            } finally {
                String newName = null;
                boolean credentialsOfOtherUser = false;
                boolean errorSettingUsername = false;
                if (ok) {
                    if (jso.optInt("id") < 1) {
                        ok = false;
                    }
                }
                if (ok) {
                    newName = Connection.getScreenName(jso);
                    ok = isUsernameValid(newName);
                }

                if (ok) {
                    if (getUsername().length() > 0 && getUsername().compareTo(newName) != 0) {
                        // Credentials belong to other User ??
                        ok = false;
                        credentialsOfOtherUser = true;
                    }
                }
                if (ok) {
                    setCredentialsVerified(CredentialsVerified.SUCCEEDED);
                }
                if (ok && isTemporal()) {
                    // Now we know the name of this User!
                    ok = setUsernameAuthenticated(newName);
                    if (!ok) {
                        errorSettingUsername = true;
                    }
                }
                if (!ok) {
                    clearAuthInformation();
                    setCredentialsVerified(CredentialsVerified.FAILED);
                }

                if (credentialsOfOtherUser) {
                    Log.e(TAG, MyPreferences.getContext().getText(R.string.error_credentials_of_other_user) + ": "
                            + newName);
                    throw (new ConnectionCredentialsOfOtherUserException(newName));
                }
                if (errorSettingUsername) {
                    String msg = MyPreferences.getContext().getText(R.string.error_set_username) + newName;
                    Log.e(TAG, msg);
                    throw (new ConnectionAuthenticationException(msg));
                }
            }
        }
        return ok;
    }

    /**
     * Set current User to 'this' object.
     * - update global (default) SharedPreferences
     */
    public synchronized void setCurrentUser() {
        // Update global SharedPreferences
        SharedPreferences sp = MyPreferences.getDefaultSharedPreferences();
        String usernameOld = sp.getString(MyPreferences.KEY_TWITTER_USERNAME, "");
        String usernameNew = getUsername();
        SharedPreferences.Editor ed = sp.edit();
        if (usernameNew.compareTo(usernameOld) != 0) {
            MyLog.v(TAG, "Changing current user from '" + usernameOld + "' " + "to '" + usernameNew + "'");
            // This preference is being set by PreferenceActivity etc.
            //ed.putString(PreferencesActivity.KEY_TWITTER_USERNAME_NEW, getUsername());
            // This preference is being set by this code only
            ed.putString(MyPreferences.KEY_TWITTER_USERNAME, getUsername());
        }
        ed.putString(MyPreferences.KEY_TWITTER_PASSWORD, getConnection().getPassword());
        ed.putBoolean(MyPreferences.KEY_OAUTH, isOAuth());
        getCredentialsVerified().put(ed);
        if (getCredentialsVerified() != CredentialsVerified.SUCCEEDED) {
            // Don't turn off Automatic updates
            // ed.putBoolean(PreferencesActivity.KEY_AUTOMATIC_UPDATES, false);
        }
        ed.commit();
    }

    /**
     * This is defined by tweet server
     * Starting from 2010-09 twitter.com allows OAuth only
     */
    public boolean canChangeOAuth() {
        return mCanChangeOAuth;
    }
}