| /* |
| * 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) { } |
| |
| public void interfaceClassDataActivityChanged(String label, boolean active) {} |
| }; |
| |
| 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); |
| } |
| } |
| } |
| } |