au.com.domain.AccountsAutoCompleteTextView.java Source code

Java tutorial

Introduction

Here is the source code for au.com.domain.AccountsAutoCompleteTextView.java

Source

/**
 * Copyright 2016 Domain.com.au
 *
 * 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 au.com.domain;

import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Patterns;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Filter;

import com.google.android.gms.auth.GoogleAuthUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import au.com.domain.library.R;

public class AccountsAutoCompleteTextView extends TextInputLayout {

    public static final int REQUEST_CODE = 1000;
    private List<String> mEmails;
    private List<String> mPriorityList;
    private boolean mAllowPrefill;
    private ArrayAdapter<String> mAdapter;
    private Activity mActivity;
    private Fragment mFragment;
    private AutoCompleteTextView mAccountsAutocomplete;

    private int mThreshold;
    private String mCurrentText;

    public AccountsAutoCompleteTextView(Context context) {
        super(context);
        init(context);
    }

    public AccountsAutoCompleteTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public AccountsAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        addView(mAccountsAutocomplete = new AutoCompleteTextView(context));
        mAccountsAutocomplete.setImeOptions(EditorInfo.IME_ACTION_NEXT);
        mAccountsAutocomplete.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);

        if (TextUtils.isEmpty(getHint())) {
            setHint("Email");
        }

        // TODO Make this a custom attr (currently does nothing)
        mThreshold = mAccountsAutocomplete.getThreshold();

        setAccountOptions();
        mAccountsAutocomplete.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View view, boolean b) {
                if (b && !isPermissionGranted()) {
                    mAccountsAutocomplete.showDropDown();
                }
            }
        });
        mAccountsAutocomplete.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                String currentText = charSequence.toString();
                if (!currentText.equalsIgnoreCase(getContext().getString(R.string.allow_accounts_suggestion))) {
                    mCurrentText = charSequence.toString();
                }
            }

            @Override
            public void afterTextChanged(Editable editable) {
                if (isErrorEnabled()) {
                    setError(null);
                    setErrorEnabled(false);
                }
            }
        });
    }

    private boolean isPermissionGranted() {
        return ContextCompat.checkSelfPermission(getContext(),
                Manifest.permission.GET_ACCOUNTS) == PackageManager.PERMISSION_GRANTED;
    }

    private void setAccountOptions() {
        Context context = getContext();
        mEmails = getGoogleAccountEmails(context);

        if (isNonEmpty(mPriorityList)) {
            mEmails.addAll(mPriorityList);
        }

        if (isPermissionGranted()) {
            //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
            mAdapter = new ArrayAdapter<>(context, android.R.layout.simple_dropdown_item_1line, mEmails);
            mAccountsAutocomplete.setOnItemClickListener(null);
            if (mAccountsAutocomplete.getThreshold() < 2) {
                mAccountsAutocomplete.setThreshold(mThreshold);
            }
        } else {
            final List<String> names = Arrays.asList(context.getString(R.string.allow_accounts_suggestion));
            mAdapter = new AccountsAdapter(context, android.R.layout.simple_dropdown_item_1line, names);
            mAccountsAutocomplete.setThreshold(1);
            setAskAccountsAutoComplete(context);
        }

        mAccountsAutocomplete.setAdapter(mAdapter);

        if (mAllowPrefill && !mEmails.isEmpty() && isNonEmpty(mPriorityList)) {
            setChosenAccountName(mPriorityList.get(0));
        }
    }

    private boolean isNonEmpty(Collection collection) {
        return collection != null && !collection.isEmpty();
    }

    /**
     * If @param allowPrefill is TRUE, the first entry in emailOptions will be used to prefill the
     * EditText.
     *
     * @param emailOptions
     */
    public void setEmailOptions(@Nullable List<String> emailOptions, boolean allowPrefill) {
        mPriorityList = emailOptions;
        mAllowPrefill = allowPrefill;

        setAccountOptions();
    }

    public void setParentActivity(Activity activity) {
        mActivity = activity;
    }

    public void setParentFragment(Fragment fragment) {
        mFragment = fragment;
    }

    public void addDropdownOptions(List<String> options) {
        mEmails.addAll(options);
        mAdapter.notifyDataSetChanged();
    }

    private void setAskAccountsAutoComplete(final Context context) {
        // This is called *after* the system has done a `replaceText()`
        mAccountsAutocomplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                mAccountsAutocomplete.setText(mCurrentText);
                mAccountsAutocomplete.setSelection(mCurrentText.length());
                hideSoftKeyboard(context, AccountsAutoCompleteTextView.this.getWindowToken());

                if (mActivity == null && mFragment == null) {
                    throw new IllegalStateException(
                            "No calling Activity or Fragment declared. Call either setParentActivity() or setParentFragment().");
                }

                if (mActivity != null) {
                    ActivityCompat.requestPermissions(mActivity, new String[] { Manifest.permission.GET_ACCOUNTS },
                            REQUEST_CODE);
                } else {
                    mFragment.requestPermissions(new String[] { Manifest.permission.GET_ACCOUNTS }, REQUEST_CODE);
                }
            }
        });
    }

    /**
     * Clients would need to call this method because Android only calls back the host activity.
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    public void onPermissionResponse(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        boolean granted = REQUEST_CODE == requestCode && grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED;

        if (granted) {
            setAccountOptions();
            mAccountsAutocomplete.showDropDown();
        } else {

            if (mActivity == null && mFragment == null) {
                throw new IllegalStateException(
                        "No calling Activity or Fragment declared. Call either setParentActivity() or setParentFragment().");
            }

            boolean shouldShowRationale;
            // This will return TRUE if te user has previously denied a request
            // On subsequent times that we request the permission and the user chooses "Don't ask again", it will return FALSE
            if (mActivity != null) {
                shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(mActivity,
                        Manifest.permission.GET_ACCOUNTS);
            } else {
                shouldShowRationale = mFragment
                        .shouldShowRequestPermissionRationale(Manifest.permission.GET_ACCOUNTS);
            }

            if (!shouldShowRationale) {
                mAccountsAutocomplete.setAdapter(mAdapter = null);
            }
        }
    }

    private void setChosenAccountName(@NonNull String accountName) {
        mAccountsAutocomplete.setText(accountName);
        if (isErrorEnabled()) {
            setError(null);
        }
    }

    private List<String> getGoogleAccountEmails(Context context) {
        List<String> emails = new ArrayList<>();
        if (isPermissionGranted()) {
            //noinspection MissingPermission isPermission() is doing the check required by lint
            Account[] accounts = AccountManager.get(context).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
            for (Account account : accounts) {
                if (android.util.Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
                    emails.add(account.name);
                }
            }
            Collections.sort(emails, String.CASE_INSENSITIVE_ORDER);
        }
        return emails;
    }

    private void hideSoftKeyboard(Context context, IBinder windowToken) {
        if (context == null) {
            return;
        }

        InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(windowToken, 0);
    }

    public String getText() {
        return mAccountsAutocomplete.getText().toString();
    }

    public AutoCompleteTextView getEditText() {
        return mAccountsAutocomplete;
    }

    public boolean isEmailValid() {
        final String input = mAccountsAutocomplete.getText().toString();
        return !TextUtils.isEmpty(input) && Patterns.EMAIL_ADDRESS.matcher(input).matches();
    }

    @Override
    public void setError(@Nullable CharSequence error) {
        setErrorEnabled(true);
        super.setError(error);
    }

    public void setText(String text) {
        mAccountsAutocomplete.setText(text);
    }

    class AccountsAdapter extends ArrayAdapter<String> {

        private Filter mFilter;

        public AccountsAdapter(Context context, int resource, final List<String> objects) {
            super(context, resource, objects);
            mFilter = new Filter() {
                @Override
                protected FilterResults performFiltering(CharSequence charSequence) {
                    FilterResults filterResults = new FilterResults();
                    filterResults.values = objects;
                    filterResults.count = objects.size();
                    return filterResults;
                }

                @Override
                protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
                    notifyDataSetChanged();
                }
            };
        }

        @NonNull
        @Override
        public Filter getFilter() {
            return mFilter;
        }
    }
}