CommonTimeManagementService.java revision c157673a590e670a9a509ec13d47ffada0b56335
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
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}
378