| /* |
| * 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; |
| } |
| } |
| } |