blob: 60b366aca5bc22dae0c89cbf3a0e571ea0d5e3f7 [file] [log] [blame]
/*
* Copyright (C) 2012 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.server;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.InterfaceConfiguration;
import android.os.Binder;
import android.os.CommonTimeConfig;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.Log;
import com.android.server.net.BaseNetworkObserver;
/**
* @hide
* <p>CommonTimeManagementService manages the configuration of the native Common Time service,
* reconfiguring the native service as appropriate in response to changes in network configuration.
*/
class CommonTimeManagementService extends Binder {
/*
* Constants and globals.
*/
private static final String TAG = CommonTimeManagementService.class.getSimpleName();
private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000;
private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable";
private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi";
private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio";
private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout";
private static final boolean AUTO_DISABLE;
private static final boolean ALLOW_WIFI;
private static final byte BASE_SERVER_PRIO;
private static final int NO_INTERFACE_TIMEOUT;
private static final InterfaceScoreRule[] IFACE_SCORE_RULES;
static {
int tmp;
AUTO_DISABLE = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1));
ALLOW_WIFI = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0));
tmp = SystemProperties.getInt(SERVER_PRIO_PROP, 1);
NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000);
if (tmp < 1)
BASE_SERVER_PRIO = 1;
else
if (tmp > 30)
BASE_SERVER_PRIO = 30;
else
BASE_SERVER_PRIO = (byte)tmp;
if (ALLOW_WIFI) {
IFACE_SCORE_RULES = new InterfaceScoreRule[] {
new InterfaceScoreRule("wlan", (byte)1),
new InterfaceScoreRule("eth", (byte)2),
};
} else {
IFACE_SCORE_RULES = new InterfaceScoreRule[] {
new InterfaceScoreRule("eth", (byte)2),
};
}
};
/*
* Internal state
*/
private final Context mContext;
private INetworkManagementService mNetMgr;
private CommonTimeConfig mCTConfig;
private String mCurIface;
private Handler mReconnectHandler = new Handler();
private Handler mNoInterfaceHandler = new Handler();
private Object mLock = new Object();
private boolean mDetectedAtStartup = false;
private byte mEffectivePrio = BASE_SERVER_PRIO;
/*
* Callback handler implementations.
*/
private INetworkManagementEventObserver mIfaceObserver = new BaseNetworkObserver() {
public void interfaceStatusChanged(String iface, boolean up) {
reevaluateServiceState();
}
public void interfaceLinkStateChanged(String iface, boolean up) {
reevaluateServiceState();
}
public void interfaceAdded(String iface) {
reevaluateServiceState();
}
public void interfaceRemoved(String iface) {
reevaluateServiceState();
}
};
private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
reevaluateServiceState();
}
};
private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener =
new CommonTimeConfig.OnServerDiedListener() {
public void onServerDied() {
scheduleTimeConfigReconnect();
}
};
private Runnable mReconnectRunnable = new Runnable() {
public void run() { connectToTimeConfig(); }
};
private Runnable mNoInterfaceRunnable = new Runnable() {
public void run() { handleNoInterfaceTimeout(); }
};
/*
* Public interface (constructor, systemReady and dump)
*/
public CommonTimeManagementService(Context context) {
mContext = context;
}
void systemRunning() {
if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) {
Log.i(TAG, "No common time service detected on this platform. " +
"Common time services will be unavailable.");
return;
}
mDetectedAtStartup = true;
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
mNetMgr = INetworkManagementService.Stub.asInterface(b);
// Network manager is running along-side us, so we should never receiver a remote exception
// while trying to register this observer.
try {
mNetMgr.registerObserver(mIfaceObserver);
}
catch (RemoteException e) { }
// Register with the connectivity manager for connectivity changed intents.
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(mConnectivityMangerObserver, filter);
// Connect to the common time config service and apply the initial configuration.
connectToTimeConfig();
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println(String.format(
"Permission Denial: can't dump CommonTimeManagement service from from " +
"pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid()));
return;
}
if (!mDetectedAtStartup) {
pw.println("Native Common Time service was not detected at startup. " +
"Service is unavailable");
return;
}
synchronized (mLock) {
pw.println("Current Common Time Management Service Config:");
pw.println(String.format(" Native service : %s",
(null == mCTConfig) ? "reconnecting"
: "alive"));
pw.println(String.format(" Bound interface : %s",
(null == mCurIface ? "unbound" : mCurIface)));
pw.println(String.format(" Allow WiFi : %s", ALLOW_WIFI ? "yes" : "no"));
pw.println(String.format(" Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no"));
pw.println(String.format(" Server Priority : %d", mEffectivePrio));
pw.println(String.format(" No iface timeout : %d", NO_INTERFACE_TIMEOUT));
}
}
/*
* Inner helper classes
*/
private static class InterfaceScoreRule {
public final String mPrefix;
public final byte mScore;
public InterfaceScoreRule(String prefix, byte score) {
mPrefix = prefix;
mScore = score;
}
};
/*
* Internal implementation
*/
private void cleanupTimeConfig() {
mReconnectHandler.removeCallbacks(mReconnectRunnable);
mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
if (null != mCTConfig) {
mCTConfig.release();
mCTConfig = null;
}
}
private void connectToTimeConfig() {
// Get access to the common time service configuration interface. If we catch a remote
// exception in the process (service crashed or no running for w/e reason), schedule an
// attempt to reconnect in the future.
cleanupTimeConfig();
try {
synchronized (mLock) {
mCTConfig = new CommonTimeConfig();
mCTConfig.setServerDiedListener(mCTServerDiedListener);
mCurIface = mCTConfig.getInterfaceBinding();
mCTConfig.setAutoDisable(AUTO_DISABLE);
mCTConfig.setMasterElectionPriority(mEffectivePrio);
}
if (NO_INTERFACE_TIMEOUT >= 0)
mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
reevaluateServiceState();
}
catch (RemoteException e) {
scheduleTimeConfigReconnect();
}
}
private void scheduleTimeConfigReconnect() {
cleanupTimeConfig();
Log.w(TAG, String.format("Native service died, will reconnect in %d mSec",
NATIVE_SERVICE_RECONNECT_TIMEOUT));
mReconnectHandler.postDelayed(mReconnectRunnable,
NATIVE_SERVICE_RECONNECT_TIMEOUT);
}
private void handleNoInterfaceTimeout() {
if (null != mCTConfig) {
Log.i(TAG, "Timeout waiting for interface to come up. " +
"Forcing networkless master mode.");
if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode())
scheduleTimeConfigReconnect();
}
}
private void reevaluateServiceState() {
String bindIface = null;
byte bestScore = -1;
try {
// Check to see if this interface is suitable to use for time synchronization.
//
// TODO : This selection algorithm needs to be enhanced for use with mobile devices. In
// particular, the choice of whether to a wireless interface or not should not be an all
// or nothing thing controlled by properties. It would probably be better if the
// platform had some concept of public wireless networks vs. home or friendly wireless
// networks (something a user would configure in settings or when a new interface is
// added). Then this algorithm could pick only wireless interfaces which were flagged
// as friendly, and be dormant when on public wireless networks.
//
// Another issue which needs to be dealt with is the use of driver supplied interface
// name to determine the network type. The fact that the wireless interface on a device
// is named "wlan0" is just a matter of convention; its not a 100% rule. For example,
// there are devices out there where the wireless is name "tiwlan0", not "wlan0". The
// internal network management interfaces in Android have all of the information needed
// to make a proper classification, there is just no way (currently) to fetch an
// interface's type (available from the ConnectionManager) as well as its address
// (available from either the java.net interfaces or from the NetworkManagment service).
// Both can enumerate interfaces, but that is no way to correlate their results (no
// common shared key; although using the interface name in the connection manager would
// be a good start). Until this gets resolved, we resort to substring searching for
// tags like wlan and eth.
//
String ifaceList[] = mNetMgr.listInterfaces();
if (null != ifaceList) {
for (String iface : ifaceList) {
byte thisScore = -1;
for (InterfaceScoreRule r : IFACE_SCORE_RULES) {
if (iface.contains(r.mPrefix)) {
thisScore = r.mScore;
break;
}
}
if (thisScore <= bestScore)
continue;
InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface);
if (null == config)
continue;
if (config.isActive()) {
bindIface = iface;
bestScore = thisScore;
}
}
}
}
catch (RemoteException e) {
// Bad news; we should not be getting remote exceptions from the connectivity manager
// since it is running in SystemServer along side of us. It probably does not matter
// what we do here, but go ahead and unbind the common time service in this case, just
// so we have some defined behavior.
bindIface = null;
}
boolean doRebind = true;
synchronized (mLock) {
if ((null != bindIface) && (null == mCurIface)) {
Log.e(TAG, String.format("Binding common time service to %s.", bindIface));
mCurIface = bindIface;
} else
if ((null == bindIface) && (null != mCurIface)) {
Log.e(TAG, "Unbinding common time service.");
mCurIface = null;
} else
if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) {
Log.e(TAG, String.format("Switching common time service binding from %s to %s.",
mCurIface, bindIface));
mCurIface = bindIface;
} else {
doRebind = false;
}
}
if (doRebind && (null != mCTConfig)) {
byte newPrio = (bestScore > 0)
? (byte)(bestScore * BASE_SERVER_PRIO)
: BASE_SERVER_PRIO;
if (newPrio != mEffectivePrio) {
mEffectivePrio = newPrio;
mCTConfig.setMasterElectionPriority(mEffectivePrio);
}
int res = mCTConfig.setNetworkBinding(mCurIface);
if (res != CommonTimeConfig.SUCCESS)
scheduleTimeConfigReconnect();
else if (NO_INTERFACE_TIMEOUT >= 0) {
mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
if (null == mCurIface)
mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
}
}
}
}