c157673a59
Add a small service to the high level core set of system services to control the configuration of the native common time service. This service is responsible for controlling policy regarding when the common time service should be allowed to run, which networks it is allowed to run on, what priority it runs at in the master election algorithm, and so on. Change-Id: I1fcd834c0286aea0df9557520693a3f42de59d69 Signed-off-by: John Grossman <johngro@google.com>
378 lines
15 KiB
Java
378 lines
15 KiB
Java
/*
|
|
* 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 java.net.InetAddress;
|
|
|
|
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.IConnectivityManager;
|
|
import android.net.INetworkManagementEventObserver;
|
|
import android.net.InterfaceConfiguration;
|
|
import android.net.NetworkInfo;
|
|
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;
|
|
|
|
/**
|
|
* @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 INetworkManagementEventObserver.Stub() {
|
|
|
|
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();
|
|
}
|
|
public void limitReached(String limitName, String iface) { }
|
|
};
|
|
|
|
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 systemReady() {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|