summaryrefslogtreecommitdiffstats
path: root/core/java/android/accounts/AuthenticatorBindHelper.java
blob: 2ca1f0e492cd6f54fcb05ea9bc74e5b9227337a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * 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 android.accounts;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;

import java.util.ArrayList;
import java.util.Map;

import com.google.android.collect.Lists;
import com.google.android.collect.Maps;

/**
 * A helper object that simplifies binding to Account Authenticators. It uses the
 * {@link AccountAuthenticatorCache} to find the component name of the authenticators,
 * allowing the user to bind by account name. It also allows multiple, simultaneous binds
 * to the same authenticator, with each bind call guaranteed to return either
 * {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call
 * itself succeeds, even if the authenticator is already bound internally.
 * @hide
 */
public class AuthenticatorBindHelper {
    private static final String TAG = "Accounts";
    private final Handler mHandler;
    private final Context mContext;
    private final int mMessageWhatConnected;
    private final int mMessageWhatDisconnected;
    private final Map<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
    private final Map<String, ArrayList<Callback>> mServiceUsers = Maps.newHashMap();
    private final AccountAuthenticatorCache mAuthenticatorCache;

    public AuthenticatorBindHelper(Context context,
            AccountAuthenticatorCache authenticatorCache, Handler handler,
            int messageWhatConnected, int messageWhatDisconnected) {
        mContext = context;
        mHandler = handler;
        mAuthenticatorCache = authenticatorCache;
        mMessageWhatConnected = messageWhatConnected;
        mMessageWhatDisconnected = messageWhatDisconnected;
    }

    public interface Callback {
        void onConnected(IBinder service);
        void onDisconnected();
    }

    public boolean bind(String authenticatorType, Callback callback) {
        // if the authenticator is connecting or connected then return true
        synchronized (mServiceConnections) {
            if (mServiceConnections.containsKey(authenticatorType)) {
                MyServiceConnection connection = mServiceConnections.get(authenticatorType);
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "service connection already exists for " + authenticatorType);
                }
                mServiceUsers.get(authenticatorType).add(callback);
                if (connection.mService != null) {
                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "the service is connected, scheduling a connected message for "
                                + authenticatorType);
                    }
                    connection.scheduleCallbackConnectedMessage(callback);
                } else {
                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "the service is *not* connected, waiting for for "
                                + authenticatorType);
                    }
                }
                return true;
            }

            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "there is no service connection for " + authenticatorType);
            }

            // otherwise find the component name for the authenticator and initiate a bind
            // if no authenticator or the bind fails then return false, otherwise return true
            AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
                    mAuthenticatorCache.getServiceInfo(
                            AuthenticatorDescription.newKey(authenticatorType));
            if (authenticatorInfo == null) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "there is no authenticator for " + authenticatorType
                            + ", bailing out");
                }
                return false;
            }

            MyServiceConnection connection = new MyServiceConnection(authenticatorType);

            Intent intent = new Intent();
            intent.setAction("android.accounts.AccountAuthenticator");
            intent.setComponent(authenticatorInfo.componentName);
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
            }
            if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
                if (Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
                }
                return false;
            }

            mServiceConnections.put(authenticatorType, connection);
            mServiceUsers.put(authenticatorType, Lists.newArrayList(callback));
            return true;
        }
    }

    public void unbind(Callback callbackToUnbind) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "unbinding callback " + callbackToUnbind);
        }
        synchronized (mServiceConnections) {
            for (Map.Entry<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) {
                final String authenticatorType = entry.getKey();
                final ArrayList<Callback> serviceUsers = entry.getValue();
                for (Callback callback : serviceUsers) {
                    if (callback == callbackToUnbind) {
                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
                            Log.v(TAG, "found callback in service" + authenticatorType);
                        }
                        serviceUsers.remove(callbackToUnbind);
                        if (serviceUsers.isEmpty()) {
                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                                Log.v(TAG, "there are no more callbacks for service "
                                        + authenticatorType + ", unbinding service");
                            }
                            unbindFromServiceLocked(authenticatorType);
                        } else {
                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                                Log.v(TAG, "leaving service " + authenticatorType
                                        + " around since there are still callbacks using it");
                            }
                        }
                        return;
                    }
                }
            }
            Log.e(TAG, "did not find callback " + callbackToUnbind + " in any of the services");
        }
    }

    /**
     * You must synchronized on mServiceConnections before calling this
     */
    private void unbindFromServiceLocked(String authenticatorType) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "unbindService from " + authenticatorType);
        }
        mContext.unbindService(mServiceConnections.get(authenticatorType));
        mServiceUsers.remove(authenticatorType);
        mServiceConnections.remove(authenticatorType);
    }

    private class ConnectedMessagePayload {
        public final IBinder mService;
        public final Callback mCallback;
        public ConnectedMessagePayload(IBinder service, Callback callback) {
            mService = service;
            mCallback = callback;
        }
    }

    private class MyServiceConnection implements ServiceConnection {
        private final String mAuthenticatorType;
        private IBinder mService = null;

        public MyServiceConnection(String authenticatorType) {
            mAuthenticatorType = authenticatorType;
        }

        public void onServiceConnected(ComponentName name, IBinder service) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "onServiceConnected for account type " + mAuthenticatorType);
            }
            // post a message for each service user to tell them that the service is connected
            synchronized (mServiceConnections) {
                mService = service;
                for (Callback callback : mServiceUsers.get(mAuthenticatorType)) {
                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "the service became connected, scheduling a connected "
                                + "message for " + mAuthenticatorType);
                    }
                    scheduleCallbackConnectedMessage(callback);
                }
            }
        }

        private void scheduleCallbackConnectedMessage(Callback callback) {
            final ConnectedMessagePayload payload =
                    new ConnectedMessagePayload(mService, callback);
            mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget();
        }

        public void onServiceDisconnected(ComponentName name) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "onServiceDisconnected for account type " + mAuthenticatorType);
            }
            // post a message for each service user to tell them that the service is disconnected,
            // and unbind from the service.
            synchronized (mServiceConnections) {
                final ArrayList<Callback> callbackList = mServiceUsers.get(mAuthenticatorType);
                if (callbackList != null) {
                    for (Callback callback : callbackList) {
                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
                            Log.v(TAG, "the service became disconnected, scheduling a "
                                    + "disconnected message for "
                                    + mAuthenticatorType);
                        }
                        mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget();
                    }
                    unbindFromServiceLocked(mAuthenticatorType);
                }
            }
        }
    }

    boolean handleMessage(Message message) {
        if (message.what == mMessageWhatConnected) {
            ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj;
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "notifying callback " + payload.mCallback + " that it is connected");
            }
            payload.mCallback.onConnected(payload.mService);
            return true;
        } else if (message.what == mMessageWhatDisconnected) {
            Callback callback = (Callback)message.obj;
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "notifying callback " + callback + " that it is disconnected");
            }
            callback.onDisconnected();
            return true;
        } else {
            return false;
        }
    }
}