summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Robin Lee <rgl@google.com> 2015-04-16 17:01:49 +0100
committer Robin Lee <rgl@google.com> 2015-04-21 15:28:20 +0100
commit9cb1d5f6418da8cecdee58114c6e97b80c1b153f (patch)
tree5a9d1cc1bc31a0a960d161098c1b3400d7d5f0b6
parentca02bc7cebabcf347bde98a45cce41c7d1ab26ee (diff)
SecurityController: track VPN for all users
Fixes inconsistencies when switching users. Bug: 17763561 Bug: 17506956 Bug: 18419023 Change-Id: I65988e6c45afd00a402d1e266922d5648fd12743
-rw-r--r--packages/SystemUI/res/values/strings.xml34
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooter.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java116
4 files changed, 91 insertions, 159 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 67a0bc61c4a8..f12fd0c8406a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -909,37 +909,25 @@
<string name="disconnect_vpn">Disconnect VPN</string>
<!-- Monitoring dialog device owner body text [CHAR LIMIT=400] -->
- <string name="monitoring_description_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
-
- <!-- Monitoring dialog non-legacy VPN text [CHAR LIMIT=400] -->
- <string name="monitoring_description_vpn">You gave \"<xliff:g id="application">%1$s</xliff:g>\" permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and secure websites.</string>
-
- <!-- Monitoring dialog legacy VPN text [CHAR LIMIT=400] -->
- <string name="monitoring_description_legacy_vpn">You\'re connected to a VPN (\"<xliff:g id="application">%1$s</xliff:g>\").\n\nYour VPN service provider can monitor your device and network activity including emails, apps, and secure websites.</string>
-
- <!-- Monitoring dialog non-legacy VPN with device owner text [CHAR LIMIT=400] -->
- <string name="monitoring_description_vpn_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%2$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string>
-
- <!-- Monitoring dialog legacy VPN with device owner text [CHAR LIMIT=400] -->
- <string name="monitoring_description_legacy_vpn_device_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you\'re connected to a VPN (\"<xliff:g id="application">%2$s</xliff:g>\"). Your VPN service provider can monitor network activity too.</string>
+ <string name="monitoring_description_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information. For more information, contact your administrator.</string>
<!-- Monitoring dialog profile owner body text [CHAR LIMIT=400] -->
- <string name="monitoring_description_profile_owned">This profile is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
+ <string name="monitoring_description_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
<!-- Monitoring dialog device and profile owner body text [CHAR LIMIT=400] -->
- <string name="monitoring_description_device_and_profile_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\nYour profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
+ <string name="monitoring_description_device_and_profile_owned">Your device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>.\nYour work profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>.\n\nYour administrator can monitor your device and network activity, including emails, apps and secure websites.\n\nFor more information, contact your administrator.</string>
- <!-- Monitoring dialog non-legacy VPN with profile owner text [CHAR LIMIT=400] -->
- <string name="monitoring_description_vpn_profile_owned">This profile is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%2$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string>
+ <!-- Monitoring dialog VPN text [CHAR LIMIT=400] -->
+ <string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps and secure websites.</string>
- <!-- Monitoring dialog legacy VPN with profile owner text [CHAR LIMIT=400] -->
- <string name="monitoring_description_legacy_vpn_profile_owned">This profile is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you\'re connected to a VPN (\"<xliff:g id="application">%2$s</xliff:g>\"). Your VPN service provider can monitor network activity too.</string>
+ <!-- Monitoring dialog VPN with device owner text [CHAR LIMIT=400] -->
+ <string name="monitoring_description_vpn_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to a VPN, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string>
- <!-- Monitoring dialog non-legacy VPN with device and profile owner text [CHAR LIMIT=400] -->
- <string name="monitoring_description_vpn_device_and_profile_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\nYour profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you gave \"<xliff:g id="application">%3$s</xliff:g>\" permission to set up a VPN connection. This app can monitor network activity too.</string>
+ <!-- Monitoring dialog VPN with profile owner text [CHAR LIMIT=400] -->
+ <string name="monitoring_description_vpn_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your network activity.</string>
- <!-- Monitoring dialog legacy VPN with device and profile owner text [CHAR LIMIT=400] -->
- <string name="monitoring_description_legacy_vpn_device_and_profile_owned">This device is managed by:\n<xliff:g id="organization">%1$s</xliff:g>\nYour profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites. For more information, contact your administrator.\n\nAlso, you\'re connected to a VPN (\"<xliff:g id="application">%3$s</xliff:g>\"). Your VPN service provider can monitor network activity too.</string>
+ <!-- Monitoring dialog VPN with device and profile owner text [CHAR LIMIT=400] -->
+ <string name="monitoring_description_vpn_device_and_profile_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\nYour work profile is managed by:\n<xliff:g id="organization">%2$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and secure websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your personal network activity</string>
<!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] -->
<string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index 0ab644ab2b8a..d8e3984a2bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -18,9 +18,11 @@ package com.android.systemui.qs;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -38,6 +40,8 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
protected static final String TAG = "QSFooter";
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
+
private final View mRootView;
private final TextView mFooterText;
private final ImageView mFooterIcon;
@@ -128,50 +132,42 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_NEGATIVE) {
- mSecurityController.disconnectFromVpn();
+ final Intent settingsIntent = new Intent(ACTION_VPN_SETTINGS);
+ mContext.startActivityAsUser(settingsIntent, UserHandle.CURRENT);
}
}
private void createDialog() {
+ boolean hasDeviceOwner = mSecurityController.hasDeviceOwner();
+ boolean hasProfile = mSecurityController.hasProfileOwner();
+ boolean hasVpn = mSecurityController.isVpnEnabled();
+
mDialog = new SystemUIDialog(mContext);
- mDialog.setTitle(getTitle());
- mDialog.setMessage(getMessage());
+ mDialog.setTitle(getTitle(hasDeviceOwner, hasProfile));
+ mDialog.setMessage(getMessage(hasDeviceOwner, hasProfile, hasVpn));
mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
- if (mSecurityController.isVpnEnabled()) {
+ if (hasVpn) {
mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
}
mDialog.show();
}
private String getNegativeButton() {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(R.string.disconnect_vpn);
- } else {
- return mContext.getString(R.string.disable_vpn);
- }
+ return mContext.getString(R.string.status_bar_settings_settings_button);
}
private String getPositiveButton() {
return mContext.getString(R.string.quick_settings_done);
}
- private String getMessage() {
- if (mSecurityController.hasDeviceOwner()) {
- if (mSecurityController.hasProfileOwner()) {
- if (mSecurityController.isVpnEnabled()) {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(
- R.string.monitoring_description_legacy_vpn_device_and_profile_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getLegacyVpnName());
- } else {
- return mContext.getString(
- R.string.monitoring_description_vpn_device_and_profile_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getVpnApp());
- }
+ private String getMessage(boolean hasDeviceOwner, boolean hasProfile, boolean hasVpn) {
+ if (hasDeviceOwner) {
+ if (hasProfile) {
+ if (hasVpn) {
+ return mContext.getString(
+ R.string.monitoring_description_vpn_device_and_profile_owned,
+ mSecurityController.getDeviceOwnerName(),
+ mSecurityController.getProfileOwnerName());
} else {
return mContext.getString(
R.string.monitoring_description_device_and_profile_owned,
@@ -179,57 +175,33 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene
mSecurityController.getProfileOwnerName());
}
} else {
- if (mSecurityController.isVpnEnabled()) {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(
- R.string.monitoring_description_legacy_vpn_device_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getLegacyVpnName());
- } else {
- return mContext.getString(R.string.monitoring_description_vpn_device_owned,
- mSecurityController.getDeviceOwnerName(),
- mSecurityController.getVpnApp());
- }
+ if (hasVpn) {
+ return mContext.getString(R.string.monitoring_description_vpn_device_owned,
+ mSecurityController.getDeviceOwnerName());
} else {
return mContext.getString(R.string.monitoring_description_device_owned,
mSecurityController.getDeviceOwnerName());
}
}
- } else if (mSecurityController.hasProfileOwner()) {
- if (mSecurityController.isVpnEnabled()) {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(
- R.string.monitoring_description_legacy_vpn_profile_owned,
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getLegacyVpnName());
- } else {
- return mContext.getString(
- R.string.monitoring_description_vpn_profile_owned,
- mSecurityController.getProfileOwnerName(),
- mSecurityController.getVpnApp());
- }
+ } else if (hasProfile) {
+ if (hasVpn) {
+ return mContext.getString(
+ R.string.monitoring_description_vpn_profile_owned,
+ mSecurityController.getProfileOwnerName());
} else {
return mContext.getString(
R.string.monitoring_description_profile_owned,
mSecurityController.getProfileOwnerName());
}
} else {
- if (mSecurityController.isLegacyVpn()) {
- return mContext.getString(R.string.monitoring_description_legacy_vpn,
- mSecurityController.getLegacyVpnName());
-
- } else {
- return mContext.getString(R.string.monitoring_description_vpn,
- mSecurityController.getVpnApp());
- }
+ return mContext.getString(R.string.monitoring_description_vpn);
}
}
- private int getTitle() {
- if (mSecurityController.hasDeviceOwner()) {
+ private int getTitle(boolean hasDeviceOwner, boolean hasProfile) {
+ if (hasDeviceOwner) {
return R.string.monitoring_title_device_owned;
- }
- if (mSecurityController.hasProfileOwner()) {
+ } else if (hasProfile) {
return R.string.monitoring_title_profile_owned;
}
return R.string.monitoring_title;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 6148febb3885..e1e022d8ac77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -22,10 +22,6 @@ public interface SecurityController {
String getDeviceOwnerName();
String getProfileOwnerName();
boolean isVpnEnabled();
- String getVpnApp();
- boolean isLegacyVpn();
- String getLegacyVpnName();
- void disconnectFromVpn();
void onUserSwitched(int newUserId);
void addCallback(SecurityControllerCallback callback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index f0dd943e93f3..4f47cc6943d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -19,6 +19,7 @@ import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IConnectivityManager;
@@ -27,10 +28,14 @@ import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -50,15 +55,13 @@ public class SecurityControllerImpl implements SecurityController {
private final Context mContext;
private final ConnectivityManager mConnectivityManager;
- private final IConnectivityManager mConnectivityService = IConnectivityManager.Stub.asInterface(
- ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ private final IConnectivityManager mConnectivityManagerService;
private final DevicePolicyManager mDevicePolicyManager;
+ private final UserManager mUserManager;
private final ArrayList<SecurityControllerCallback> mCallbacks
= new ArrayList<SecurityControllerCallback>();
- private VpnConfig mVpnConfig;
- private String mVpnName;
- private int mCurrentVpnNetworkId = NO_NETWORK;
+ private SparseArray<Boolean> mCurrentVpnUsers = new SparseArray<>();
private int mCurrentUserId;
public SecurityControllerImpl(Context context) {
@@ -67,6 +70,10 @@ public class SecurityControllerImpl implements SecurityController {
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mConnectivityManagerService = IConnectivityManager.Stub.asInterface(
+ ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ mUserManager = (UserManager)
+ context.getSystemService(Context.USER_SERVICE);
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
@@ -75,9 +82,7 @@ public class SecurityControllerImpl implements SecurityController {
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("SecurityController state:");
- pw.print(" mCurrentVpnNetworkId="); pw.println(mCurrentVpnNetworkId);
- pw.print(" mVpnConfig="); pw.println(mVpnConfig);
- pw.print(" mVpnName="); pw.println(mVpnName);
+ pw.print(" mCurrentVpnUsers=" + mCurrentVpnUsers);
}
@Override
@@ -86,56 +91,33 @@ public class SecurityControllerImpl implements SecurityController {
}
@Override
- public boolean hasProfileOwner() {
- return !TextUtils.isEmpty(mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId));
- }
-
- @Override
public String getDeviceOwnerName() {
return mDevicePolicyManager.getDeviceOwnerName();
}
@Override
- public String getProfileOwnerName() {
- return mDevicePolicyManager.getProfileOwnerNameAsUser(mCurrentUserId);
- }
-
-
- @Override
- public boolean isVpnEnabled() {
- return mCurrentVpnNetworkId != NO_NETWORK;
- }
-
- @Override
- public boolean isLegacyVpn() {
- return mVpnConfig.legacy;
- }
-
- @Override
- public String getVpnApp() {
- return mVpnName;
+ public boolean hasProfileOwner() {
+ boolean result = false;
+ for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) {
+ result |= (mDevicePolicyManager.getProfileOwnerAsUser(profile.id) != null);
+ }
+ return result;
}
@Override
- public String getLegacyVpnName() {
- return mVpnConfig.session;
+ public String getProfileOwnerName() {
+ for (UserInfo profile : mUserManager.getProfiles(mCurrentUserId)) {
+ String name = mDevicePolicyManager.getProfileOwnerNameAsUser(profile.id);
+ if (name != null) {
+ return name;
+ }
+ }
+ return null;
}
@Override
- public void disconnectFromVpn() {
- try {
- if (isLegacyVpn()) {
- mConnectivityService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
- } else {
- // Prevent this app from initiating VPN connections in the future without user
- // intervention.
- mConnectivityService.setVpnPackageAuthorization(false);
-
- mConnectivityService.prepareVpn(mVpnConfig.user, VpnConfig.LEGACY_VPN);
- }
- } catch (Exception e) {
- Log.e(TAG, "Unable to disconnect from VPN", e);
- }
+ public boolean isVpnEnabled() {
+ return mCurrentVpnUsers.get(mCurrentUserId) != null;
}
@Override
@@ -158,14 +140,6 @@ public class SecurityControllerImpl implements SecurityController {
fireCallbacks();
}
- private void setCurrentNetid(int netId) {
- if (netId != mCurrentVpnNetworkId) {
- mCurrentVpnNetworkId = netId;
- updateState();
- fireCallbacks();
- }
- }
-
private void fireCallbacks() {
for (SecurityControllerCallback callback : mCallbacks) {
callback.onStateChanged();
@@ -173,27 +147,30 @@ public class SecurityControllerImpl implements SecurityController {
}
private void updateState() {
+ // Find all users with an active VPN
+ SparseArray<Boolean> vpnUsers = new SparseArray<>();
try {
- mVpnConfig = mConnectivityService.getVpnConfig();
+ for (VpnInfo vpn : mConnectivityManagerService.getAllVpnInfo()) {
+ UserInfo user = mUserManager.getUserInfo(UserHandle.getUserId(vpn.ownerUid));
+ int groupId = (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID ?
+ user.profileGroupId : user.id);
- if (mVpnConfig != null && !mVpnConfig.legacy) {
- mVpnName = VpnConfig.getVpnLabel(mContext, mVpnConfig.user).toString();
+ vpnUsers.put(groupId, Boolean.TRUE);
}
- } catch (RemoteException | NameNotFoundException e) {
- Log.w(TAG, "Unable to get current VPN", e);
+ } catch (RemoteException rme) {
+ // Roll back to previous state
+ Log.e(TAG, "Unable to list active VPNs", rme);
+ return;
}
+ mCurrentVpnUsers = vpnUsers;
}
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
- NetworkCapabilities networkCapabilities =
- mConnectivityManager.getNetworkCapabilities(network);
- if (DEBUG) Log.d(TAG, "onAvailable " + network.netId + " : " + networkCapabilities);
- if (networkCapabilities != null &&
- networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
- setCurrentNetid(network.netId);
- }
+ if (DEBUG) Log.d(TAG, "onAvailable " + network.netId);
+ updateState();
+ fireCallbacks();
};
// TODO Find another way to receive VPN lost. This may be delayed depending on
@@ -201,9 +178,8 @@ public class SecurityControllerImpl implements SecurityController {
@Override
public void onLost(Network network) {
if (DEBUG) Log.d(TAG, "onLost " + network.netId);
- if (mCurrentVpnNetworkId == network.netId) {
- setCurrentNetid(NO_NETWORK);
- }
+ updateState();
+ fireCallbacks();
};
};