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