John Grossman | c157673 | 2012-02-01 15:23:33 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 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 | |
| 17 | package com.android.server; |
| 18 | |
| 19 | import java.io.FileDescriptor; |
| 20 | import java.io.PrintWriter; |
| 21 | import java.net.InetAddress; |
| 22 | |
| 23 | import android.content.BroadcastReceiver; |
| 24 | import android.content.Context; |
| 25 | import android.content.Intent; |
| 26 | import android.content.IntentFilter; |
| 27 | import android.content.pm.PackageManager; |
| 28 | import android.net.ConnectivityManager; |
| 29 | import android.net.IConnectivityManager; |
| 30 | import android.net.INetworkManagementEventObserver; |
| 31 | import android.net.InterfaceConfiguration; |
| 32 | import android.net.NetworkInfo; |
| 33 | import android.os.Binder; |
| 34 | import android.os.CommonTimeConfig; |
| 35 | import android.os.Handler; |
| 36 | import android.os.IBinder; |
| 37 | import android.os.INetworkManagementService; |
| 38 | import android.os.RemoteException; |
| 39 | import android.os.ServiceManager; |
| 40 | import android.os.SystemProperties; |
| 41 | import android.util.Log; |
| 42 | |
| 43 | /** |
| 44 | * @hide |
| 45 | * <p>CommonTimeManagementService manages the configuration of the native Common Time service, |
| 46 | * reconfiguring the native service as appropriate in response to changes in network configuration. |
| 47 | */ |
| 48 | class CommonTimeManagementService extends Binder { |
| 49 | /* |
| 50 | * Constants and globals. |
| 51 | */ |
| 52 | private static final String TAG = CommonTimeManagementService.class.getSimpleName(); |
| 53 | private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000; |
| 54 | private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable"; |
| 55 | private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi"; |
| 56 | private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio"; |
| 57 | private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout"; |
| 58 | private static final boolean AUTO_DISABLE; |
| 59 | private static final boolean ALLOW_WIFI; |
| 60 | private static final byte BASE_SERVER_PRIO; |
| 61 | private static final int NO_INTERFACE_TIMEOUT; |
| 62 | private static final InterfaceScoreRule[] IFACE_SCORE_RULES; |
| 63 | |
| 64 | static { |
| 65 | int tmp; |
| 66 | AUTO_DISABLE = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1)); |
| 67 | ALLOW_WIFI = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0)); |
| 68 | tmp = SystemProperties.getInt(SERVER_PRIO_PROP, 1); |
| 69 | NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000); |
| 70 | |
| 71 | if (tmp < 1) |
| 72 | BASE_SERVER_PRIO = 1; |
| 73 | else |
| 74 | if (tmp > 30) |
| 75 | BASE_SERVER_PRIO = 30; |
| 76 | else |
| 77 | BASE_SERVER_PRIO = (byte)tmp; |
| 78 | |
| 79 | if (ALLOW_WIFI) { |
| 80 | IFACE_SCORE_RULES = new InterfaceScoreRule[] { |
| 81 | new InterfaceScoreRule("wlan", (byte)1), |
| 82 | new InterfaceScoreRule("eth", (byte)2), |
| 83 | }; |
| 84 | } else { |
| 85 | IFACE_SCORE_RULES = new InterfaceScoreRule[] { |
| 86 | new InterfaceScoreRule("eth", (byte)2), |
| 87 | }; |
| 88 | } |
| 89 | }; |
| 90 | |
| 91 | /* |
| 92 | * Internal state |
| 93 | */ |
| 94 | private final Context mContext; |
| 95 | private INetworkManagementService mNetMgr; |
| 96 | private CommonTimeConfig mCTConfig; |
| 97 | private String mCurIface; |
| 98 | private Handler mReconnectHandler = new Handler(); |
| 99 | private Handler mNoInterfaceHandler = new Handler(); |
| 100 | private Object mLock = new Object(); |
| 101 | private boolean mDetectedAtStartup = false; |
| 102 | private byte mEffectivePrio = BASE_SERVER_PRIO; |
| 103 | |
| 104 | /* |
| 105 | * Callback handler implementations. |
| 106 | */ |
| 107 | private INetworkManagementEventObserver mIfaceObserver = |
| 108 | new INetworkManagementEventObserver.Stub() { |
| 109 | |
| 110 | public void interfaceStatusChanged(String iface, boolean up) { |
| 111 | reevaluateServiceState(); |
| 112 | } |
| 113 | public void interfaceLinkStateChanged(String iface, boolean up) { |
| 114 | reevaluateServiceState(); |
| 115 | } |
| 116 | public void interfaceAdded(String iface) { |
| 117 | reevaluateServiceState(); |
| 118 | } |
| 119 | public void interfaceRemoved(String iface) { |
| 120 | reevaluateServiceState(); |
| 121 | } |
| 122 | public void limitReached(String limitName, String iface) { } |
| 123 | }; |
| 124 | |
| 125 | private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() { |
| 126 | @Override |
| 127 | public void onReceive(Context context, Intent intent) { |
| 128 | reevaluateServiceState(); |
| 129 | } |
| 130 | }; |
| 131 | |
| 132 | private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener = |
| 133 | new CommonTimeConfig.OnServerDiedListener() { |
| 134 | public void onServerDied() { |
| 135 | scheduleTimeConfigReconnect(); |
| 136 | } |
| 137 | }; |
| 138 | |
| 139 | private Runnable mReconnectRunnable = new Runnable() { |
| 140 | public void run() { connectToTimeConfig(); } |
| 141 | }; |
| 142 | |
| 143 | private Runnable mNoInterfaceRunnable = new Runnable() { |
| 144 | public void run() { handleNoInterfaceTimeout(); } |
| 145 | }; |
| 146 | |
| 147 | /* |
| 148 | * Public interface (constructor, systemReady and dump) |
| 149 | */ |
| 150 | public CommonTimeManagementService(Context context) { |
| 151 | mContext = context; |
| 152 | } |
| 153 | |
| 154 | void systemReady() { |
| 155 | if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) { |
| 156 | Log.i(TAG, "No common time service detected on this platform. " + |
| 157 | "Common time services will be unavailable."); |
| 158 | return; |
| 159 | } |
| 160 | |
| 161 | mDetectedAtStartup = true; |
| 162 | |
| 163 | IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); |
| 164 | mNetMgr = INetworkManagementService.Stub.asInterface(b); |
| 165 | |
| 166 | // Network manager is running along-side us, so we should never receiver a remote exception |
| 167 | // while trying to register this observer. |
| 168 | try { |
| 169 | mNetMgr.registerObserver(mIfaceObserver); |
| 170 | } |
| 171 | catch (RemoteException e) { } |
| 172 | |
| 173 | // Register with the connectivity manager for connectivity changed intents. |
| 174 | IntentFilter filter = new IntentFilter(); |
| 175 | filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); |
| 176 | mContext.registerReceiver(mConnectivityMangerObserver, filter); |
| 177 | |
| 178 | // Connect to the common time config service and apply the initial configuration. |
| 179 | connectToTimeConfig(); |
| 180 | } |
| 181 | |
| 182 | @Override |
| 183 | protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| 184 | if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) |
| 185 | != PackageManager.PERMISSION_GRANTED) { |
| 186 | pw.println(String.format( |
| 187 | "Permission Denial: can't dump CommonTimeManagement service from from " + |
| 188 | "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid())); |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | if (!mDetectedAtStartup) { |
| 193 | pw.println("Native Common Time service was not detected at startup. " + |
| 194 | "Service is unavailable"); |
| 195 | return; |
| 196 | } |
| 197 | |
| 198 | synchronized (mLock) { |
| 199 | pw.println("Current Common Time Management Service Config:"); |
| 200 | pw.println(String.format(" Native service : %s", |
| 201 | (null == mCTConfig) ? "reconnecting" |
| 202 | : "alive")); |
| 203 | pw.println(String.format(" Bound interface : %s", |
| 204 | (null == mCurIface ? "unbound" : mCurIface))); |
| 205 | pw.println(String.format(" Allow WiFi : %s", ALLOW_WIFI ? "yes" : "no")); |
| 206 | pw.println(String.format(" Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no")); |
| 207 | pw.println(String.format(" Server Priority : %d", mEffectivePrio)); |
| 208 | pw.println(String.format(" No iface timeout : %d", NO_INTERFACE_TIMEOUT)); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | /* |
| 213 | * Inner helper classes |
| 214 | */ |
| 215 | private static class InterfaceScoreRule { |
| 216 | public final String mPrefix; |
| 217 | public final byte mScore; |
| 218 | public InterfaceScoreRule(String prefix, byte score) { |
| 219 | mPrefix = prefix; |
| 220 | mScore = score; |
| 221 | } |
| 222 | }; |
| 223 | |
| 224 | /* |
| 225 | * Internal implementation |
| 226 | */ |
| 227 | private void cleanupTimeConfig() { |
| 228 | mReconnectHandler.removeCallbacks(mReconnectRunnable); |
| 229 | mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable); |
| 230 | if (null != mCTConfig) { |
| 231 | mCTConfig.release(); |
| 232 | mCTConfig = null; |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | private void connectToTimeConfig() { |
| 237 | // Get access to the common time service configuration interface. If we catch a remote |
| 238 | // exception in the process (service crashed or no running for w/e reason), schedule an |
| 239 | // attempt to reconnect in the future. |
| 240 | cleanupTimeConfig(); |
| 241 | try { |
| 242 | synchronized (mLock) { |
| 243 | mCTConfig = new CommonTimeConfig(); |
| 244 | mCTConfig.setServerDiedListener(mCTServerDiedListener); |
| 245 | mCurIface = mCTConfig.getInterfaceBinding(); |
| 246 | mCTConfig.setAutoDisable(AUTO_DISABLE); |
| 247 | mCTConfig.setMasterElectionPriority(mEffectivePrio); |
| 248 | } |
| 249 | |
| 250 | if (NO_INTERFACE_TIMEOUT >= 0) |
| 251 | mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT); |
| 252 | |
| 253 | reevaluateServiceState(); |
| 254 | } |
| 255 | catch (RemoteException e) { |
| 256 | scheduleTimeConfigReconnect(); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | private void scheduleTimeConfigReconnect() { |
| 261 | cleanupTimeConfig(); |
| 262 | Log.w(TAG, String.format("Native service died, will reconnect in %d mSec", |
| 263 | NATIVE_SERVICE_RECONNECT_TIMEOUT)); |
| 264 | mReconnectHandler.postDelayed(mReconnectRunnable, |
| 265 | NATIVE_SERVICE_RECONNECT_TIMEOUT); |
| 266 | } |
| 267 | |
| 268 | private void handleNoInterfaceTimeout() { |
| 269 | if (null != mCTConfig) { |
| 270 | Log.i(TAG, "Timeout waiting for interface to come up. " + |
| 271 | "Forcing networkless master mode."); |
| 272 | if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode()) |
| 273 | scheduleTimeConfigReconnect(); |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | private void reevaluateServiceState() { |
| 278 | String bindIface = null; |
| 279 | byte bestScore = -1; |
| 280 | try { |
| 281 | // Check to see if this interface is suitable to use for time synchronization. |
| 282 | // |
| 283 | // TODO : This selection algorithm needs to be enhanced for use with mobile devices. In |
| 284 | // particular, the choice of whether to a wireless interface or not should not be an all |
| 285 | // or nothing thing controlled by properties. It would probably be better if the |
| 286 | // platform had some concept of public wireless networks vs. home or friendly wireless |
| 287 | // networks (something a user would configure in settings or when a new interface is |
| 288 | // added). Then this algorithm could pick only wireless interfaces which were flagged |
| 289 | // as friendly, and be dormant when on public wireless networks. |
| 290 | // |
| 291 | // Another issue which needs to be dealt with is the use of driver supplied interface |
| 292 | // name to determine the network type. The fact that the wireless interface on a device |
| 293 | // is named "wlan0" is just a matter of convention; its not a 100% rule. For example, |
| 294 | // there are devices out there where the wireless is name "tiwlan0", not "wlan0". The |
| 295 | // internal network management interfaces in Android have all of the information needed |
| 296 | // to make a proper classification, there is just no way (currently) to fetch an |
| 297 | // interface's type (available from the ConnectionManager) as well as its address |
| 298 | // (available from either the java.net interfaces or from the NetworkManagment service). |
| 299 | // Both can enumerate interfaces, but that is no way to correlate their results (no |
| 300 | // common shared key; although using the interface name in the connection manager would |
| 301 | // be a good start). Until this gets resolved, we resort to substring searching for |
| 302 | // tags like wlan and eth. |
| 303 | // |
| 304 | String ifaceList[] = mNetMgr.listInterfaces(); |
| 305 | if (null != ifaceList) { |
| 306 | for (String iface : ifaceList) { |
| 307 | |
| 308 | byte thisScore = -1; |
| 309 | for (InterfaceScoreRule r : IFACE_SCORE_RULES) { |
| 310 | if (iface.contains(r.mPrefix)) { |
| 311 | thisScore = r.mScore; |
| 312 | break; |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | if (thisScore <= bestScore) |
| 317 | continue; |
| 318 | |
| 319 | InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface); |
| 320 | if (null == config) |
| 321 | continue; |
| 322 | |
| 323 | if (config.isActive()) { |
| 324 | bindIface = iface; |
| 325 | bestScore = thisScore; |
| 326 | } |
| 327 | } |
| 328 | } |
| 329 | } |
| 330 | catch (RemoteException e) { |
| 331 | // Bad news; we should not be getting remote exceptions from the connectivity manager |
| 332 | // since it is running in SystemServer along side of us. It probably does not matter |
| 333 | // what we do here, but go ahead and unbind the common time service in this case, just |
| 334 | // so we have some defined behavior. |
| 335 | bindIface = null; |
| 336 | } |
| 337 | |
| 338 | boolean doRebind = true; |
| 339 | synchronized (mLock) { |
| 340 | if ((null != bindIface) && (null == mCurIface)) { |
| 341 | Log.e(TAG, String.format("Binding common time service to %s.", bindIface)); |
| 342 | mCurIface = bindIface; |
| 343 | } else |
| 344 | if ((null == bindIface) && (null != mCurIface)) { |
| 345 | Log.e(TAG, "Unbinding common time service."); |
| 346 | mCurIface = null; |
| 347 | } else |
| 348 | if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) { |
| 349 | Log.e(TAG, String.format("Switching common time service binding from %s to %s.", |
| 350 | mCurIface, bindIface)); |
| 351 | mCurIface = bindIface; |
| 352 | } else { |
| 353 | doRebind = false; |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | if (doRebind && (null != mCTConfig)) { |
| 358 | byte newPrio = (bestScore > 0) |
| 359 | ? (byte)(bestScore * BASE_SERVER_PRIO) |
| 360 | : BASE_SERVER_PRIO; |
| 361 | if (newPrio != mEffectivePrio) { |
| 362 | mEffectivePrio = newPrio; |
| 363 | mCTConfig.setMasterElectionPriority(mEffectivePrio); |
| 364 | } |
| 365 | |
| 366 | int res = mCTConfig.setNetworkBinding(mCurIface); |
| 367 | if (res != CommonTimeConfig.SUCCESS) |
| 368 | scheduleTimeConfigReconnect(); |
| 369 | |
| 370 | else if (NO_INTERFACE_TIMEOUT >= 0) { |
| 371 | mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable); |
| 372 | if (null == mCurIface) |
| 373 | mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT); |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | } |