1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.phone;
18
19import android.app.Service;
20import android.content.Context;
21import android.content.Intent;
22import com.android.internal.telephony.OperatorInfo;
23import android.os.AsyncResult;
24import android.os.Binder;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Message;
28import android.os.RemoteCallbackList;
29import android.os.RemoteException;
30import android.telephony.SubscriptionManager;
31import com.android.internal.telephony.Phone;
32import com.android.internal.telephony.PhoneFactory;
33import android.util.Log;
34
35import java.util.ArrayList;
36
37/**
38 * Service code used to assist in querying the network for service
39 * availability.
40 */
41public class NetworkQueryService extends Service {
42    // debug data
43    private static final String LOG_TAG = "NetworkQuery";
44    private static final boolean DBG = true;
45
46    // static events
47    private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
48
49    // static states indicating the query status of the service
50    private static final int QUERY_READY = -1;
51    private static final int QUERY_IS_RUNNING = -2;
52
53    // error statuses that will be retured in the callback.
54    public static final int QUERY_OK = 0;
55    public static final int QUERY_EXCEPTION = 1;
56
57    static final String ACTION_LOCAL_BINDER = "com.android.phone.intent.action.LOCAL_BINDER";
58
59    /** state of the query service */
60    private int mState;
61
62    /**
63     * Class for clients to access.  Because we know this service always
64     * runs in the same process as its clients, we don't need to deal with
65     * IPC.
66     */
67    public class LocalBinder extends Binder {
68        INetworkQueryService getService() {
69            return mBinder;
70        }
71    }
72    private final IBinder mLocalBinder = new LocalBinder();
73
74    /**
75     * Local handler to receive the network query compete callback
76     * from the RIL.
77     */
78    Handler mHandler = new Handler() {
79        @Override
80        public void handleMessage(Message msg) {
81            switch (msg.what) {
82                // if the scan is complete, broadcast the results.
83                // to all registerd callbacks.
84                case EVENT_NETWORK_SCAN_COMPLETED:
85                    if (DBG) log("scan completed, broadcasting results");
86                    broadcastQueryResults((AsyncResult) msg.obj);
87                    break;
88            }
89        }
90    };
91
92    /**
93     * List of callback objects, also used to synchronize access to
94     * itself and to changes in state.
95     */
96    final RemoteCallbackList<INetworkQueryServiceCallback> mCallbacks =
97        new RemoteCallbackList<INetworkQueryServiceCallback> ();
98
99    /**
100     * Implementation of the INetworkQueryService interface.
101     */
102    private final INetworkQueryService.Stub mBinder = new INetworkQueryService.Stub() {
103
104        /**
105         * Starts a query with a INetworkQueryServiceCallback object if
106         * one has not been started yet.  Ignore the new query request
107         * if the query has been started already.  Either way, place the
108         * callback object in the queue to be notified upon request
109         * completion.
110         */
111        public void startNetworkQuery(INetworkQueryServiceCallback cb, int phoneId) {
112            if (cb != null) {
113                // register the callback to the list of callbacks.
114                synchronized (mCallbacks) {
115                    mCallbacks.register(cb);
116                    if (DBG) log("registering callback " + cb.getClass().toString());
117
118                    switch (mState) {
119                        case QUERY_READY:
120                            // TODO: we may want to install a timeout here in case we
121                            // do not get a timely response from the RIL.
122                            Phone phone = PhoneFactory.getPhone(phoneId);
123                            if (phone != null) {
124                                phone.getAvailableNetworks(
125                                        mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED));
126                                mState = QUERY_IS_RUNNING;
127                                if (DBG) log("starting new query");
128                            } else {
129                                if (DBG) {
130                                    log("phone is null");
131                                }
132                            }
133                            break;
134
135                        // do nothing if we're currently busy.
136                        case QUERY_IS_RUNNING:
137                            if (DBG) log("query already in progress");
138                            break;
139                        default:
140                    }
141                }
142            }
143        }
144
145        /**
146         * Stops a query with a INetworkQueryServiceCallback object as
147         * a token.
148         */
149        public void stopNetworkQuery(INetworkQueryServiceCallback cb) {
150            // currently we just unregister the callback, since there is
151            // no way to tell the RIL to terminate the query request.
152            // This means that the RIL may still be busy after the stop
153            // request was made, but the state tracking logic ensures
154            // that the delay will only last for 1 request even with
155            // repeated button presses in the NetworkSetting activity.
156            unregisterCallback(cb);
157        }
158
159        /**
160         * Unregisters the callback without impacting an underlying query.
161         */
162        public void unregisterCallback(INetworkQueryServiceCallback cb) {
163            if (cb != null) {
164                synchronized (mCallbacks) {
165                    if (DBG) log("unregistering callback " + cb.getClass().toString());
166                    mCallbacks.unregister(cb);
167                }
168            }
169        }
170    };
171
172    @Override
173    public void onCreate() {
174        mState = QUERY_READY;
175    }
176
177    /**
178     * Required for service implementation.
179     */
180    @Override
181    public void onStart(Intent intent, int startId) {
182    }
183
184    /**
185     * Handle the bind request.
186     */
187    @Override
188    public IBinder onBind(Intent intent) {
189        if (DBG) log("binding service implementation");
190        if (ACTION_LOCAL_BINDER.equals(intent.getAction())) {
191            return mLocalBinder;
192        }
193
194        return mBinder;
195    }
196
197    /**
198     * Broadcast the results from the query to all registered callback
199     * objects.
200     */
201    private void broadcastQueryResults (AsyncResult ar) {
202        // reset the state.
203        synchronized (mCallbacks) {
204            mState = QUERY_READY;
205
206            // see if we need to do any work.
207            if (ar == null) {
208                if (DBG) log("AsyncResult is null.");
209                return;
210            }
211
212            // TODO: we may need greater accuracy here, but for now, just a
213            // simple status integer will suffice.
214            int exception = (ar.exception == null) ? QUERY_OK : QUERY_EXCEPTION;
215            if (DBG) log("AsyncResult has exception " + exception);
216
217            // Make the calls to all the registered callbacks.
218            for (int i = (mCallbacks.beginBroadcast() - 1); i >= 0; i--) {
219                INetworkQueryServiceCallback cb = mCallbacks.getBroadcastItem(i);
220                if (DBG) log("broadcasting results to " + cb.getClass().toString());
221                try {
222                    cb.onQueryComplete((ArrayList<OperatorInfo>) ar.result, exception);
223                } catch (RemoteException e) {
224                }
225            }
226
227            // finish up.
228            mCallbacks.finishBroadcast();
229        }
230    }
231
232    private static void log(String msg) {
233        Log.d(LOG_TAG, msg);
234    }
235}
236