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