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