AccountMonitor.java

/*
* Copyright (C) 2007 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.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.database.SQLException;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
/**
* A helper class that calls back on the provided
* AccountMonitorListener with the set of current accounts both when
* it gets created and whenever the set changes. It does this by
* binding to the AccountsService and registering to receive the
* intent broadcast when the set of accounts is changed. The
* connection to the accounts service is only made when it needs to
* fetch the current list of accounts (that is, when the
* AccountMonitor is first created, and when the intent is received).
*/
public class AccountMonitor extends BroadcastReceiver implements ServiceConnection {
private final Context mContext;
private final AccountMonitorListener mListener;
private boolean mClosed = false;
private int pending = 0;
// This thread runs in the background and runs the code to update accounts
// in the listener.
private class AccountUpdater extends Thread {
private IBinder mService;
public AccountUpdater(IBinder service) {
mService = service;
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
IAccountsService accountsService = IAccountsService.Stub.asInterface(mService);
String[] accounts = null;
do {
try {
accounts = accountsService.getAccounts();
} catch (RemoteException e) {
// if the service was killed then the system will restart it and when it does we
// will get another onServiceConnected, at which point we will do a notify.
Log.w("AccountMonitor", "Remote exception when getting accounts", e);
return;
}
synchronized (AccountMonitor.this) {
--pending;
if (pending == 0) {
break;
}
}
} while (true);
mContext.unbindService(AccountMonitor.this);
try {
mListener.onAccountsUpdated(accounts);
} catch (SQLException e) {
// Better luck next time. If the problem was disk-full,
// the STORAGE_OK intent will re-trigger the update.
Log.e("AccountMonitor", "Can't update accounts", e);
}
}
}
/**
* Initializes the AccountMonitor and initiates a bind to the
* AccountsService to get the initial account list. For 1.0,
* the "list" is always a single account.
*
* @param context the context we are running in
* @param listener the user to notify when the account set changes
*/
public AccountMonitor(Context context, AccountMonitorListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener is null");
}
mContext = context;
mListener = listener;
// Register a broadcast receiver to monitor account changes
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AccountsServiceConstants.LOGIN_ACCOUNTS_CHANGED_ACTION);
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); // To recover from disk-full.
mContext.registerReceiver(this, intentFilter);
// Send the listener the initial state now.
notifyListener();
}
@Override
public void onReceive(Context context, Intent intent) {
notifyListener();
}
public void onServiceConnected(ComponentName className, IBinder service) {
// Create a background thread to update the accounts.
new AccountUpdater(service).start();
}
public void onServiceDisconnected(ComponentName className) {
}
private synchronized void notifyListener() {
if (pending == 0) {
// initiate the bind
if (!mContext.bindService(AccountsServiceConstants.SERVICE_INTENT,
this, Context.BIND_AUTO_CREATE)) {
// This is normal if GLS isn't part of this build.
Log.w("AccountMonitor",
"Couldn't connect to " +
AccountsServiceConstants.SERVICE_INTENT +
" (Missing service?)");
}
} else {
// already bound. bindService will not trigger another
// call to onServiceConnected, so instead we make sure
// that the existing background thread will call
// getAccounts() after this function returns, by
// incrementing pending.
//
// Yes, this else clause contains only a comment.
}
++pending;
}
/**
* calls close()
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Unregisters the account receiver. Consecutive calls to this
* method are harmless, but also do nothing. Once this call is
* made no more notifications will occur.
*/
public synchronized void close() {
if (!mClosed) {
mContext.unregisterReceiver(this);
mClosed = true;
}
}
}