Android 使用 AccountManager 添加帐户

Android Adding Account with AccountManager

大家好,我正在尝试使用 androids accountManager 添加一个帐户,我一直在下面看到这个堆栈跟踪。

伙计们,我不能 post 我的所有代码,因为我打破了 Stack Overflow 允许我输入的限制。所以只有 post 您请求的代码片段,因为有更多的代码用于此。对不起,我只是乱搞乱码,直到我可以让它工作然后我清理它。

FATAL EXCEPTION: main

Process: com.example.rapid.rapid, PID: 6168
java.lang.SecurityException: uid 10335 cannot explicitly add accounts of type: com.example.rapid.rapid
    at android.os.Parcel.readException(Parcel.java:1620)
    at android.os.Parcel.readException(Parcel.java:1573)
    at android.accounts.IAccountManager$Stub$Proxy.addAccountExplicitly(IAccountManager.java:890)
    at android.accounts.AccountManager.addAccountExplicitly(AccountManager.java:716)
    at com.example.rapid.rapid.LoginActivity.onResponse(LoginActivity.java:174)
    at com.example.rapid.rapid.LoginActivity.onResponse(LoginActivity.java:140)
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:60)
    at com.android.volley.toolbox.StringRequest.deliverResponse(StringRequest.java:30)
    at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:158)
    at android.app.ActivityThread.main(ActivityThread.java:7237)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

LoginActivity.java

public class LoginActivity extends Activity {
    private static final String TAG = "LoginActivity";
    public final static String ARG_ACCOUNT_TYPE = "com.example.rapid.rapid";
    public final static String ARG_AUTH_TYPE = "AUTH_TYPE";
    public final static String ARG_ACCOUNT_NAME = "com.example.rapid.rapid";
    public final static String ARG_IS_ADDING_NEW_ACCOUNT = "IS_ADDING_ACCOUNT";
    public static final String KEY_ERROR_MESSAGE = "ERR_MSG";
    public final static String PARAM_USER_PASS = "USER_PASS";
    private static final int REQUEST_SIGNUP = 0;
    private AccountManager mAccountManager;
    public static final String ACCOUNT_TYPE = "com.example.rapid.rapid";
    private static final String CONTENT_AUTHORITY = "com.example.rapid.rapid";
    private static final String PREF_SETUP_COMPLETE = "setup_complete";
    private static final long SYNC_FREQUENCY = 60 * 60;  // 1 hour (in seconds)
    private String mAuthTokenType;
    private boolean mInvalidate;
    private AlertDialog mAlertDialog;

    @InjectView(R.id.loginEmailWrapper)
    TextInputLayout _loginEmailWrapper;
    @InjectView(R.id.loginPasswordWrapper)
    TextInputLayout _loginPasswordWrapper;
    @InjectView(R.id.loginEmailInput)
    EditText _loginEmailInput;
    @InjectView(R.id.loginPasswordInput)
    EditText _loginPasswordInput;
    @InjectView(R.id.loginPasswordVisibility)
    ImageView _loginPasswordVisibility;
    @InjectView(R.id.btn_login)
    Button _loginButton;
    @InjectView(R.id.link_signup)
    TextView _signupLink;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /*Uncomment this to make this screen of the app fullscreen.
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);*/
        setContentView(R.layout.activity_login);
        ButterKnife.inject(this);
        mAccountManager = AccountManager.get(this);

        boolean setupComplete = PreferenceManager
                .getDefaultSharedPreferences(this.getApplicationContext()).getBoolean(PREF_SETUP_COMPLETE, false);

        String accountName = getIntent().getStringExtra(ARG_ACCOUNT_NAME);
        mAuthTokenType = getIntent().getStringExtra(ARG_AUTH_TYPE);
        if (mAuthTokenType == null)
            mAuthTokenType = AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS;

        if (accountName != null) {
            _loginEmailInput.setText(accountName);
        }

        _loginButton.setOnClickListener(new View.OnClickListener() {


            @Override
            public void onClick(View v) {
                Log.d(TAG, "Begin Login process...");
                showAccountPicker(mAuthTokenType, false);

                if (!validate()) {
                    onLoginFailed();
                    return;
                }

                final String email = _loginEmailInput.getText().toString();
                final String password = _loginPasswordInput.getText().toString();

                final String accountType = getIntent().getStringExtra(ARG_ACCOUNT_TYPE);

                _loginButton.setEnabled(false);

                final ProgressDialog progressDialog = new ProgressDialog(LoginActivity.this,
                        R.style.Theme_IAPTheme);
                progressDialog.setIndeterminate(true);
                progressDialog.setMessage("Authenticating...");
                progressDialog.show();



                // Response received from the server
                Response.Listener<String> responseListener = new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        String authtoken = null;
                        boolean newAccount = false;
                        try {
                            Log.i("tagconvertstr", "[" + response + "]");
                            JSONObject jsonResponse = new JSONObject(response);
                            boolean success = jsonResponse.getBoolean("success");
                            if (success) {
                                String trainer_name = jsonResponse.getString("trainer_name");
                                authtoken = jsonResponse.getString("token");
                                //String name = jsonResponse.getString("name");

                                //Intent intent = new Intent(LoginActivity.this, UserHomeActivity.class);
                                //intent.putExtra("name", name);
                                //intent.putExtra("username", username);
                                //LoginActivity.this.startActivity(intent);
                                //Intent intent = new Intent(LoginActivity.this, UserHomeActivity.class);
                                //LoginActivity.this.startActivity(intent);
                                //startActivityForResult(intent, 1);

                                String accountName = AccountManager.KEY_ACCOUNT_NAME;
                                String accountPassword = password;
                                //final Account account = new Account(email, "com.example.rapid.rapid");

                                if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, true)) {
                                    Log.d("rapid", TAG + "> finishLogin > addAccountExplicitly");
                                    authtoken = AccountManager.KEY_AUTHTOKEN;
                                    String authtokenType = mAuthTokenType;

                                    Account account = rapidAuthenticatorService.GetAccount(ACCOUNT_TYPE);
                                    AccountManager accountManager =
                                            (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
                                    if (accountManager.addAccountExplicitly(account, null, null)) {
                                        // Inform the system that this account supports sync
                                        ContentResolver.setIsSyncable(account, CONTENT_AUTHORITY, 1);
                                        // Inform the system that this account is eligible for auto sync when the network is up
                                        ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, true);
                                        // Recommend a schedule for automatic synchronization. The system may modify this based
                                        // on other scheduled syncs and network utilization.
                                        ContentResolver.addPeriodicSync(
                                                account, CONTENT_AUTHORITY, new Bundle(),SYNC_FREQUENCY);
                                        newAccount = true;
                                    }

                                    if (newAccount) {
                                        TriggerRefresh();
                                        PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit()
                                                .putBoolean(PREF_SETUP_COMPLETE, true).commit();

                                    }

                                    Log.d("rapid", TAG + "> ALL SETUP!");
                                    // Creating the account on the device and setting the auth token we got
                                    // (Not setting the auth token will cause another call to the server to authenticate the user)
                                    //mAccountManager.addAccountExplicitly(account, null, null);
                                    //mAccountManager.setAuthToken(account, authtokenType, authtoken);
                                } else {
                                    Log.d("rapid", TAG + "> finishLogin > setPassword");
                                    //mAccountManager.setPassword(account, accountPassword);
                                    Log.d("rapid", TAG + "> done setting account password");
                                }

                                //setAccountAuthenticatorResult(intent.getExtras());
                                //setResult(RESULT_OK, intent);

                                Toast.makeText(getBaseContext(), "Login Successful", Toast.LENGTH_LONG).show();

                                Intent intent = new Intent(LoginActivity.this, UserHomeActivity.class);
                                intent.putExtra("trainer_name", trainer_name);
                                startActivity(intent);
                            } else {
                                progressDialog.dismiss();
                                onLoginFailed();
                            }

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                };

                LoginRequest loginRequest = new LoginRequest(email, password, responseListener);
                RequestQueue queue = Volley.newRequestQueue(LoginActivity.this);
                queue.add(loginRequest);


            }
        });

        _loginPasswordInput.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                //_registerPasswordVisibility.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE);
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                //_registerPasswordVisibility.setVisibility(s.length() > 0 ? View.VISIBLE : View.GONE);
                //_trainerNameWrapper.setBackgroundColor(Color.parseColor("#0000ff"));
            }
        });
        _loginPasswordVisibility.setOnTouchListener(mPasswordVisibleTouchListener);

        _signupLink.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // Start the Signup activity
                Intent intent = new Intent(getApplicationContext(), RegisterActivity.class);
                startActivityForResult(intent, REQUEST_SIGNUP);
            }
        });
    }

    public static void TriggerRefresh() {
        Bundle b = new Bundle();
        // Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
        b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
        b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        ContentResolver.requestSync(
                rapidAuthenticatorService.GetAccount(ACCOUNT_TYPE), // Sync account
                CONTENT_AUTHORITY,                 // Content authority
                b);                                             // Extras
    }

    private View.OnTouchListener mPasswordVisibleTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            final boolean isOutsideView = event.getX() < 0 ||
                    event.getX() > v.getWidth() ||
                    event.getY() < 0 ||
                    event.getY() > v.getHeight();

            // change input type will reset cursor position, so we want to save it
            final int cursor = _loginPasswordInput.getSelectionStart();

            if (isOutsideView || MotionEvent.ACTION_UP == event.getAction())
                _loginPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT |
                        InputType.TYPE_TEXT_VARIATION_PASSWORD);
            else
                _loginPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT |
                        InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);

            _loginPasswordInput.setSelection(cursor);
            return true;
        }
    };

    /**
     * Show all the accounts registered on the account manager. Request an auth token upon user select.
     *
     * @param authTokenType
     */
    private void showAccountPicker(final String authTokenType, final boolean invalidate) {
        mInvalidate = invalidate;
        final Account availableAccounts[] = mAccountManager.getAccountsByType(AccountGeneral.ACCOUNT_TYPE);

        if (availableAccounts.length == 0) {
            Toast.makeText(this, "No accounts", Toast.LENGTH_SHORT).show();
        } else {
            String name[] = new String[availableAccounts.length];
            for (int i = 0; i < availableAccounts.length; i++) {
                name[i] = availableAccounts[i].name;
            }

            // Account picker
            mAlertDialog = new AlertDialog.Builder(this).setTitle("Pick Account").setAdapter(new ArrayAdapter<String>(getBaseContext(), android.R.layout.simple_list_item_1, name), new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if (invalidate)
                        invalidateAuthToken(availableAccounts[which], authTokenType);
                    else
                        getExistingAccountAuthToken(availableAccounts[which], authTokenType);
                }
            }).create();
            mAlertDialog.show();
        }
    }

    /**
     * Get the auth token for an existing account on the AccountManager
     *
     * @param account
     * @param authTokenType
     */
    private void getExistingAccountAuthToken(Account account, String authTokenType) {
        final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account, authTokenType, null, this, null, null);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Bundle bnd = future.getResult();

                    final String authtoken = bnd.getString(AccountManager.KEY_AUTHTOKEN);
                    showMessage((authtoken != null) ? "SUCCESS!\ntoken: " + authtoken : "FAIL");
                    Log.d("udinic", "GetToken Bundle is " + bnd);
                } catch (Exception e) {
                    e.printStackTrace();
                    showMessage(e.getMessage());
                }
            }
        }).start();
    }

    /**
     * Invalidates the auth token for the account
     *
     * @param account
     * @param authTokenType
     */
    private void invalidateAuthToken(final Account account, String authTokenType) {
        final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(account, authTokenType, null, this, null, null);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Bundle bnd = future.getResult();

                    final String authtoken = bnd.getString(AccountManager.KEY_AUTHTOKEN);
                    mAccountManager.invalidateAuthToken(account.type, authtoken);
                    showMessage(account.name + " invalidated");
                } catch (Exception e) {
                    e.printStackTrace();
                    showMessage(e.getMessage());
                }
            }
        }).start();
    }

    /**
     * Get an auth token for the account.
     * If not exist - add it and then return its auth token.
     * If one exist - return its auth token.
     * If more than one exists - show a picker and return the select account's auth token.
     *
     * @param accountType
     * @param authTokenType
     */
    private void getTokenForAccountCreateIfNeeded(String accountType, String authTokenType) {
        final AccountManagerFuture<Bundle> future = mAccountManager.getAuthTokenByFeatures(accountType, authTokenType, null, this, null, null,
                new AccountManagerCallback<Bundle>() {
                    @Override
                    public void run(AccountManagerFuture<Bundle> future) {
                        Bundle bnd = null;
                        try {
                            bnd = future.getResult();
                            final String authtoken = bnd.getString(AccountManager.KEY_AUTHTOKEN);
                            showMessage(((authtoken != null) ? "SUCCESS!\ntoken: " + authtoken : "FAIL"));
                            Log.d("udinic", "GetTokenForAccount Bundle is " + bnd);

                        } catch (Exception e) {
                            e.printStackTrace();
                            showMessage(e.getMessage());
                        }
                    }
                }
                , null);
    }

    private void showMessage(final String msg) {
        if (TextUtils.isEmpty(msg))
            return;

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getBaseContext(), msg, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

rapidAuthenticator.java

public class rapidAuthenticator extends AbstractAccountAuthenticator {

    private String TAG = "rapidAuthenticator";
    private final Context mContext;

    public rapidAuthenticator(Context context) {
        super(context);

        // I hate you! Google - set mContext as protected!
        this.mContext = context;
    }

    @Override
    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
        Log.d("rapid", TAG + "> addAccount");

        final Intent intent = new Intent(mContext, LoginActivity.class);
        intent.putExtra(LoginActivity.ARG_ACCOUNT_TYPE, accountType);
        intent.putExtra(LoginActivity.ARG_AUTH_TYPE, authTokenType);
        intent.putExtra(LoginActivity.ARG_IS_ADDING_NEW_ACCOUNT, true);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);

        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }

    @Override
    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {

        Log.d("udinic", TAG + "> getAuthToken");

        // If the caller requested an authToken type we don't support, then
        // return an error
        if (!authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY) && !authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS)) {
            final Bundle result = new Bundle();
            result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
            return result;
        }

        // Extract the username and password from the Account Manager, and ask
        // the server for an appropriate AuthToken.
        final AccountManager am = AccountManager.get(mContext);

        String authToken = am.peekAuthToken(account, authTokenType);

        Log.d("udinic", TAG + "> peekAuthToken returned - " + authToken);

        // Lets give another try to authenticate the user
        if (TextUtils.isEmpty(authToken)) {
            final String password = am.getPassword(account);
            if (password != null) {
                try {
                    Log.d("udinic", TAG + "> re-authenticating with the existing password");
                    authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        // If we get an authToken - we return it
        if (!TextUtils.isEmpty(authToken)) {
            final Bundle result = new Bundle();
            result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
            result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
            result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
            return result;
        }

        // If we get here, then we couldn't access the user's password - so we
        // need to re-prompt them for their credentials. We do that by creating
        // an intent to display our AuthenticatorActivity.
        final Intent intent = new Intent(mContext, LoginActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
        intent.putExtra(LoginActivity.ARG_ACCOUNT_TYPE, account.type);
        intent.putExtra(LoginActivity.ARG_AUTH_TYPE, authTokenType);
        intent.putExtra(LoginActivity.ARG_ACCOUNT_NAME, account.name);
        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }


    @Override
    public String getAuthTokenLabel(String authTokenType) {
        if (AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS.equals(authTokenType))
            return AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS_LABEL;
        else if (AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY.equals(authTokenType))
            return AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY_LABEL;
        else
            return authTokenType + " (Label)";
    }

    @Override
    public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
        final Bundle result = new Bundle();
        result.putBoolean(KEY_BOOLEAN_RESULT, false);
        return result;
    }

    @Override
    public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
        return null;
    }

    @Override
    public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        return null;
    }
}

清单

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.rapid.rapid">

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

    <application>
        <service android:name="com.example.rapid.rapid.rapidAuthenticatorService">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>
            <meta-data android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" />
        </service>
    </application>
</manifest>

Authenticator.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
        android:accountType="com.example.rapid.rapid"
        android:icon="@drawable/logo"
        android:smallIcon="@drawable/logo"
        android:label="rapid"
        android:accountPreferences="@xml/prefs"/>
</selector>

如例外所述,调用者 uid 不同于 验证者的 uid。要显式添加帐户,callerauthenticator's uid 应该相同。

这应该与您的 应用程序 ID 相同,即 程序包名称

android:accountType="com.example.rapid.rapid" 

Android Developer Documentation

This method requires the caller to have a signature match with the authenticator that owns the specified account.

我遇到了类似的问题,但在我重启设备后解决了。尝试一下可能会有所帮助。