blob: 2ca1f0e492cd6f54fcb05ea9bc74e5b9227337a9 [file] [log] [blame]
/*
* 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;
}
}
}