summaryrefslogtreecommitdiffstats
path: root/core/java/android/accounts/AuthenticatorBindHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/accounts/AuthenticatorBindHelper.java')
-rw-r--r--core/java/android/accounts/AuthenticatorBindHelper.java251
1 files changed, 251 insertions, 0 deletions
diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java
new file mode 100644
index 0000000..9d2ccf6
--- /dev/null
+++ b/core/java/android/accounts/AuthenticatorBindHelper.java
@@ -0,0 +1,251 @@
+/*
+ * 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 authenticatorInfo =
+ mAuthenticatorCache.getServiceInfo(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");
+ }
+ unbindFromService(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");
+ }
+ }
+
+ private void unbindFromService(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) {
+ for (Callback callback : mServiceUsers.get(mAuthenticatorType)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "the service became disconnected, scheduling a "
+ + "disconnected message for "
+ + mAuthenticatorType);
+ }
+ mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget();
+ }
+ unbindFromService(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;
+ }
+ }
+}