1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.example.android.samplesync.authenticator;
18
19import com.example.android.samplesync.Constants;
20import com.example.android.samplesync.client.NetworkUtilities;
21
22import android.accounts.AbstractAccountAuthenticator;
23import android.accounts.Account;
24import android.accounts.AccountAuthenticatorResponse;
25import android.accounts.AccountManager;
26import android.accounts.NetworkErrorException;
27import android.content.Context;
28import android.content.Intent;
29import android.os.Bundle;
30import android.text.TextUtils;
31import android.util.Log;
32
33/**
34 * This class is an implementation of AbstractAccountAuthenticator for
35 * authenticating accounts in the com.example.android.samplesync domain. The
36 * interesting thing that this class demonstrates is the use of authTokens as
37 * part of the authentication process. In the account setup UI, the user enters
38 * their username and password. But for our subsequent calls off to the service
39 * for syncing, we want to use an authtoken instead - so we're not continually
40 * sending the password over the wire. getAuthToken() will be called when
41 * SyncAdapter calls AccountManager.blockingGetAuthToken(). When we get called,
42 * we need to return the appropriate authToken for the specified account. If we
43 * already have an authToken stored in the account, we return that authToken. If
44 * we don't, but we do have a username and password, then we'll attempt to talk
45 * to the sample service to fetch an authToken. If that fails (or we didn't have
46 * a username/password), then we need to prompt the user - so we create an
47 * AuthenticatorActivity intent and return that. That will display the dialog
48 * that prompts the user for their login information.
49 */
50class Authenticator extends AbstractAccountAuthenticator {
51
52    /** The tag used to log to adb console. **/
53    private static final String TAG = "Authenticator";
54
55    // Authentication Service context
56    private final Context mContext;
57
58    public Authenticator(Context context) {
59        super(context);
60        mContext = context;
61    }
62
63    @Override
64    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
65            String authTokenType, String[] requiredFeatures, Bundle options) {
66        Log.v(TAG, "addAccount()");
67        final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
68        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
69        final Bundle bundle = new Bundle();
70        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
71        return bundle;
72    }
73
74    @Override
75    public Bundle confirmCredentials(
76            AccountAuthenticatorResponse response, Account account, Bundle options) {
77        Log.v(TAG, "confirmCredentials()");
78        return null;
79    }
80
81    @Override
82    public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
83        Log.v(TAG, "editProperties()");
84        throw new UnsupportedOperationException();
85    }
86
87    @Override
88    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
89            String authTokenType, Bundle loginOptions) throws NetworkErrorException {
90        Log.v(TAG, "getAuthToken()");
91
92        // If the caller requested an authToken type we don't support, then
93        // return an error
94        if (!authTokenType.equals(Constants.AUTHTOKEN_TYPE)) {
95            final Bundle result = new Bundle();
96            result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
97            return result;
98        }
99
100        // Extract the username and password from the Account Manager, and ask
101        // the server for an appropriate AuthToken.
102        final AccountManager am = AccountManager.get(mContext);
103        final String password = am.getPassword(account);
104        if (password != null) {
105            final String authToken = NetworkUtilities.authenticate(account.name, password);
106            if (!TextUtils.isEmpty(authToken)) {
107                final Bundle result = new Bundle();
108                result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
109                result.putString(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
110                result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
111                return result;
112            }
113        }
114
115        // If we get here, then we couldn't access the user's password - so we
116        // need to re-prompt them for their credentials. We do that by creating
117        // an intent to display our AuthenticatorActivity panel.
118        final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
119        intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
120        intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, authTokenType);
121        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
122        final Bundle bundle = new Bundle();
123        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
124        return bundle;
125    }
126
127    @Override
128    public String getAuthTokenLabel(String authTokenType) {
129        // null means we don't support multiple authToken types
130        Log.v(TAG, "getAuthTokenLabel()");
131        return null;
132    }
133
134    @Override
135    public Bundle hasFeatures(
136            AccountAuthenticatorResponse response, Account account, String[] features) {
137        // This call is used to query whether the Authenticator supports
138        // specific features. We don't expect to get called, so we always
139        // return false (no) for any queries.
140        Log.v(TAG, "hasFeatures()");
141        final Bundle result = new Bundle();
142        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
143        return result;
144    }
145
146    @Override
147    public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
148            String authTokenType, Bundle loginOptions) {
149        Log.v(TAG, "updateCredentials()");
150        return null;
151    }
152}
153