Vpn.java revision 899223b97c9b0ae56a8211a46600914c0ecfd854
1/*
2 * Copyright (C) 2011 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.connectivity;
18
19import static android.Manifest.permission.BIND_VPN_SERVICE;
20
21import android.app.Notification;
22import android.app.NotificationManager;
23import android.app.PendingIntent;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.graphics.Bitmap;
32import android.graphics.Canvas;
33import android.graphics.drawable.Drawable;
34import android.net.BaseNetworkStateTracker;
35import android.net.ConnectivityManager;
36import android.net.INetworkManagementEventObserver;
37import android.net.LocalSocket;
38import android.net.LocalSocketAddress;
39import android.net.NetworkInfo;
40import android.net.NetworkInfo.DetailedState;
41import android.os.Binder;
42import android.os.FileUtils;
43import android.os.IBinder;
44import android.os.INetworkManagementService;
45import android.os.Parcel;
46import android.os.ParcelFileDescriptor;
47import android.os.Process;
48import android.os.RemoteException;
49import android.os.SystemClock;
50import android.os.SystemService;
51import android.util.Log;
52
53import com.android.internal.R;
54import com.android.internal.net.LegacyVpnInfo;
55import com.android.internal.net.VpnConfig;
56import com.android.internal.util.Preconditions;
57import com.android.server.ConnectivityService.VpnCallback;
58import com.android.server.net.BaseNetworkObserver;
59
60import java.io.File;
61import java.io.InputStream;
62import java.io.OutputStream;
63import java.nio.charset.Charsets;
64import java.util.Arrays;
65
66import libcore.io.IoUtils;
67
68/**
69 * @hide
70 */
71public class Vpn extends BaseNetworkStateTracker {
72    private static final String TAG = "Vpn";
73    private static final boolean LOGD = true;
74
75    // TODO: create separate trackers for each unique VPN to support
76    // automated reconnection
77
78    private final VpnCallback mCallback;
79
80    private String mPackage = VpnConfig.LEGACY_VPN;
81    private String mInterface;
82    private Connection mConnection;
83    private LegacyVpnRunner mLegacyVpnRunner;
84    private PendingIntent mStatusIntent;
85
86    public Vpn(Context context, VpnCallback callback, INetworkManagementService netService) {
87        // TODO: create dedicated TYPE_VPN network type
88        super(ConnectivityManager.TYPE_DUMMY);
89        mContext = context;
90        mCallback = callback;
91
92        try {
93            netService.registerObserver(mObserver);
94        } catch (RemoteException e) {
95            Log.wtf(TAG, "Problem registering observer", e);
96        }
97    }
98
99    @Override
100    protected void startMonitoringInternal() {
101        // Ignored; events are sent through callbacks for now
102    }
103
104    @Override
105    public boolean teardown() {
106        // TODO: finish migration to unique tracker for each VPN
107        throw new UnsupportedOperationException();
108    }
109
110    @Override
111    public boolean reconnect() {
112        // TODO: finish migration to unique tracker for each VPN
113        throw new UnsupportedOperationException();
114    }
115
116    @Override
117    public String getTcpBufferSizesPropName() {
118        return PROP_TCP_BUFFER_UNKNOWN;
119    }
120
121    /**
122     * Update current state, dispaching event to listeners.
123     */
124    private void updateState(DetailedState detailedState, String reason) {
125        if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
126        mNetworkInfo.setDetailedState(detailedState, reason, null);
127        mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
128    }
129
130    /**
131     * Prepare for a VPN application. This method is designed to solve
132     * race conditions. It first compares the current prepared package
133     * with {@code oldPackage}. If they are the same, the prepared
134     * package is revoked and replaced with {@code newPackage}. If
135     * {@code oldPackage} is {@code null}, the comparison is omitted.
136     * If {@code newPackage} is the same package or {@code null}, the
137     * revocation is omitted. This method returns {@code true} if the
138     * operation is succeeded.
139     *
140     * Legacy VPN is handled specially since it is not a real package.
141     * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
142     * it can be revoked by itself.
143     *
144     * @param oldPackage The package name of the old VPN application.
145     * @param newPackage The package name of the new VPN application.
146     * @return true if the operation is succeeded.
147     */
148    public synchronized boolean prepare(String oldPackage, String newPackage) {
149        // Return false if the package does not match.
150        if (oldPackage != null && !oldPackage.equals(mPackage)) {
151            return false;
152        }
153
154        // Return true if we do not need to revoke.
155        if (newPackage == null ||
156                (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
157            return true;
158        }
159
160        // Check if the caller is authorized.
161        enforceControlPermission();
162
163        // Reset the interface and hide the notification.
164        if (mInterface != null) {
165            jniReset(mInterface);
166            final long token = Binder.clearCallingIdentity();
167            try {
168                mCallback.restore();
169                hideNotification();
170            } finally {
171                Binder.restoreCallingIdentity(token);
172            }
173            mInterface = null;
174        }
175
176        // Revoke the connection or stop LegacyVpnRunner.
177        if (mConnection != null) {
178            try {
179                mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
180                        Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
181            } catch (Exception e) {
182                // ignore
183            }
184            mContext.unbindService(mConnection);
185            mConnection = null;
186        } else if (mLegacyVpnRunner != null) {
187            mLegacyVpnRunner.exit();
188            mLegacyVpnRunner = null;
189        }
190
191        Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
192        mPackage = newPackage;
193        updateState(DetailedState.IDLE, "prepare");
194        return true;
195    }
196
197    /**
198     * Protect a socket from routing changes by binding it to the given
199     * interface. The socket is NOT closed by this method.
200     *
201     * @param socket The socket to be bound.
202     * @param interfaze The name of the interface.
203     */
204    public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
205        PackageManager pm = mContext.getPackageManager();
206        ApplicationInfo app = pm.getApplicationInfo(mPackage, 0);
207        if (Binder.getCallingUid() != app.uid) {
208            throw new SecurityException("Unauthorized Caller");
209        }
210        jniProtect(socket.getFd(), interfaze);
211    }
212
213    /**
214     * Establish a VPN network and return the file descriptor of the VPN
215     * interface. This methods returns {@code null} if the application is
216     * revoked or not prepared.
217     *
218     * @param config The parameters to configure the network.
219     * @return The file descriptor of the VPN interface.
220     */
221    public synchronized ParcelFileDescriptor establish(VpnConfig config) {
222        // Check if the caller is already prepared.
223        PackageManager pm = mContext.getPackageManager();
224        ApplicationInfo app = null;
225        try {
226            app = pm.getApplicationInfo(mPackage, 0);
227        } catch (Exception e) {
228            return null;
229        }
230        if (Binder.getCallingUid() != app.uid) {
231            return null;
232        }
233
234        // Check if the service is properly declared.
235        Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
236        intent.setClassName(mPackage, config.user);
237        ResolveInfo info = pm.resolveService(intent, 0);
238        if (info == null) {
239            throw new SecurityException("Cannot find " + config.user);
240        }
241        if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
242            throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
243        }
244
245        // Load the label.
246        String label = app.loadLabel(pm).toString();
247
248        // Load the icon and convert it into a bitmap.
249        Drawable icon = app.loadIcon(pm);
250        Bitmap bitmap = null;
251        if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
252            int width = mContext.getResources().getDimensionPixelSize(
253                    android.R.dimen.notification_large_icon_width);
254            int height = mContext.getResources().getDimensionPixelSize(
255                    android.R.dimen.notification_large_icon_height);
256            icon.setBounds(0, 0, width, height);
257            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
258            Canvas c = new Canvas(bitmap);
259            icon.draw(c);
260            c.setBitmap(null);
261        }
262
263        // Configure the interface. Abort if any of these steps fails.
264        ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
265        try {
266            updateState(DetailedState.CONNECTING, "establish");
267            String interfaze = jniGetName(tun.getFd());
268            if (jniSetAddresses(interfaze, config.addresses) < 1) {
269                throw new IllegalArgumentException("At least one address must be specified");
270            }
271            if (config.routes != null) {
272                jniSetRoutes(interfaze, config.routes);
273            }
274            Connection connection = new Connection();
275            if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
276                throw new IllegalStateException("Cannot bind " + config.user);
277            }
278            if (mConnection != null) {
279                mContext.unbindService(mConnection);
280            }
281            if (mInterface != null && !mInterface.equals(interfaze)) {
282                jniReset(mInterface);
283            }
284            mConnection = connection;
285            mInterface = interfaze;
286        } catch (RuntimeException e) {
287            updateState(DetailedState.FAILED, "establish");
288            IoUtils.closeQuietly(tun);
289            throw e;
290        }
291        Log.i(TAG, "Established by " + config.user + " on " + mInterface);
292
293        // Fill more values.
294        config.user = mPackage;
295        config.interfaze = mInterface;
296
297        // Override DNS servers and show the notification.
298        final long token = Binder.clearCallingIdentity();
299        try {
300            mCallback.override(config.dnsServers, config.searchDomains);
301            showNotification(config, label, bitmap);
302        } finally {
303            Binder.restoreCallingIdentity(token);
304        }
305        // TODO: ensure that contract class eventually marks as connected
306        updateState(DetailedState.AUTHENTICATING, "establish");
307        return tun;
308    }
309
310    @Deprecated
311    public synchronized void interfaceStatusChanged(String iface, boolean up) {
312        try {
313            mObserver.interfaceStatusChanged(iface, up);
314        } catch (RemoteException e) {
315            // ignored; target is local
316        }
317    }
318
319    private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
320        @Override
321        public void interfaceStatusChanged(String interfaze, boolean up) {
322            synchronized (Vpn.this) {
323                if (!up && mLegacyVpnRunner != null) {
324                    mLegacyVpnRunner.check(interfaze);
325                }
326            }
327        }
328
329        @Override
330        public void interfaceRemoved(String interfaze) {
331            synchronized (Vpn.this) {
332                if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
333                    final long token = Binder.clearCallingIdentity();
334                    try {
335                        mCallback.restore();
336                        hideNotification();
337                    } finally {
338                        Binder.restoreCallingIdentity(token);
339                    }
340                    mInterface = null;
341                    if (mConnection != null) {
342                        mContext.unbindService(mConnection);
343                        mConnection = null;
344                        updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
345                    } else if (mLegacyVpnRunner != null) {
346                        mLegacyVpnRunner.exit();
347                        mLegacyVpnRunner = null;
348                    }
349                }
350            }
351        }
352    };
353
354    private void enforceControlPermission() {
355        // System user is allowed to control VPN.
356        if (Binder.getCallingUid() == Process.SYSTEM_UID) {
357            return;
358        }
359
360        try {
361            // System dialogs are also allowed to control VPN.
362            PackageManager pm = mContext.getPackageManager();
363            ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
364            if (Binder.getCallingUid() == app.uid) {
365                return;
366            }
367        } catch (Exception e) {
368            // ignore
369        }
370
371        throw new SecurityException("Unauthorized Caller");
372    }
373
374    private class Connection implements ServiceConnection {
375        private IBinder mService;
376
377        @Override
378        public void onServiceConnected(ComponentName name, IBinder service) {
379            mService = service;
380        }
381
382        @Override
383        public void onServiceDisconnected(ComponentName name) {
384            mService = null;
385        }
386    }
387
388    private void showNotification(VpnConfig config, String label, Bitmap icon) {
389        mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config);
390
391        NotificationManager nm = (NotificationManager)
392                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
393
394        if (nm != null) {
395            String title = (label == null) ? mContext.getString(R.string.vpn_title) :
396                    mContext.getString(R.string.vpn_title_long, label);
397            String text = (config.session == null) ? mContext.getString(R.string.vpn_text) :
398                    mContext.getString(R.string.vpn_text_long, config.session);
399            config.startTime = SystemClock.elapsedRealtime();
400
401            Notification notification = new Notification.Builder(mContext)
402                    .setSmallIcon(R.drawable.vpn_connected)
403                    .setLargeIcon(icon)
404                    .setContentTitle(title)
405                    .setContentText(text)
406                    .setContentIntent(mStatusIntent)
407                    .setDefaults(0)
408                    .setOngoing(true)
409                    .build();
410            nm.notify(R.drawable.vpn_connected, notification);
411        }
412    }
413
414    private void hideNotification() {
415        mStatusIntent = null;
416
417        NotificationManager nm = (NotificationManager)
418                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
419
420        if (nm != null) {
421            nm.cancel(R.drawable.vpn_connected);
422        }
423    }
424
425    private native int jniCreate(int mtu);
426    private native String jniGetName(int tun);
427    private native int jniSetAddresses(String interfaze, String addresses);
428    private native int jniSetRoutes(String interfaze, String routes);
429    private native void jniReset(String interfaze);
430    private native int jniCheck(String interfaze);
431    private native void jniProtect(int socket, String interfaze);
432
433    /**
434     * Start legacy VPN. This method stops the daemons and restart them
435     * if arguments are not null. Heavy things are offloaded to another
436     * thread, so callers will not be blocked for a long time.
437     *
438     * @param config The parameters to configure the network.
439     * @param racoon The arguments to be passed to racoon.
440     * @param mtpd The arguments to be passed to mtpd.
441     */
442    public synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
443        stopLegacyVpn();
444
445        // TODO: move legacy definition to settings
446        config.legacy = true;
447
448        // Prepare for the new request. This also checks the caller.
449        prepare(null, VpnConfig.LEGACY_VPN);
450        updateState(DetailedState.CONNECTING, "startLegacyVpn");
451
452        // Start a new LegacyVpnRunner and we are done!
453        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
454        mLegacyVpnRunner.start();
455    }
456
457    public synchronized void stopLegacyVpn() {
458        if (mLegacyVpnRunner != null) {
459            mLegacyVpnRunner.exit();
460            mLegacyVpnRunner = null;
461
462            synchronized (LegacyVpnRunner.TAG) {
463                // wait for old thread to completely finish before spinning up
464                // new instance, otherwise state updates can be out of order.
465            }
466        }
467    }
468
469    /**
470     * Return the information of the current ongoing legacy VPN.
471     */
472    public synchronized LegacyVpnInfo getLegacyVpnInfo() {
473        // Check if the caller is authorized.
474        enforceControlPermission();
475        if (mLegacyVpnRunner == null) return null;
476
477        final LegacyVpnInfo info = new LegacyVpnInfo();
478        info.key = mLegacyVpnRunner.mConfig.user;
479        info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
480        if (mNetworkInfo.isConnected()) {
481            info.intent = mStatusIntent;
482        }
483        return info;
484    }
485
486    /**
487     * Bringing up a VPN connection takes time, and that is all this thread
488     * does. Here we have plenty of time. The only thing we need to take
489     * care of is responding to interruptions as soon as possible. Otherwise
490     * requests will be piled up. This can be done in a Handler as a state
491     * machine, but it is much easier to read in the current form.
492     */
493    private class LegacyVpnRunner extends Thread {
494        private static final String TAG = "LegacyVpnRunner";
495
496        private final VpnConfig mConfig;
497        private final String[] mDaemons;
498        private final String[][] mArguments;
499        private final LocalSocket[] mSockets;
500
501        private long mTimer = -1;
502
503        public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
504            super(TAG);
505            mConfig = config;
506            mDaemons = new String[] {"racoon", "mtpd"};
507            // TODO: clear arguments from memory once launched
508            mArguments = new String[][] {racoon, mtpd};
509            mSockets = new LocalSocket[mDaemons.length];
510        }
511
512        public void check(String interfaze) {
513            if (interfaze.equals(mConfig.interfaze)) {
514                Log.i(TAG, "Legacy VPN is going down with " + interfaze);
515                exit();
516            }
517        }
518
519        public void exit() {
520            // We assume that everything is reset after stopping the daemons.
521            interrupt();
522            for (LocalSocket socket : mSockets) {
523                IoUtils.closeQuietly(socket);
524            }
525            updateState(DetailedState.DISCONNECTED, "exit");
526        }
527
528        @Override
529        public void run() {
530            // Wait for the previous thread since it has been interrupted.
531            Log.v(TAG, "Waiting");
532            synchronized (TAG) {
533                Log.v(TAG, "Executing");
534                execute();
535                monitorDaemons();
536            }
537        }
538
539        private void checkpoint(boolean yield) throws InterruptedException {
540            long now = SystemClock.elapsedRealtime();
541            if (mTimer == -1) {
542                mTimer = now;
543                Thread.sleep(1);
544            } else if (now - mTimer <= 60000) {
545                Thread.sleep(yield ? 200 : 1);
546            } else {
547                updateState(DetailedState.FAILED, "checkpoint");
548                throw new IllegalStateException("Time is up");
549            }
550        }
551
552        private void execute() {
553            // Catch all exceptions so we can clean up few things.
554            boolean initFinished = false;
555            try {
556                // Initialize the timer.
557                checkpoint(false);
558
559                // Wait for the daemons to stop.
560                for (String daemon : mDaemons) {
561                    while (!SystemService.isStopped(daemon)) {
562                        checkpoint(true);
563                    }
564                }
565
566                // Clear the previous state.
567                File state = new File("/data/misc/vpn/state");
568                state.delete();
569                if (state.exists()) {
570                    throw new IllegalStateException("Cannot delete the state");
571                }
572                new File("/data/misc/vpn/abort").delete();
573                initFinished = true;
574
575                // Check if we need to restart any of the daemons.
576                boolean restart = false;
577                for (String[] arguments : mArguments) {
578                    restart = restart || (arguments != null);
579                }
580                if (!restart) {
581                    updateState(DetailedState.DISCONNECTED, "execute");
582                    return;
583                }
584                updateState(DetailedState.CONNECTING, "execute");
585
586                // Start the daemon with arguments.
587                for (int i = 0; i < mDaemons.length; ++i) {
588                    String[] arguments = mArguments[i];
589                    if (arguments == null) {
590                        continue;
591                    }
592
593                    // Start the daemon.
594                    String daemon = mDaemons[i];
595                    SystemService.start(daemon);
596
597                    // Wait for the daemon to start.
598                    while (!SystemService.isRunning(daemon)) {
599                        checkpoint(true);
600                    }
601
602                    // Create the control socket.
603                    mSockets[i] = new LocalSocket();
604                    LocalSocketAddress address = new LocalSocketAddress(
605                            daemon, LocalSocketAddress.Namespace.RESERVED);
606
607                    // Wait for the socket to connect.
608                    while (true) {
609                        try {
610                            mSockets[i].connect(address);
611                            break;
612                        } catch (Exception e) {
613                            // ignore
614                        }
615                        checkpoint(true);
616                    }
617                    mSockets[i].setSoTimeout(500);
618
619                    // Send over the arguments.
620                    OutputStream out = mSockets[i].getOutputStream();
621                    for (String argument : arguments) {
622                        byte[] bytes = argument.getBytes(Charsets.UTF_8);
623                        if (bytes.length >= 0xFFFF) {
624                            throw new IllegalArgumentException("Argument is too large");
625                        }
626                        out.write(bytes.length >> 8);
627                        out.write(bytes.length);
628                        out.write(bytes);
629                        checkpoint(false);
630                    }
631                    out.write(0xFF);
632                    out.write(0xFF);
633                    out.flush();
634
635                    // Wait for End-of-File.
636                    InputStream in = mSockets[i].getInputStream();
637                    while (true) {
638                        try {
639                            if (in.read() == -1) {
640                                break;
641                            }
642                        } catch (Exception e) {
643                            // ignore
644                        }
645                        checkpoint(true);
646                    }
647                }
648
649                // Wait for the daemons to create the new state.
650                while (!state.exists()) {
651                    // Check if a running daemon is dead.
652                    for (int i = 0; i < mDaemons.length; ++i) {
653                        String daemon = mDaemons[i];
654                        if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
655                            throw new IllegalStateException(daemon + " is dead");
656                        }
657                    }
658                    checkpoint(true);
659                }
660
661                // Now we are connected. Read and parse the new state.
662                String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
663                if (parameters.length != 6) {
664                    throw new IllegalStateException("Cannot parse the state");
665                }
666
667                // Set the interface and the addresses in the config.
668                mConfig.interfaze = parameters[0].trim();
669                mConfig.addresses = parameters[1].trim();
670
671                // Set the routes if they are not set in the config.
672                if (mConfig.routes == null || mConfig.routes.isEmpty()) {
673                    mConfig.routes = parameters[2].trim();
674                }
675
676                // Set the DNS servers if they are not set in the config.
677                if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
678                    String dnsServers = parameters[3].trim();
679                    if (!dnsServers.isEmpty()) {
680                        mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
681                    }
682                }
683
684                // Set the search domains if they are not set in the config.
685                if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
686                    String searchDomains = parameters[4].trim();
687                    if (!searchDomains.isEmpty()) {
688                        mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
689                    }
690                }
691
692                // Set the routes.
693                jniSetRoutes(mConfig.interfaze, mConfig.routes);
694
695                // Here is the last step and it must be done synchronously.
696                synchronized (Vpn.this) {
697                    // Check if the thread is interrupted while we are waiting.
698                    checkpoint(false);
699
700                    // Check if the interface is gone while we are waiting.
701                    if (jniCheck(mConfig.interfaze) == 0) {
702                        throw new IllegalStateException(mConfig.interfaze + " is gone");
703                    }
704
705                    // Now INetworkManagementEventObserver is watching our back.
706                    mInterface = mConfig.interfaze;
707                    mCallback.override(mConfig.dnsServers, mConfig.searchDomains);
708                    showNotification(mConfig, null, null);
709
710                    Log.i(TAG, "Connected!");
711                    updateState(DetailedState.CONNECTED, "execute");
712                }
713            } catch (Exception e) {
714                Log.i(TAG, "Aborting", e);
715                exit();
716            } finally {
717                // Kill the daemons if they fail to stop.
718                if (!initFinished) {
719                    for (String daemon : mDaemons) {
720                        SystemService.stop(daemon);
721                    }
722                }
723
724                // Do not leave an unstable state.
725                if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
726                    updateState(DetailedState.FAILED, "execute");
727                }
728            }
729        }
730
731        /**
732         * Monitor the daemons we started, moving to disconnected state if the
733         * underlying services fail.
734         */
735        private void monitorDaemons() {
736            if (!mNetworkInfo.isConnected()) {
737                return;
738            }
739
740            try {
741                while (true) {
742                    Thread.sleep(2000);
743                    for (int i = 0; i < mDaemons.length; i++) {
744                        if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
745                            return;
746                        }
747                    }
748                }
749            } catch (InterruptedException e) {
750                Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
751            } finally {
752                for (String daemon : mDaemons) {
753                    SystemService.stop(daemon);
754                }
755
756                updateState(DetailedState.DISCONNECTED, "babysit");
757            }
758        }
759    }
760}
761