Wednesday, September 17, 2014

Android Account Manager (Adding MyApp to Android Account Manager)


Adding myApp to Android Account Manager is really critical and vital task. It will Show myApp icon in my phone-book like whatsapp, Facebook and gmail does.

There are various step included in this process.
I will not discuss each and every class.
Most important is code. So directly I am going for code.

Firstly have a look how to arrange all classes in different packages.


Lest start coding one class by one.
I used Google account to authenticate myApp here. You have to provide your server authentications

Code Starts from here.

<====================================================================>

// Class for Account authentication
public class AccountAuthenticatorService extends Service {

    /**
     * The tag used for the logs.
     */
    private static final String LOG_TAG = AccountAuthenticatorService.class.getSimpleName();

    /**
     * The implementation of the class |AccountAuthenticatorImpl|.
     * It is implemented as a singleton
     */
    private static AccountAuthenticatorImpl sAccountAuthenticator = null;

    /**
     * The main constructor.
     */
    public AccountAuthenticatorService() {
        super();
    }

    /**
     * The bind method of the service.
     * @param intent The intent used to invoke the service
     * @return The binder of the class which has implemented |AbstractAccountAuthenticator|
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.v(AccountAuthenticatorService.LOG_TAG, "Binding the service");
        IBinder ret = null;
        if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
            ret = getAuthenticator().getIBinder();
        }
        return ret;
    }

    /**
     * The method used to obtain the authenticator. It is implemented as a singleton
     * @return The implementation of the class |AbstractAccountAuthenticator|
     */
    private AccountAuthenticatorImpl getAuthenticator() {
        if (AccountAuthenticatorService.sAccountAuthenticator == null) {
            AccountAuthenticatorService.sAccountAuthenticator = new AccountAuthenticatorImpl(this);
        }

        return AccountAuthenticatorService.sAccountAuthenticator;
    }

    /**
     * The class which implements the class |AbstractAccountAuthenticator|.
     * It is the one which the Android system calls to perform any action related with the account
     */
    private static class AccountAuthenticatorImpl extends AbstractAccountAuthenticator {

        /**
         * The Context used.
         */
        private final Context mContext;

        /**
         * The main constructor of the class.
         * @param context The context used
         */
        public AccountAuthenticatorImpl(Context context) {
            super(context);
            mContext = context;
        }

        @Override
        public Bundle addAccount(AccountAuthenticatorResponse response,
                String accountType,
                String authTokenType,
                String[] requiredFeatures,
                Bundle options) throws NetworkErrorException {
            Log.d(AccountAuthenticatorService.LOG_TAG, "Adding new account");
            Bundle reply = new Bundle();

            Log.d(AccountAuthenticatorService.LOG_TAG, "The auth token type is " + authTokenType);
            Intent i = new Intent(mContext, AddNewAccountActivity.class);
            i.setAction("com.gowex.pista.addnewaccount");
            i.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
            i.putExtra("AuthTokenType", authTokenType);
            reply.putParcelable(AccountManager.KEY_INTENT, i);

            return reply;
        }

        @Override
        public Bundle confirmCredentials(AccountAuthenticatorResponse arg0, Account arg1,
                Bundle arg2) throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle editProperties(AccountAuthenticatorResponse arg0, String arg1) {
            return null;
        }

        @Override
        public Bundle getAuthToken(AccountAuthenticatorResponse arg0, Account arg1,
                String arg2, Bundle arg3) throws NetworkErrorException {
            return null;
        }

        @Override
        public String getAuthTokenLabel(String arg0) {
            return null;
        }

        @Override
        public Bundle hasFeatures(AccountAuthenticatorResponse arg0, Account arg1,
                String[] arg2) throws NetworkErrorException {
            return null;
        }

        @Override
        public Bundle updateCredentials(AccountAuthenticatorResponse arg0, Account arg1,
                String arg2, Bundle arg3) throws NetworkErrorException {
            return null;
        }
    }
}

<====================================================================>
/**
 * This class is called when the user want to create an account in the configuration of Android.
 */
public class AddNewAccountActivity extends Activity {

    /**
     * The tag utilized for the log.
     */
    private static final String LOG_TAG = AddNewAccountActivity.class.getSimpleName();

    /**
     * The context of the program.
     */
    private Context context;

    /**
     * The user name input by the user.
     */
    private EditText usernameET;

    /**
     * The password input by the user.
     */
    private EditText passwordET;

    /**
     * The button to add a new account.
     */
    private Button addNewAccountButton;

    /**
     * The response passed by the service.
     * It is used to give the user name and the password to the account manager
     */
    private AccountAuthenticatorResponse response;

    /**
     * The account manager used to request and add account.
     */
    private AccountManager accountManager;

    /**
     * Called when the activity is first created.
     * @param savedInstanceState The state saved previously
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Remove title bar
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
//Remove notification bar
//this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Lock the screen orientation
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        setContentView(R.layout.add_new_account_layout);

        context = this;

        usernameET = (EditText) findViewById(R.id.username);

        passwordET = (EditText) findViewById(R.id.password);

        addNewAccountButton = (Button) findViewById(R.id.createNewAccountButton);
        addNewAccountButton.setOnClickListener(onClickListener);

        Bundle extras = getIntent().getExtras();

        if (extras != null) {
            /*
             * Pass the new account back to the account manager
             */
            response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
        }
    }

    /**
     * The listener for the button pressed.
     */
    private final View.OnClickListener onClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            final String username = usernameET.getText().toString();
final String password = passwordET.getText().toString();

// Check the contents
// Check the user name
if (username == null || username.equalsIgnoreCase("")) {
Toast.makeText(context, getResources().getString(R.string.warning_username_empty), Toast.LENGTH_LONG).show();
return;
}

// Check the password
if (password == null || password.equalsIgnoreCase("")) {
Toast.makeText(context, getResources().getString(R.string.warning_password_empty), Toast.LENGTH_LONG).show();
return;
}

            accountManager = AccountManager.get(context);

            /*
             * Check if the account already exists.
             */
            if (AccountUtils.getUserAccount(context, username) != null) {
                Toast.makeText(context, R.string.warning_account_already_exists, Toast.LENGTH_SHORT).show();
                return;
            }

            /*
             * Check the user name and the password against the server.
             */
            Account newUserAccount = new Account(username, getResources().getString(R.string.account_type));

            try {
                String encryptedPassword = SecurityUtils.encryptToHex(password);
                boolean accountCreated = accountManager.addAccountExplicitly(newUserAccount, encryptedPassword, null);
                if (accountCreated) {
                    if (response != null) {
                        Bundle result = new Bundle();
                        result.putString(AccountManager.KEY_ACCOUNT_NAME, username);
                        result.putString(AccountManager.KEY_ACCOUNT_TYPE, getString(R.string.account_type));
                        response.onResult(result);
                        Toast.makeText(context, R.string.add_new_account_done, Toast.LENGTH_LONG).show();
                        finish();
                    } else {
                        Toast.makeText(context, R.string.error_creating_account, Toast.LENGTH_LONG).show();
                    }
                } else {
                    Toast.makeText(context, R.string.error_creating_account, Toast.LENGTH_LONG).show();
                }
            } catch (Exception e) {
                Log.e(LOG_TAG, e.getLocalizedMessage(), e);
                Toast.makeText(context, R.string.error_creating_account, Toast.LENGTH_LONG).show();
            }
        }
    };
}

<====================================================================>
/**
 * This class is used to implement the error completion handler.
 */
public interface Callback {

    /**
     * Method called when the operation has been finished.
     * @param error Indication of if any error happened or not
     */
    void done(final boolean error);
}

<====================================================================>

/**
 * This class is used to implement the error completion handler.
 */
public interface RequestDataCallback {

    /**
     * Method called when the operation has been finished.
     * @param data An array of bytes returned by the server
     * @param error Indication of if any error happened or not
     */
    void done(final byte[] data, final boolean error);
}

<====================================================================>
/**
 * This class is used to implement the error completion handler.
 */
public interface RequestJSONCallback {

    /**
     * Method called when the operation has been finished.
     * @param jsonObject The JSON object returned by the server
     * @param error Indication of if any error happened or not
     */
    void done(final JSONObject jsonObject, final boolean error);
}

<====================================================================>
/**
 * The customized Session exception
 *
 */
public class SessionException extends Exception {

    /**
* The default Serial version UID
*/
private static final long serialVersionUID = 1L;

public SessionException(String message) {
    super(message);
    }

}
<====================================================================>
/**
 * The Session class models a user's session. It is the intermediate level between Controllers and Service.
 */
public final class Session {

    private static final String LOG_TAG = Session.class.getSimpleName();

    private Service service;

    private Preferences preferences;

    private static Session currentSession = null;

    /**
     * The constructor of the session.
     * Because it is a singleton, there is not parameters for the constructors and it's private
     */
    private Session() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to
     * SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder {
        private static final Session INSTANCE = new Session();
    }

    // It is synchronized to avoid problems with multithreading
    // Once get, it must initialize the service and the preferences based on the context
    private static synchronized Session getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // To avoid clone problem
    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    /**
     * This method starts the sign up process.
     * @param context Context in which execution must be
     * @param username the name of the user
     * @param password User's password
     * @param callback Callback to call when process finishes
     */
    public static void signUp(final Context context, final String username,
    final String password, final Callback callback) {

        Service service = new Service(username);

        service.signUp(username, password, new Callback() {
            @Override
            public void done(final boolean error) {
                // If there is not error, the method login will call callback
                if (!error) {
                    Session.logIn(context, username, password, callback);
                } else {
                    callback.done(error);
                }
            }
        });
    }

    /**
     * Allow the user to log in.
     * It checks the user name and the password against the server.
     * @param context    The context utilized
     * @param username   The name of the user
     * @param password   The password
     * @param callback   The callback to call when the server returns an answer
     */
    public static void logIn(final Context context, final String username,
            final String password, final Callback callback) {

        final Service service = new Service(username);
        // The preferences is here to avoid to be used statically
        final Preferences preferences = new Preferences(context);

        service.logIn(username, password, new RequestJSONCallback() {
            @Override
            public void done(JSONObject jsonObject, boolean error) {
                if (!error) {
                    /*
                     * Creates an instance of the current session.
                     */
                    Session session = Session.getInstance();
                    session.setService(service);
                    session.setPreferences(preferences);

                    /*
                     * Save the current session
                     */
                    Session.setCurrentSession(session);
                    Session.currentSession.saveAsCurrentSession(context, username, password);
                }

                callback.done(error);
            }
        });
    }

    /**
     * Save the session in a persistent way.
     * @param context  The context utilized to get the data
     * @param username The name of the verified user
     * @param password The password of the verified user
     */
    private void saveAsCurrentSession(Context context, String username, String password) {
        // Save the user name
        preferences.setUserName(username);

        // Save the password
        AccountUtils.setPasswordByUserName(context, username, password);

    }

    /**
     * Check if there is any saved session
     * @param context The context used
     * @return true if there is any saved session
     *         false otherwise
     */
    public static boolean isSavedSessionExists(Context context) {
        Preferences preferences = new Preferences(context);

        // Check if the user name exists in the Shared preferences
        String username = preferences.getUsername();
        if (username == null || username.equals(Preferences.DEFAULT_USERNAME)) {
            return false;
        }

        // Check if the user account exists
        if (AccountUtils.getUserAccount(context, username) == null) {
        return false;
        }

        return true;
    }

    /**
     * Get the current session.
     * @param context The context utilized to retrieve the data
     * @return The current session
     * @throws SessionException if some data is missing
     */
    public static synchronized Session getCurrentSession(Context context) throws SessionException {
        if (Session.currentSession == null) {
            Session.currentSession = Session.sessionFromCurrentSession(context);
        }

        return Session.currentSession;
    }

    /**
     * Creates a new session from the data saved in the persistent data storage.
     * It might return null. Use the method existSavedSession to check if
     * the session saved exists
     * Precondition: The user name exists in the Shared preferences and the user
     *  account exists.
     * @param context The context utilized.
     * @return A new session if all the data are set
     * @throws SessionException if some data is missing
     */
    private static Session sessionFromCurrentSession(Context context) throws SessionException {
        Preferences preferences = new Preferences(context);

        // Get the user name
        String username = preferences.getUsername();
        if (username == null || username.equals(Preferences.DEFAULT_USERNAME)) {
            throw new SessionException("Error getting the session from the current one. " +
            "The previous session doesn't exsit");
        }

        Service newService = new Service(username);
        Session newSession = Session.getInstance();
        newSession.setService(newService);
        newSession.setPreferences(preferences);

        return newSession;
    }

    // Getters & setters

    private static synchronized void setCurrentSession(Session session) {
        Session.currentSession = session;
    }

    /*
    private Service getService() {
        return service;
    }*/

    /**
     * Set the service as the service utilized for the sessions
     * This is private to prevent other to set the service
     * The service won't be set until the user has logged in.
     * @param service The service to set.
     */
    private void setService(Service service) {
        this.service = service;
    }

    public Preferences getPreferences() {
       return preferences;
    }

    /**
     * Set the preferences as the preferences utilized for the session.
     * This is private to prevent other ot set the preferences from outside
     * The preferences won't be set until the user has logged in.
     * @param preferences The preferences to set.
     */
    private void setPreferences(Preferences preferences) {
        this.preferences = preferences;
    }

}
<====================================================================>
/**
 * The Request class used to establish REST communication with the server.
 */
public class HttpRequest {

    /**
     * The tag used in logs.
     */
    private static final String LOG_TAG = HttpRequest.class.getSimpleName();

    /**
     * Enumerated data which represents the REST methods used in the Request.
     */
    public enum RequestMethod {
        /**
         * Method get.
         */
        RequestMethodGet,
        /**
         * Method post.
         */
        RequestMethodPost,
        /**
         * Method put.
         */
        RequestMethodPut,
        /**
         * Method delete.
         */
        RequestMethodDelete
    }

    /**
     * The uri used to establish the REST communication.
     */
    private Uri uri;

    /**
     * The list of parameters to sent to the server.
     */
    private Map<String, String> parameters;

    /**
     * The request method established.
     */
    private final RequestMethod requestMethod;

    /**
     * The headers used.
     */
    private final Map<String, String> headerFields;

    /**
     * The instance of server Fetcher which is used to communicates with the server.
     */
    private ServerFetcher serverFetcher;

    /**
     * The instance of server Fetcher which is used to communicates with the server.
     */
    private final ExecutorServiceSingleton executorServiceSingleton;


    /**
     * The threadPool which contains all the threads used to communicate with the server.
     */
    private final ExecutorService threadPool;

    /**
     * The handler used to execute all the callbacks in the main thread.
     */
    private final Handler handler;

    /**
     * Method used to set the default header fields.
     * @return A map of default fields
     */
    private Map<String, String> defaultHeaderFields() {
        Map<String, String> headerFields = new HashMap<String, String>();
        /*
         * TODO Get locale language
         */
        String localelanguage = "en";
        headerFields.put("Accept-Language", localelanguage);
        return headerFields;
    }

    /**
     * The method used to parse URI and the parameters.
     * @param uri The URI of the server to connect
     * @param parameters The list of parameters to append
     * @return A URI with appended parameters
     */
    private Uri parseUriAndParameters(Uri uri, Map<String, String> parameters) {
        String listParameters = "";

        if (parameters != null) {
            for (String key : parameters.keySet()) {
                listParameters += key + "=" + parameters.get(key);
                listParameters += "&";
            }
        }

        /*
         * Remove the last character if it is "&"
         */
        if (listParameters.endsWith("&")) {
            listParameters = listParameters.substring(0, listParameters.length() - 1);
        }

        Uri result = Uri.parse(uri.toString() + listParameters);
        return result;
    }

    /**
     * Method used to set the authentication data in the request.
     * @param username The user name to set
     * @param password The password to set
     */
    public void setUsernamePassword(String username, String password) {
        /*
         * Encode the data
         */
        byte[] ascii = EncodingUtils.getAsciiBytes(username + password);
        /*
         * The code should not end with the EOL character
         */
        String authentication = SecurityUtils.base64Encode(ascii);

        /*
         * Add the authentication to the header
         */
        headerFields.put("Authorization", authentication);
    }

    /**
     * The main constructor of the class.
     * @param uri The URI of the server.
     * @param parameters The list of the parameters used.
     * @param requestMethod The request method
     */
    public HttpRequest(Uri uri, Map<String, String> parameters, RequestMethod requestMethod) {
        executorServiceSingleton = ExecutorServiceSingleton.instance();
        threadPool = executorServiceSingleton.getExecutorService();

        this.requestMethod = requestMethod;

        // If the request method is get, all the parameters is shown in the uri
        if (requestMethod == RequestMethod.RequestMethodGet) {
            this.uri = parseUriAndParameters(uri, parameters);
            Log.d("Request uri", uri.toString());
        } else {
            this.uri = uri;
            this.parameters = parameters;
        }

        headerFields = defaultHeaderFields();

        handler = new Handler();
    }

    /**
     * Method used to send request to the server, which returns an array of bytes.
     * @param requestDataCallback The callback to call when the communication finishes
     */
    public void performRequestWithHandler(final RequestDataCallback requestDataCallback) {
        serverFetcher = new ServerFetcher(requestMethod, uri, parameters, headerFields, new RequestDataCallback() {
            @Override
            public void done(final byte[] data, final boolean error) {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.d(LOG_TAG, "Request to " + uri.toString() + "done");
                        requestDataCallback.done(data, error);
                    }
                });
            }
        });
        threadPool.execute(serverFetcher);
    }

    /**
     * Method used to send request to the server, which parse the content returned to a json object.
     * @param jsonHandler The callback to call when the communication finishes.
     */
    public void performRequestWithJSONHandler(final RequestJSONCallback jsonHandler) {
        /*
         * Add new header
         */
        headerFields.put("Accept", "application/json");

        serverFetcher = new ServerFetcher(requestMethod, uri,
                parameters, headerFields,
                new RequestDataCallback() {

                @Override
                public void done(final byte[] data, final boolean error) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (!error && data != null && data.length > 0) {
                                try {
                                    // For latin languages, the codification must be ISO 8859-1
                                    //http://es.wikipedia.org/wiki/ISO_8859-1
                                    String jsonString = new String(data, "US-ASCII");
                                    Log.v(HttpRequest.LOG_TAG, jsonString);
                                    /*
                                     * Parse JSON data
                                     */
                                    JSONObject jsonObject = new JSONObject(jsonString);
                                    jsonHandler.done(jsonObject, error);
                                } catch (Exception exception) {
                                    Log.e(HttpRequest.LOG_TAG, exception.getLocalizedMessage(), exception);
                                    jsonHandler.done(null, true);
                                }
                            } else {
                                jsonHandler.done(null, error);
                            }
                        }
                    });
                }
            });
        threadPool.execute(serverFetcher);
    }

    /**
     * Informs that the request has been finished or not.
     * @return True if the request has been finished.
     *         False otherwise
     */
    public boolean hasFinished() {
        return (serverFetcher == null || !serverFetcher.isRunning);
    }

    /**
     * Method used to cancel the actual request.
     */
    public void cancelRequest() {
        if (serverFetcher != null && serverFetcher.isRunning) {
            serverFetcher.stopFetching();
        }

        serverFetcher = null;
    }

    public Uri getUri() {
        return uri;
    }

    /**
     * The runnable class used to connect with the server.
     */
    private class ServerFetcher implements Runnable {

        /**
         * The tag utilized for the log.
         */
        private static final String LOG_TAG = "ServerFetcher";

        /**
         * The registration time out before it launches the exception.
         */
        private static final int REGISTRATION_TIMEOUT = 3 * 1000;

        /**
         * The wait time out before it launches the exception.
         */
        private static final int WAIT_TIMEOUT = 30 * 1000;

        /**
         * The http client utilized.
         */
        private final HttpClient httpClient = new DefaultHttpClient();

        /**
         * The variable to record the running state (Yes/No).
         */
        private boolean isRunning = false;
        /**
         * The list of parameters of the HTTP client.
         */
        private final HttpParams params = httpClient.getParams();

        /**
         * The http response from the server.
         */
        private HttpResponse response;

        /**
         * The URI to connect.
         */
        private final Uri uri;

        /**
         * The list of parameters that includes in the http request.
         */
        private final Map<String, String> parameters;
        /**
         * The headers used.
         */
        private final Map<String, String> headerFields;

        /**
         * The callback to call when the operation finishes.
         */
        private final RequestDataCallback requestDataCallback;


        /**
         *  The final data obtained from the server.
         */
        private byte[] dataObtained;

        /**
         * The variable to save the final state of the operation.
         */
        private boolean error = false;

        /**
         * The main constructor.
         * @param requestMethod The REST method to perform
         * @param uri The Uri of the server to connect
         * @param parameters The list of parameters to be added to the HTTP request
         * @param headerFields The header of the HTTP request
         * @param requestDataCallback The Callback to call when the operation finishes
         */
        public ServerFetcher(RequestMethod requestMethod, Uri uri, Map<String,
                String> parameters, Map<String, String> headerFields,
                RequestDataCallback requestDataCallback) {
            this.uri = uri;
            this.parameters = parameters;
            this.headerFields = headerFields;

            this.requestDataCallback = requestDataCallback;
        }

        /**
         * The method which tells the actual state of the operation.
         * @return True if the server has not returned the response yet
         *         False otherwise
         */
        public boolean isRunning() {
            return isRunning;
        }

        /**
         * This methods stops the communication with the server.
         */
        public void stopFetching() {
            /*
             * TODO Cancel the http request
             */
        }

        @Override
        public void run() {
            Log.v(ServerFetcher.LOG_TAG, ServerFetcher.LOG_TAG + " running");
            isRunning = true;

            try {
                /*
                 * Set the connection parameters
                 */
                HttpConnectionParams.setConnectionTimeout(params, ServerFetcher.REGISTRATION_TIMEOUT);
                HttpConnectionParams.setSoTimeout(params, ServerFetcher.WAIT_TIMEOUT);
                ConnManagerParams.setTimeout(params, ServerFetcher.WAIT_TIMEOUT);

                /*
                 * Create ServerFetcher and prepare the data
                 */
                if (requestMethod == RequestMethod.RequestMethodGet) {
                    HttpGet httpGet = new HttpGet(uri.toString());

                    /*
                     * Add the headers
                     */
                    for (String key : headerFields.keySet()) {
                        httpGet.addHeader(key, headerFields.get(key));
                    }

                    /*
                     * Response from the Http Request
                     */
                    response = httpClient.execute(httpGet);

                } else if (requestMethod == RequestMethod.RequestMethodPost) {

                    HttpPost httpPost = new HttpPost(uri.toString());

                    /*
                     * Add the headers
                     */
                    for (String key : headerFields.keySet()) {
                        httpPost.addHeader(key, headerFields.get(key));
                    }

                    /*
                     * Add the values in the parameters
                     */
                    if (parameters != null) {
                        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
                        for (String key : parameters.keySet()) {
                            nameValuePairs.add(new BasicNameValuePair(key, parameters.get(key)));
                        }
                        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
                    }

                    /*
                     * Response from the Http Request
                     */
                    response = httpClient.execute(httpPost);

                } else if (requestMethod == RequestMethod.RequestMethodPut) {
                    /*
                     * TODO Implement it
                     */
                    Log.w(ServerFetcher.LOG_TAG, "Executing Request method not implemented: put");
                } else if (requestMethod == RequestMethod.RequestMethodDelete) {
                    /*
                     * TODO Implement it
                     */
                    Log.w(ServerFetcher.LOG_TAG, "Executing Request method not implemented: delete");
                }

                StatusLine statusLine = response.getStatusLine();
                Log.v(ServerFetcher.LOG_TAG, "Status code" + statusLine.getStatusCode());
                /*
                 * Check the Http Request for success
                 */
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
                    response.getEntity().writeTo(out);
                    out.close();
                    dataObtained = out.toByteArray();
                    error = false;
                } else if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
                    out.close();
                    response.getEntity().getContent().close();
                    error = true;
                } else {
                    /*
                     * Close the connection
                     */
                    response.getEntity().getContent().close();
                    throw new IOException(statusLine.getReasonPhrase());
                }
            } catch (Exception e) {
                Log.w(LOG_TAG, e.getLocalizedMessage(), e);
                error = true;
            } finally {
                isRunning = false;
                if (requestDataCallback != null) {
                    requestDataCallback.done(dataObtained, error);
                }
            }
        }
    }

    /**
     * The class which creates the threadPool as singleton.
     */
    private static final class ExecutorServiceSingleton {

        /**
         * The number of the threads which are running in parallel.
         */
        private static final int MAXIMUM_NUM_RUNNING_THREAD = 3;

        /**
         * A class to hold the singleton.
         */
        private static class SingletonHolder {
            /**
             * The instance of the class.
             */
            private static final ExecutorServiceSingleton INSTANCE = new ExecutorServiceSingleton();
        }

        /**
         * Creates the executor server as soft reference.
         */
        private SoftReference<ExecutorService> executorServiceReference = new SoftReference<ExecutorService>(
                createExecutorService());

        /**
         * The empty constructor of the class.
         */
        private ExecutorServiceSingleton() {
        };

        /**
         * The public method to return the instance.
         * @return A instance of the Singleton holder
         */
        public static ExecutorServiceSingleton instance() {
            return SingletonHolder.INSTANCE;
        }

        /**
         * Method used to get the executor service.
         * @return The executor service. Create it if it has not been created before
         */
        public ExecutorService getExecutorService() {
            ExecutorService executorService = executorServiceReference.get();

            if (executorService == null) {
                // (the reference was cleared)
                executorService = createExecutorService();
                executorServiceReference = new SoftReference<ExecutorService>(executorService);
            }

            return executorService;
        }

        /**
         * The method which creates a threadPool with limit number of threads.
         * Those threads will be re-utilized and they should exist while the application is running.
         * @return A threadPool with always same number of threads
         */
        private ExecutorService createExecutorService() {
            return Executors.newFixedThreadPool(ExecutorServiceSingleton.MAXIMUM_NUM_RUNNING_THREAD);
        }
    }
}
<====================================================================>

/**
 * This class is used to communicates with the remote server.
 */
public class Service {

    /**
     * The tag used for log.
     */
    private static final String LOG_TAG = Service.class.getSimpleName();

    /**
     * The base of the server url
     */
    private static final String BASE_URL = "http:google.com/";   // Provide here your server link
    

    /**
     * The user name used for the login, sign up and/or authentication.
     * The password is saved on the Account manager and it is associated
     * with the user name.
     */
    private String username;

    /**
     * The main constructor.
     * @param username The user name used for login, sign up and/or authentication
     *        This is necessary because the user name is used to get the hash for
     *        the authentication.
     */
    public Service(String username) {
        this.username = username;
    }

    /**
     * Method used for sign up.
     * @param username The user name used for sign up
     * @param invitationCode The invitation code used for sign up
     * @param password The password used for sign up
     * @param callback The callback to call when the sign up process has been finished
     */
    public void signUp(final String username,
            final String password, final Callback callback) {
        Map<String, String> parameters = new HashMap<String, String>();
        parameters.put("email", username);
        parameters.put("password", password);

        final Uri finalUri = Uri.parse(BASE_URL + "/signup");   // Provide here your server link

        HttpRequest httpRequest = new HttpRequest(finalUri, parameters, RequestMethod.RequestMethodPost);

        httpRequest.performRequestWithHandler(new RequestDataCallback() {

            @Override
            public void done(final byte[] data, final boolean error) {
                callback.done(error);
            }
        });
    }

    /**
     * Method used for login.
     * @param username The user name used for login
     * @param password The password used for login
     * @param jsonCallback The callback to call when the login process has been finished
     */
    public void logIn(final String username, final String password,
            final RequestJSONCallback jsonCallback) {

        Map<String, String> parameters = new HashMap<String, String>();
        parameters.put("username_login", username);
        parameters.put("password", password);

        final Uri finalUri = Uri.parse(BASE_URL + "/login");   // Provide here your server link

        HttpRequest httpRequest = new HttpRequest(finalUri, parameters, RequestMethod.RequestMethodPost);

        httpRequest.performRequestWithJSONHandler(new RequestJSONCallback() {

            @Override
            public void done(JSONObject jsonObject, boolean error) {
                jsonCallback.done(jsonObject, error);
            }
        });
    }

    // Getters & setters

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}
<====================================================================>
public class AccountUtils {

    /**
     * The tag used for log
     */
    private static final String LOG_TAG = AccountUtils.class.getSimpleName();

    /**
     * Check if an account with the specific user name exists or not
     * @param context  The context used
     * @param username The user name of the account
     * @return If the account exists, return the account
     *         Otherwise return null
     */
    public static Account getUserAccount(Context context, String username) {
        AccountManager accountManager = AccountManager.get(context);
        Account[] accounts = accountManager.getAccountsByType(context.getResources().getString(R.string.account_type));
        for (Account account : accounts) {
            if (account.name.equalsIgnoreCase(username)) {
            return account;
            }
        }

        return null;
    }

    /**
     * Obtains the password stored in the account manager.
     * Password is encrypted, so it must be decrypted to get its real value
     * @param context  The context used
     * @param username The user name that the password is associated
     * @return The password stored in the account manager
     *         "" if the account is not set
     */
    public static String getPasswordyByUserName(Context context, String username) {
        AccountManager accountManager = AccountManager.get(context);
        String encryptedPassword;
        String decryptedPassword = "";

        Account account = getUserAccount(context, username);
        if (account != null) {
            encryptedPassword = accountManager.getPassword(account);
            try {
                decryptedPassword = SecurityUtils.decrypt(encryptedPassword);
            } catch (Exception e) {
                Log.e(LOG_TAG, e.getLocalizedMessage(), e);
            }
        }

        return decryptedPassword;
    }

    /**
     * Set the encrypted password in the user account. If the account doesn't exist
     * before, it creates a new one.
     * @param password The password to be stored in the account manager
     * @param username The user name associated with the password
     * @return true if the password has been set
     *         false otherwise
     */
    public static boolean setPasswordByUserName(Context context, String username, String password) {
        String accountType = context.getResources().getString(R.string.account_type);
        AccountManager accountManager = AccountManager.get(context);

        // Encrypt the password
        try {
            String encryptedPassword = SecurityUtils.encryptToHex(password);
            Account account = getUserAccount(context, username);
            if (account != null) {
                // Check if the old password is the same as the new one
                String oldPassword = accountManager.getPassword(account);
                if (!oldPassword.equalsIgnoreCase(encryptedPassword)) {
                    accountManager.setPassword(account, encryptedPassword);
                }
            }
            // If the account doesn't exist before, create it.
            else {
                Account newUserAccount = new Account(username, accountType);
                accountManager.addAccountExplicitly(newUserAccount, encryptedPassword, null);
            }
            return true;
        } catch (Exception e) {
            Log.e(LOG_TAG, e.getLocalizedMessage(), e);
            return false;
        }
    }
}
<====================================================================>
/**
 * Class utilized to save the user elemental data persistently.
 */
public class Preferences {

    /**
     * The tag utilized for the log.
     */
    private static final String LOG_TAG = Preferences.class.getSimpleName();

    /**
     * The name of the file utilized to store the data.
     */
    private static final String FILE_NAME = "Preferences";

    /**
     * The set of keys utilized.
     */
    /**
     * The key for the user name.
     */
    private static final String USERNAME_KEY = "username";

    /**
     * The default user name
     */
    public static final String DEFAULT_USERNAME = "";

    /**
     * The context passed by any Android's component.
     */
    private final Context context;

    /**
     * The shared preferences to save/restore the data.
     */
    private final SharedPreferences sharedPreferences;

    /**
     * The editor to save the data.
     */
    private final SharedPreferences.Editor editor;

    /**
     * The main constructor.
     * @param context The context passed by any Android's component.
     */
    public Preferences(Context context) {
        this.context = context;
        sharedPreferences = context.getSharedPreferences(Preferences.FILE_NAME, Context.MODE_PRIVATE);
        editor = sharedPreferences.edit();
    }

    /*
     * User name
     */
    /**
     * Obtains the user name stored in the shared preferences.
     * @return The user name if it has been stored
     *         "" if the user id has not been stored
     */
    public String getUsername() {
        String username = sharedPreferences.getString(Preferences.USERNAME_KEY, DEFAULT_USERNAME);
        return username;
    }

    /**
     * Stores the user name in the shared preferences.
     * @param username The user name to be stored
     */
    public void setUserName(String username) {
        editor.putString(Preferences.USERNAME_KEY, username);
        editor.commit();
    }

    /**
     * Remove everything from the shared preferences.
     */
    public void clear() {
        editor.clear();
        editor.commit();
    }

}
<====================================================================>
/**
 * The SecurityUtils class contains methods used for password encryption/decryption.
 */
public final class SecurityUtils {

    /**
     * The SecurityUtils class should not be instantiated, so its constructor.
     *  is private to prevent instantiation by other objects.
     */
    private SecurityUtils() { }

    /**
     * Digits used in hex format.
     */
    private static final String HEX = "0123456789ABCDEF";

    /**
     * Seed to encryption.
     */
    private static final String SEED = "com.jiahaoliuliu.android.sampleaccountandserver";

    /**
     * This method appends a byte to a string buffer with hex format.
     * @param sb String buffer to append in
     * @param b Byte to append to the buffer
     */
    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(SecurityUtils.HEX.charAt((b >> 4) & 0x0f)).append(SecurityUtils.HEX.charAt(b & 0x0f));
    }

    /**
     * This method gets a raw key from the given seed.
     *  Raw key is needed for encrypt/decrypt text
     * @param seed Seed used to get the raw key
     * @return Byte array with the raw key
     * @throws Exception
     */
    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }

    /**
     * This method encrypts text with the given raw byte array.
     *  The algorithm used is AES
     * @param raw
     * @param clear Byte representation of text to encrypt
     * @return Byte array with encrypted text
     * @throws Exception
     */
    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    /**
     * This method decrypts encrypted text with the given raw byte array.
     * @param raw Byte array
     * @param encrypted Text encrypted for decryption
     * @return Byte array with decrypted text
     * @throws Exception
     */
    private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    /**
     * This method converts a byte value into a String with hexadecimal format.
     * @param value Value to convert
     * @return String with hexadecimal format
     */
    private static String toHexadecimal(byte[] value) {
        String result = "";
        for (byte aux : value) {
            int b = aux & 0xff;
            if (Integer.toHexString(b).length() == 1) {
                result += "0";
            }
            result += Integer.toHexString(b);
        }

        return result;
    }

    /**
     * This method encrypts text.
     * @param cleartext Text to encrypt
     * @return String in hex format with |cleartext| encrypted
     * @throws Exception
     */
    public static String encryptToHex(String cleartext) throws Exception {
        byte[] rawKey = SecurityUtils.getRawKey(SecurityUtils.SEED.getBytes());
        byte[] result = SecurityUtils.encrypt(rawKey, cleartext.getBytes());
        return SecurityUtils.toHex(result);
    }

    /**
     * This method encrypts text.
     * @param cleartext Text to encrypt
     * @return |cleartext| encrypted into byte array
     * @throws Exception
     */
    public static byte[] encryptToBytes(String cleartext) throws Exception {
        byte[] rawKey = SecurityUtils.getRawKey(SecurityUtils.SEED.getBytes());
        byte[] result = SecurityUtils.encrypt(rawKey, cleartext.getBytes());
        return result;
    }

    /**
     * This method decrypts text.
     * @param encrypted Text to decrypt
     * @return String |encrypted| decrypted into a string
     * @throws Exception
     */
    public static String decrypt(String encrypted) throws Exception {
        byte[] rawKey = SecurityUtils.getRawKey(SecurityUtils.SEED.getBytes());
        byte[] enc = SecurityUtils.toByte(encrypted);
        byte[] result = SecurityUtils.decrypt(rawKey, enc);
        return new String(result);
    }

    /**
     * This method encodes a byte array into a string with base64 format.
     * @param bytes Byte array to encode
     * @return String with |bytes| encoded into base64
     */
    public static String base64Encode(byte[] bytes) {
        return Base64.encodeToString(bytes, Base64.NO_WRAP);
    }

    /**
     * This method decodes a base64 encoded string.
     * @param base64 String encoded in base64 format
     * @return String with |base64| decoded
     */
    public static String base64Decode(String base64) {
        return SecurityUtils.toHexadecimal(Base64.decode(base64, Base64.NO_WRAP));
    }

    /**
     * This method converts a string to its hex representation.
     */
    public static String toHex(String txt) {
        return SecurityUtils.toHex(txt.getBytes());
    }

    /**
     * This method converts a string in hex format to a byte array.
     */
    public static String fromHex(String hex) {
        return new String(SecurityUtils.toByte(hex));
    }

    /**
     * This method converts a string in hex format to a byte array.
     * @param hexString String in hex format to convert
     * @return Byte array gets from |hexString|
     */
    public static byte[] toByte(String hexString) {
        int len = hexString.length() / 2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++) {
            result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
        }
        return result;
    }

    /**
     * This method gives a hex conversion of a byte array.
     * @param buf Byte array to get its hex representation
     * @return String with |buf| in hex format
     */
    public static String toHex(byte[] buf) {
        if (buf == null) {
            return "";
        }
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            SecurityUtils.appendHex(result, buf[i]);
        }
        return result.toString();
    }
}
<====================================================================>

And You are done with All java files..
Now layout files
<====================================================================>
add_new_account_layout.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".AddNewAccount">

    <EditText
        android:id="@+id/password"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/username"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:layout_alignLeft="@+id/username"
        android:layout_alignRight="@+id/username"
        android:ems="10"
        android:inputType="textPassword"
        android:hint="@string/hint_password"
        android:lines="1"
        android:imeOptions="actionDone"
        />

    <Button
    android:id="@+id/createNewAccountButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
    android:layout_alignLeft="@+id/password"
    android:layout_alignRight="@+id/password"
    android:layout_below="@+id/password"
    android:layout_marginTop="24dp"
    android:text="@string/create_new_account"/>

    <EditText
    android:id="@+id/username"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="160dp"
    android:ems="10"
    android:hint="@string/hint_username"
    android:imeOptions="actionNext"
    android:lines="1" />

</RelativeLayout>

<====================================================================>
Your manifest file should contains these permission

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>

And full manifest file should look like this...


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ankit.account.manager"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>

    <application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<service
            android:name="ankit.account.manager.accountmanager.AccountAuthenticatorService"
            android:exported="true" android:process=":auth"
            >
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>
            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator"
                />
        </service>
        
        <activity
            android:name="ankit.account.manager.accountmanager.AddNewAccountActivity"
            android:screenOrientation="portrait"
            android:configChanges="keyboardHidden|keyboard|orientation"
            >
        </activity>
</application>

</manifest>

<====================================================================>

Now your application is added to Android Account Manager.

Full Source Code is available on github.

Enjoy...!!!


2 comments:

  1. please upload project?
    @xml/authenticator ?

    ReplyDelete
    Replies
    1. You can download source code here (https://github.com/AnkitIISc/Android-Acoount-Manager)

      Delete