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