summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/CommonTimeManagementService.java
blob: aa2c8b88a77e5648bd6f3f77b8ff8b0bb5db643b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.InterfaceConfiguration;
import android.net.NetworkInfo;
import android.os.Binder;
import android.os.CommonTimeConfig;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.Log;

/**
 * @hide
 * <p>CommonTimeManagementService manages the configuration of the native Common Time service,
 * reconfiguring the native service as appropriate in response to changes in network configuration.
 */
class CommonTimeManagementService extends Binder {
    /*
     * Constants and globals.
     */
    private static final String TAG = CommonTimeManagementService.class.getSimpleName();
    private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000;
    private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable";
    private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi";
    private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio";
    private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout";
    private static final boolean AUTO_DISABLE;
    private static final boolean ALLOW_WIFI;
    private static final byte BASE_SERVER_PRIO;
    private static final int NO_INTERFACE_TIMEOUT;
    private static final InterfaceScoreRule[] IFACE_SCORE_RULES;

    static {
        int tmp;
        AUTO_DISABLE         = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1));
        ALLOW_WIFI           = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0));
        tmp                  = SystemProperties.getInt(SERVER_PRIO_PROP, 1);
        NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000);

        if (tmp < 1)
            BASE_SERVER_PRIO = 1;
        else
        if (tmp > 30)
            BASE_SERVER_PRIO = 30;
        else
            BASE_SERVER_PRIO = (byte)tmp;

        if (ALLOW_WIFI) {
            IFACE_SCORE_RULES = new InterfaceScoreRule[] {
                new InterfaceScoreRule("wlan", (byte)1),
                new InterfaceScoreRule("eth", (byte)2),
            };
        } else {
            IFACE_SCORE_RULES = new InterfaceScoreRule[] {
                new InterfaceScoreRule("eth", (byte)2),
            };
        }
    };

    /*
     * Internal state
     */
    private final Context mContext;
    private INetworkManagementService mNetMgr;
    private CommonTimeConfig mCTConfig;
    private String mCurIface;
    private Handler mReconnectHandler = new Handler();
    private Handler mNoInterfaceHandler = new Handler();
    private Object mLock = new Object();
    private boolean mDetectedAtStartup = false;
    private byte mEffectivePrio = BASE_SERVER_PRIO;

    /*
     * Callback handler implementations.
     */
    private INetworkManagementEventObserver mIfaceObserver =
        new INetworkManagementEventObserver.Stub() {

        public void interfaceStatusChanged(String iface, boolean up) {
            reevaluateServiceState();
        }
        public void interfaceLinkStateChanged(String iface, boolean up) {
            reevaluateServiceState();
        }
        public void interfaceAdded(String iface) {
            reevaluateServiceState();
        }
        public void interfaceRemoved(String iface) {
            reevaluateServiceState();
        }
        public void limitReached(String limitName, String iface) { }

        public void interfaceClassDataActivityChanged(String label, boolean active) {}
    };

    private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            reevaluateServiceState();
        }
    };

    private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener =
        new CommonTimeConfig.OnServerDiedListener() {
            public void onServerDied() {
                scheduleTimeConfigReconnect();
            }
        };

    private Runnable mReconnectRunnable = new Runnable() {
        public void run() { connectToTimeConfig(); }
    };

    private Runnable mNoInterfaceRunnable = new Runnable() {
        public void run() { handleNoInterfaceTimeout(); }
    };

    /*
     * Public interface (constructor, systemReady and dump)
     */
    public CommonTimeManagementService(Context context) {
        mContext = context;
    }

    void systemRunning() {
        if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) {
            Log.i(TAG, "No common time service detected on this platform.  " +
                       "Common time services will be unavailable.");
            return;
        }

        mDetectedAtStartup = true;

        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        mNetMgr = INetworkManagementService.Stub.asInterface(b);

        // Network manager is running along-side us, so we should never receiver a remote exception
        // while trying to register this observer.
        try {
            mNetMgr.registerObserver(mIfaceObserver);
        }
        catch (RemoteException e) { }

        // Register with the connectivity manager for connectivity changed intents.
        IntentFilter filter = new IntentFilter();
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        mContext.registerReceiver(mConnectivityMangerObserver, filter);

        // Connect to the common time config service and apply the initial configuration.
        connectToTimeConfig();
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                != PackageManager.PERMISSION_GRANTED) {
            pw.println(String.format(
                        "Permission Denial: can't dump CommonTimeManagement service from from " +
                        "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid()));
            return;
        }

        if (!mDetectedAtStartup) {
            pw.println("Native Common Time service was not detected at startup.  " +
                       "Service is unavailable");
            return;
        }

        synchronized (mLock) {
            pw.println("Current Common Time Management Service Config:");
            pw.println(String.format("  Native service     : %s",
                                     (null == mCTConfig) ? "reconnecting"
                                                         : "alive"));
            pw.println(String.format("  Bound interface    : %s",
                                     (null == mCurIface ? "unbound" : mCurIface)));
            pw.println(String.format("  Allow WiFi         : %s", ALLOW_WIFI ? "yes" : "no"));
            pw.println(String.format("  Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no"));
            pw.println(String.format("  Server Priority    : %d", mEffectivePrio));
            pw.println(String.format("  No iface timeout   : %d", NO_INTERFACE_TIMEOUT));
        }
    }

    /*
     * Inner helper classes
     */
    private static class InterfaceScoreRule {
        public final String mPrefix;
        public final byte mScore;
        public InterfaceScoreRule(String prefix, byte score) {
            mPrefix = prefix;
            mScore = score;
        }
    };

    /*
     * Internal implementation
     */
    private void cleanupTimeConfig() {
        mReconnectHandler.removeCallbacks(mReconnectRunnable);
        mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
        if (null != mCTConfig) {
            mCTConfig.release();
            mCTConfig = null;
        }
    }

    private void connectToTimeConfig() {
        // Get access to the common time service configuration interface.  If we catch a remote
        // exception in the process (service crashed or no running for w/e reason), schedule an
        // attempt to reconnect in the future.
        cleanupTimeConfig();
        try {
            synchronized (mLock) {
                mCTConfig = new CommonTimeConfig();
                mCTConfig.setServerDiedListener(mCTServerDiedListener);
                mCurIface = mCTConfig.getInterfaceBinding();
                mCTConfig.setAutoDisable(AUTO_DISABLE);
                mCTConfig.setMasterElectionPriority(mEffectivePrio);
            }

            if (NO_INTERFACE_TIMEOUT >= 0)
                mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);

            reevaluateServiceState();
        }
        catch (RemoteException e) {
            scheduleTimeConfigReconnect();
        }
    }

    private void scheduleTimeConfigReconnect() {
        cleanupTimeConfig();
        Log.w(TAG, String.format("Native service died, will reconnect in %d mSec",
                                 NATIVE_SERVICE_RECONNECT_TIMEOUT));
        mReconnectHandler.postDelayed(mReconnectRunnable,
                                      NATIVE_SERVICE_RECONNECT_TIMEOUT);
    }

    private void handleNoInterfaceTimeout() {
        if (null != mCTConfig) {
            Log.i(TAG, "Timeout waiting for interface to come up.  " +
                       "Forcing networkless master mode.");
            if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode())
                scheduleTimeConfigReconnect();
        }
    }

    private void reevaluateServiceState() {
        String bindIface = null;
        byte bestScore = -1;
        try {
            // Check to see if this interface is suitable to use for time synchronization.
            //
            // TODO : This selection algorithm needs to be enhanced for use with mobile devices.  In
            // particular, the choice of whether to a wireless interface or not should not be an all
            // or nothing thing controlled by properties.  It would probably be better if the
            // platform had some concept of public wireless networks vs. home or friendly wireless
            // networks (something a user would configure in settings or when a new interface is
            // added).  Then this algorithm could pick only wireless interfaces which were flagged
            // as friendly, and be dormant when on public wireless networks.
            //
            // Another issue which needs to be dealt with is the use of driver supplied interface
            // name to determine the network type.  The fact that the wireless interface on a device
            // is named "wlan0" is just a matter of convention; its not a 100% rule.  For example,
            // there are devices out there where the wireless is name "tiwlan0", not "wlan0".  The
            // internal network management interfaces in Android have all of the information needed
            // to make a proper classification, there is just no way (currently) to fetch an
            // interface's type (available from the ConnectionManager) as well as its address
            // (available from either the java.net interfaces or from the NetworkManagment service).
            // Both can enumerate interfaces, but that is no way to correlate their results (no
            // common shared key; although using the interface name in the connection manager would
            // be a good start).  Until this gets resolved, we resort to substring searching for
            // tags like wlan and eth.
            //
            String ifaceList[] = mNetMgr.listInterfaces();
            if (null != ifaceList) {
                for (String iface : ifaceList) {

                    byte thisScore = -1;
                    for (InterfaceScoreRule r : IFACE_SCORE_RULES) {
                        if (iface.contains(r.mPrefix)) {
                            thisScore = r.mScore;
                            break;
                        }
                    }

                    if (thisScore <= bestScore)
                        continue;

                    InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface);
                    if (null == config)
                        continue;

                    if (config.isActive()) {
                        bindIface = iface;
                        bestScore = thisScore;
                    }
                }
            }
        }
        catch (RemoteException e) {
            // Bad news; we should not be getting remote exceptions from the connectivity manager
            // since it is running in SystemServer along side of us.  It probably does not matter
            // what we do here, but go ahead and unbind the common time service in this case, just
            // so we have some defined behavior.
            bindIface = null;
        }

        boolean doRebind = true;
        synchronized (mLock) {
            if ((null != bindIface) && (null == mCurIface)) {
                Log.e(TAG, String.format("Binding common time service to %s.", bindIface));
                mCurIface = bindIface;
            } else
            if ((null == bindIface) && (null != mCurIface)) {
                Log.e(TAG, "Unbinding common time service.");
                mCurIface = null;
            } else
            if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) {
                Log.e(TAG, String.format("Switching common time service binding from %s to %s.",
                                         mCurIface, bindIface));
                mCurIface = bindIface;
            } else {
                doRebind = false;
            }
        }

        if (doRebind && (null != mCTConfig)) {
            byte newPrio = (bestScore > 0)
                         ? (byte)(bestScore * BASE_SERVER_PRIO)
                         : BASE_SERVER_PRIO;
            if (newPrio != mEffectivePrio) {
                mEffectivePrio = newPrio;
                mCTConfig.setMasterElectionPriority(mEffectivePrio);
            }

            int res = mCTConfig.setNetworkBinding(mCurIface);
            if (res != CommonTimeConfig.SUCCESS)
                scheduleTimeConfigReconnect();

            else if (NO_INTERFACE_TIMEOUT >= 0) {
                mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
                if (null == mCurIface)
                    mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
            }
        }
    }
}