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