summaryrefslogtreecommitdiffstats
path: root/core/java/android/accounts/AccountMonitor.java
blob: f21385eadeb70cf72b220fffddc3083e73b263dd (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
/*
 * 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;
        }
    }
}