Skip to content

Instantly share code, notes, and snippets.

@asksven
Created November 24, 2011 16:05
Show Gist options
  • Save asksven/1391689 to your computer and use it in GitHub Desktop.
Save asksven/1391689 to your computer and use it in GitHub Desktop.
Network scan
/*
* Copyright (C) 2008 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 com.android.phone;
import android.app.Service;
import android.content.Intent;
import com.android.internal.telephony.gsm.NetworkInfo;
import android.os.AsyncResult;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import android.util.Log;
import java.util.ArrayList;
/**
* Service code used to assist in querying the network for service
* availability.
*/
public class NetworkQueryService extends Service {
// debug data
private static final String LOG_TAG = "NetworkQuery";
private static final boolean DBG = false;
// static events
private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
// static states indicating the query status of the service
private static final int QUERY_READY = -1;
private static final int QUERY_IS_RUNNING = -2;
// error statuses that will be retured in the callback.
public static final int QUERY_OK = 0;
public static final int QUERY_EXCEPTION = 1;
/** state of the query service */
private int mState;
/** local handle to the phone object */
private Phone mPhone;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
INetworkQueryService getService() {
return mBinder;
}
}
private final IBinder mLocalBinder = new LocalBinder();
/**
* Local handler to receive the network query compete callback
* from the RIL.
*/
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// if the scan is complete, broadcast the results.
// to all registerd callbacks.
case EVENT_NETWORK_SCAN_COMPLETED:
if (DBG) log("scan completed, broadcasting results");
broadcastQueryResults((AsyncResult) msg.obj);
break;
}
}
};
/**
* List of callback objects, also used to synchronize access to
* itself and to changes in state.
*/
final RemoteCallbackList<INetworkQueryServiceCallback> mCallbacks =
new RemoteCallbackList<INetworkQueryServiceCallback> ();
/**
* Implementation of the INetworkQueryService interface.
*/
private final INetworkQueryService.Stub mBinder = new INetworkQueryService.Stub() {
/**
* Starts a query with a INetworkQueryServiceCallback object if
* one has not been started yet. Ignore the new query request
* if the query has been started already. Either way, place the
* callback object in the queue to be notified upon request
* completion.
*/
public void startNetworkQuery(INetworkQueryServiceCallback cb) {
if (cb != null) {
// register the callback to the list of callbacks.
synchronized (mCallbacks) {
mCallbacks.register(cb);
if (DBG) log("registering callback " + cb.getClass().toString());
switch (mState) {
case QUERY_READY:
// TODO: we may want to install a timeout here in case we
// do not get a timely response from the RIL.
mPhone.getAvailableNetworks(
mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED));
mState = QUERY_IS_RUNNING;
if (DBG) log("starting new query");
break;
// do nothing if we're currently busy.
case QUERY_IS_RUNNING:
if (DBG) log("query already in progress");
break;
default:
}
}
}
}
/**
* Stops a query with a INetworkQueryServiceCallback object as
* a token.
*/
public void stopNetworkQuery(INetworkQueryServiceCallback cb) {
// currently we just unregister the callback, since there is
// no way to tell the RIL to terminate the query request.
// This means that the RIL may still be busy after the stop
// request was made, but the state tracking logic ensures
// that the delay will only last for 1 request even with
// repeated button presses in the NetworkSetting activity.
if (cb != null) {
synchronized (mCallbacks) {
if (DBG) log("unregistering callback " + cb.getClass().toString());
mCallbacks.unregister(cb);
}
}
}
};
@Override
public void onCreate() {
mState = QUERY_READY;
mPhone = PhoneFactory.getDefaultPhone();
}
/**
* Required for service implementation.
*/
@Override
public void onStart(Intent intent, int startId) {
}
/**
* Handle the bind request.
*/
@Override
public IBinder onBind(Intent intent) {
// TODO: Currently, return only the LocalBinder instance. If we
// end up requiring support for a remote binder, we will need to
// return mBinder as well, depending upon the intent.
if (DBG) log("binding service implementation");
return mLocalBinder;
}
/**
* Broadcast the results from the query to all registered callback
* objects.
*/
private void broadcastQueryResults (AsyncResult ar) {
// reset the state.
synchronized (mCallbacks) {
mState = QUERY_READY;
// see if we need to do any work.
if (ar == null) {
if (DBG) log("AsyncResult is null.");
return;
}
// TODO: we may need greater accuracy here, but for now, just a
// simple status integer will suffice.
int exception = (ar.exception == null) ? QUERY_OK : QUERY_EXCEPTION;
if (DBG) log("AsyncResult has exception " + exception);
// Make the calls to all the registered callbacks.
for (int i = (mCallbacks.beginBroadcast() - 1); i >= 0; i--) {
INetworkQueryServiceCallback cb = mCallbacks.getBroadcastItem(i);
if (DBG) log("broadcasting results to " + cb.getClass().toString());
try {
cb.onQueryComplete((ArrayList<NetworkInfo>) ar.result, exception);
} catch (RemoteException e) {
}
}
// finish up.
mCallbacks.finishBroadcast();
}
}
private static void log(String msg) {
Log.d(LOG_TAG, msg);
}
}
/*
* Copyright (C) 2006 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 com.android.phone;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.util.Log;
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.gsm.NetworkInfo;
import java.util.HashMap;
import java.util.List;
/**
* "Networks" settings UI for the Phone app.
*/
public class NetworkSetting extends PreferenceActivity
implements DialogInterface.OnCancelListener {
private static final String LOG_TAG = "phone";
private static final boolean DBG = false;
private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
private static final int EVENT_NETWORK_SELECTION_DONE = 200;
private static final int EVENT_AUTO_SELECT_DONE = 300;
//dialog ids
private static final int DIALOG_NETWORK_SELECTION = 100;
private static final int DIALOG_NETWORK_LIST_LOAD = 200;
private static final int DIALOG_NETWORK_AUTO_SELECT = 300;
//String keys for preference lookup
private static final String LIST_NETWORKS_KEY = "list_networks_key";
private static final String BUTTON_SRCH_NETWRKS_KEY = "button_srch_netwrks_key";
private static final String BUTTON_AUTO_SELECT_KEY = "button_auto_select_key";
//map of network controls to the network data.
private HashMap<Preference, NetworkInfo> mNetworkMap;
Phone mPhone;
protected boolean mIsForeground = false;
/** message for network selection */
String mNetworkSelectMsg;
//preference objects
private PreferenceGroup mNetworkList;
private Preference mSearchButton;
private Preference mAutoSelect;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_NETWORK_SCAN_COMPLETED:
networksListLoaded ((List<NetworkInfo>) msg.obj, msg.arg1);
break;
case EVENT_NETWORK_SELECTION_DONE:
if (DBG) log("hideProgressPanel");
removeDialog(DIALOG_NETWORK_SELECTION);
getPreferenceScreen().setEnabled(true);
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
if (DBG) log("manual network selection: failed!");
displayNetworkSelectionFailed(ar.exception);
} else {
if (DBG) log("manual network selection: succeeded!");
displayNetworkSelectionSucceeded();
}
break;
case EVENT_AUTO_SELECT_DONE:
if (DBG) log("hideProgressPanel");
if (mIsForeground) {
dismissDialog(DIALOG_NETWORK_AUTO_SELECT);
}
getPreferenceScreen().setEnabled(true);
ar = (AsyncResult) msg.obj;
if (ar.exception != null) {
if (DBG) log("automatic network selection: failed!");
displayNetworkSelectionFailed(ar.exception);
} else {
if (DBG) log("automatic network selection: succeeded!");
displayNetworkSelectionSucceeded();
}
break;
}
return;
}
};
/**
* Service connection code for the NetworkQueryService.
* Handles the work of binding to a local object so that we can make
* the appropriate service calls.
*/
/** Local service interface */
private INetworkQueryService mNetworkQueryService = null;
/** Service connection */
private final ServiceConnection mNetworkQueryServiceConnection = new ServiceConnection() {
/** Handle the task of binding the local object to the service */
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) log("connection created, binding local service.");
mNetworkQueryService = ((NetworkQueryService.LocalBinder) service).getService();
// as soon as it is bound, run a query.
loadNetworksList();
}
/** Handle the task of cleaning up the local binding */
public void onServiceDisconnected(ComponentName className) {
if (DBG) log("connection disconnected, cleaning local binding.");
mNetworkQueryService = null;
}
};
/**
* This implementation of INetworkQueryServiceCallback is used to receive
* callback notifications from the network query service.
*/
private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() {
/** place the message on the looper queue upon query completion. */
public void onQueryComplete(List<NetworkInfo> networkInfoArray, int status) {
if (DBG) log("notifying message loop of query completion.");
Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED,
status, 0, networkInfoArray);
msg.sendToTarget();
}
};
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
boolean handled = false;
if (preference == mSearchButton) {
loadNetworksList();
handled = true;
} else if (preference == mAutoSelect) {
selectNetworkAutomatic();
handled = true;
} else {
Preference selectedCarrier = preference;
String networkStr = selectedCarrier.getTitle().toString();
if (DBG) log("selected network: " + networkStr);
Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE);
mPhone.selectNetworkManually(mNetworkMap.get(selectedCarrier), msg);
displayNetworkSeletionInProgress(networkStr);
handled = true;
}
return handled;
}
//implemented for DialogInterface.OnCancelListener
public void onCancel(DialogInterface dialog) {
// request that the service stop the query with this callback object.
try {
mNetworkQueryService.stopNetworkQuery(mCallback);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
finish();
}
public String getNormalizedCarrierName(NetworkInfo ni) {
if (ni != null) {
return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")";
}
return null;
}
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.carrier_select);
mPhone = PhoneApp.getInstance().phone;
mNetworkList = (PreferenceGroup) getPreferenceScreen().findPreference(LIST_NETWORKS_KEY);
mNetworkMap = new HashMap<Preference, NetworkInfo>();
mSearchButton = getPreferenceScreen().findPreference(BUTTON_SRCH_NETWRKS_KEY);
mAutoSelect = getPreferenceScreen().findPreference(BUTTON_AUTO_SELECT_KEY);
// Start the Network Query service, and bind it.
// The OS knows to start he service only once and keep the instance around (so
// long as startService is called) until a stopservice request is made. Since
// we want this service to just stay in the background until it is killed, we
// don't bother stopping it from our end.
startService (new Intent(this, NetworkQueryService.class));
bindService (new Intent(this, NetworkQueryService.class), mNetworkQueryServiceConnection,
Context.BIND_AUTO_CREATE);
}
@Override
public void onResume() {
super.onResume();
mIsForeground = true;
}
@Override
public void onPause() {
super.onPause();
mIsForeground = false;
}
/**
* Override onDestroy() to unbind the query service, avoiding service
* leak exceptions.
*/
@Override
protected void onDestroy() {
// unbind the service.
unbindService(mNetworkQueryServiceConnection);
super.onDestroy();
}
@Override
protected Dialog onCreateDialog(int id) {
if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) ||
(id == DIALOG_NETWORK_AUTO_SELECT)) {
ProgressDialog dialog = new ProgressDialog(this);
switch (id) {
case DIALOG_NETWORK_SELECTION:
// It would be more efficient to reuse this dialog by moving
// this setMessage() into onPreparedDialog() and NOT use
// removeDialog(). However, this is not possible since the
// message is rendered only 2 times in the ProgressDialog -
// after show() and before onCreate.
dialog.setMessage(mNetworkSelectMsg);
dialog.setCancelable(false);
dialog.setIndeterminate(true);
break;
case DIALOG_NETWORK_AUTO_SELECT:
dialog.setMessage(getResources().getString(R.string.register_automatically));
dialog.setCancelable(false);
dialog.setIndeterminate(true);
break;
case DIALOG_NETWORK_LIST_LOAD:
default:
// reinstate the cancelablity of the dialog.
dialog.setMessage(getResources().getString(R.string.load_networks_progress));
dialog.setCancelable(true);
dialog.setOnCancelListener(this);
break;
}
return dialog;
}
return null;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) ||
(id == DIALOG_NETWORK_AUTO_SELECT)) {
// when the dialogs come up, we'll need to indicate that
// we're in a busy state to dissallow further input.
getPreferenceScreen().setEnabled(false);
}
}
private void displayEmptyNetworkList(boolean flag) {
mNetworkList.setTitle(flag ? R.string.empty_networks_list : R.string.label_available);
}
private void displayNetworkSeletionInProgress(String networkStr) {
// TODO: use notification manager?
mNetworkSelectMsg = getResources().getString(R.string.register_on_network, networkStr);
if (mIsForeground) {
showDialog(DIALOG_NETWORK_SELECTION);
}
}
private void displayNetworkQueryFailed(int error) {
String status = getResources().getString(R.string.network_query_error);
NotificationMgr.getDefault().postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
}
private void displayNetworkSelectionFailed(Throwable ex) {
String status;
if ((ex != null && ex instanceof CommandException) &&
((CommandException)ex).getCommandError()
== CommandException.Error.ILLEGAL_SIM_OR_ME)
{
status = getResources().getString(R.string.not_allowed);
} else {
status = getResources().getString(R.string.connect_later);
}
NotificationMgr.getDefault().postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
}
private void displayNetworkSelectionSucceeded() {
String status = getResources().getString(R.string.registration_done);
NotificationMgr.getDefault().postTransientNotification(
NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
mHandler.postDelayed(new Runnable() {
public void run() {
finish();
}
}, 3000);
}
private void loadNetworksList() {
if (DBG) log("load networks list...");
if (mIsForeground) {
showDialog(DIALOG_NETWORK_LIST_LOAD);
}
// delegate query request to the service.
try {
mNetworkQueryService.startNetworkQuery(mCallback);
} catch (RemoteException e) {
}
displayEmptyNetworkList(false);
}
/**
* networksListLoaded has been rewritten to take an array of
* NetworkInfo objects and a status field, instead of an
* AsyncResult. Otherwise, the functionality which takes the
* NetworkInfo array and creates a list of preferences from it,
* remains unchanged.
*/
private void networksListLoaded(List<NetworkInfo> result, int status) {
if (DBG) log("networks list loaded");
// update the state of the preferences.
if (DBG) log("hideProgressPanel");
if (mIsForeground) {
dismissDialog(DIALOG_NETWORK_LIST_LOAD);
}
getPreferenceScreen().setEnabled(true);
clearList();
if (status != NetworkQueryService.QUERY_OK) {
if (DBG) log("error while querying available networks");
displayNetworkQueryFailed(status);
displayEmptyNetworkList(true);
} else {
if (result != null){
displayEmptyNetworkList(false);
// create a preference for each item in the list.
// just use the operator name instead of the mildly
// confusing mcc/mnc.
for (NetworkInfo ni : result) {
Preference carrier = new Preference(this, null);
carrier.setTitle(ni.getOperatorAlphaLong());
carrier.setPersistent(false);
mNetworkList.addPreference(carrier);
mNetworkMap.put(carrier, ni);
if (DBG) log(" " + ni);
}
} else {
displayEmptyNetworkList(true);
}
}
}
private void clearList() {
for (Preference p : mNetworkMap.keySet()) {
mNetworkList.removePreference(p);
}
mNetworkMap.clear();
}
private void selectNetworkAutomatic() {
if (DBG) log("select network automatically...");
if (mIsForeground) {
showDialog(DIALOG_NETWORK_AUTO_SELECT);
}
Message msg = mHandler.obtainMessage(EVENT_AUTO_SELECT_DONE);
mPhone.setNetworkSelectionModeAutomatic(msg);
}
private void log(String msg) {
Log.d(LOG_TAG, "[NetworksList] " + msg);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment