diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java index da27db55c49a8..db58012d47abd 100644 --- a/core/java/android/os/SystemService.java +++ b/core/java/android/os/SystemService.java @@ -16,15 +16,53 @@ package android.os; -/** @hide */ -public class SystemService -{ - /** Request that the init daemon start a named service. */ +import com.google.android.collect.Maps; + +import java.util.HashMap; +import java.util.concurrent.TimeoutException; + +/** + * Controls and utilities for low-level {@code init} services. + * + * @hide + */ +public class SystemService { + + private static HashMap sStates = Maps.newHashMap(); + + /** + * State of a known {@code init} service. + */ + public enum State { + RUNNING("running"), + STOPPING("stopping"), + STOPPED("stopped"), + RESTARTING("restarting"); + + State(String state) { + sStates.put(state, this); + } + } + + private static Object sPropertyLock = new Object(); + + static { + SystemProperties.addChangeCallback(new Runnable() { + @Override + public void run() { + synchronized (sPropertyLock) { + sPropertyLock.notifyAll(); + } + } + }); + } + + /** Request that the init daemon start a named service. */ public static void start(String name) { SystemProperties.set("ctl.start", name); } - - /** Request that the init daemon stop a named service. */ + + /** Request that the init daemon stop a named service. */ public static void stop(String name) { SystemProperties.set("ctl.stop", name); } @@ -33,4 +71,77 @@ public class SystemService public static void restart(String name) { SystemProperties.set("ctl.restart", name); } + + /** + * Return current state of given service. + */ + public static State getState(String service) { + final String rawState = SystemProperties.get("init.svc." + service); + final State state = sStates.get(rawState); + if (state != null) { + return state; + } else { + throw new IllegalStateException("Service " + service + " in unknown state " + rawState); + } + } + + /** + * Check if given service is {@link State#STOPPED}. + */ + public static boolean isStopped(String service) { + return State.STOPPED.equals(getState(service)); + } + + /** + * Check if given service is {@link State#RUNNING}. + */ + public static boolean isRunning(String service) { + return State.RUNNING.equals(getState(service)); + } + + /** + * Wait until given service has entered specific state. + */ + public static void waitForState(String service, State state, long timeoutMillis) + throws TimeoutException { + final long endMillis = SystemClock.elapsedRealtime() + timeoutMillis; + while (true) { + synchronized (sPropertyLock) { + final State currentState = getState(service); + if (state.equals(currentState)) { + return; + } + + if (SystemClock.elapsedRealtime() >= endMillis) { + throw new TimeoutException("Service " + service + " currently " + currentState + + "; waited " + timeoutMillis + "ms for " + state); + } + + try { + sPropertyLock.wait(timeoutMillis); + } catch (InterruptedException e) { + } + } + } + } + + /** + * Wait until any of given services enters {@link State#STOPPED}. + */ + public static void waitForAnyStopped(String... services) { + while (true) { + synchronized (sPropertyLock) { + for (String service : services) { + if (State.STOPPED.equals(getState(service))) { + return; + } + } + + try { + sPropertyLock.wait(); + } catch (InterruptedException e) { + } + } + } + } } diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index d788eba9de444..1232846674e56 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -25,7 +25,6 @@ import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -39,7 +38,7 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.SystemClock; -import android.os.SystemProperties; +import android.os.SystemService; import android.util.Log; import com.android.internal.R; @@ -485,8 +484,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Wait for the daemons to stop. for (String daemon : mDaemons) { - String key = "init.svc." + daemon; - while (!"stopped".equals(SystemProperties.get(key, "stopped"))) { + while (!SystemService.isStopped(daemon)) { checkpoint(true); } } @@ -519,11 +517,10 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Start the daemon. String daemon = mDaemons[i]; - SystemProperties.set("ctl.start", daemon); + SystemService.start(daemon); // Wait for the daemon to start. - String key = "init.svc." + daemon; - while (!"running".equals(SystemProperties.get(key))) { + while (!SystemService.isRunning(daemon)) { checkpoint(true); } @@ -579,8 +576,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Check if a running daemon is dead. for (int i = 0; i < mDaemons.length; ++i) { String daemon = mDaemons[i]; - if (mArguments[i] != null && !"running".equals( - SystemProperties.get("init.svc." + daemon))) { + if (mArguments[i] != null && !SystemService.isRunning(daemon)) { throw new IllegalStateException(daemon + " is dead"); } } @@ -647,7 +643,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { // Kill the daemons if they fail to stop. if (mInfo.state == LegacyVpnInfo.STATE_INITIALIZING) { for (String daemon : mDaemons) { - SystemProperties.set("ctl.stop", daemon); + SystemService.stop(daemon); } }