diff options
33 files changed, 823 insertions, 194 deletions
diff --git a/api/current.txt b/api/current.txt index cbe4195e4c26..7c532d4b8ffa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -48707,6 +48707,7 @@ package android.webkit { method public abstract int getMixedContentMode(); method public abstract boolean getOffscreenPreRaster(); method public abstract deprecated android.webkit.WebSettings.PluginState getPluginState(); + method public abstract boolean getSafeBrowsingEnabled(); method public abstract java.lang.String getSansSerifFontFamily(); method public abstract deprecated boolean getSaveFormData(); method public abstract deprecated boolean getSavePassword(); @@ -48756,6 +48757,7 @@ package android.webkit { method public abstract void setOffscreenPreRaster(boolean); method public abstract deprecated void setPluginState(android.webkit.WebSettings.PluginState); method public abstract deprecated void setRenderPriority(android.webkit.WebSettings.RenderPriority); + method public abstract void setSafeBrowsingEnabled(boolean); method public abstract void setSansSerifFontFamily(java.lang.String); method public abstract deprecated void setSaveFormData(boolean); method public abstract deprecated void setSavePassword(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index 34f05da5b367..99a579321c79 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -52341,6 +52341,7 @@ package android.webkit { method public abstract boolean getOffscreenPreRaster(); method public abstract deprecated android.webkit.WebSettings.PluginState getPluginState(); method public abstract deprecated boolean getPluginsEnabled(); + method public abstract boolean getSafeBrowsingEnabled(); method public abstract java.lang.String getSansSerifFontFamily(); method public abstract deprecated boolean getSaveFormData(); method public abstract deprecated boolean getSavePassword(); @@ -52396,6 +52397,7 @@ package android.webkit { method public abstract deprecated void setPluginState(android.webkit.WebSettings.PluginState); method public abstract deprecated void setPluginsEnabled(boolean); method public abstract deprecated void setRenderPriority(android.webkit.WebSettings.RenderPriority); + method public abstract void setSafeBrowsingEnabled(boolean); method public abstract void setSansSerifFontFamily(java.lang.String); method public abstract deprecated void setSaveFormData(boolean); method public abstract deprecated void setSavePassword(boolean); diff --git a/api/test-current.txt b/api/test-current.txt index ac49a9bc720f..570855fb0043 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -49089,6 +49089,7 @@ package android.webkit { method public abstract int getMixedContentMode(); method public abstract boolean getOffscreenPreRaster(); method public abstract deprecated android.webkit.WebSettings.PluginState getPluginState(); + method public abstract boolean getSafeBrowsingEnabled(); method public abstract java.lang.String getSansSerifFontFamily(); method public abstract deprecated boolean getSaveFormData(); method public abstract deprecated boolean getSavePassword(); @@ -49138,6 +49139,7 @@ package android.webkit { method public abstract void setOffscreenPreRaster(boolean); method public abstract deprecated void setPluginState(android.webkit.WebSettings.PluginState); method public abstract deprecated void setRenderPriority(android.webkit.WebSettings.RenderPriority); + method public abstract void setSafeBrowsingEnabled(boolean); method public abstract void setSansSerifFontFamily(java.lang.String); method public abstract deprecated void setSaveFormData(boolean); method public abstract deprecated void setSavePassword(boolean); diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 2a2fdbd272d3..80522886c78f 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -364,11 +364,19 @@ public class AccountManager { "android.accounts.key_legacy_visible"; /** - * Key to set visibility for applications targeting API level below - * {@link android.os.Build.VERSION_CODES#O} with - * {@link android.Manifest.permission#GET_ACCOUNTS} permission, or applications with any - * targeting API level with the same signature as authenticator. See - * {@link #getAccountVisibility}. If the value was not set by authenticator + * Key to set visibility for applications which satisfy one of the following conditions: + * <ul> + * <li>Target API level below {@link android.os.Build.VERSION_CODES#O} and have + * deprecated {@link android.Manifest.permission#GET_ACCOUNTS} permission. + * </li> + * <li> Have {@link android.Manifest.permission#GET_ACCOUNTS_PRIVILEGED} permission. </li> + * <li> Have the same signature as authenticator. </li> + * <li> Have {@link android.Manifest.permission#READ_CONTACTS} permission and + * account type may be associated with contacts data - (verified by + * {@link android.Manifest.permission#WRITE_CONTACTS} permission check for the authenticator). + * </li> + * </ul> + * See {@link #getAccountVisibility}. If the value was not set by authenticator * {@link #VISIBILITY_USER_MANAGED_VISIBLE} is used. */ public static final String PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE = diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 42192356ed10..95549d68e480 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -18,6 +18,7 @@ package android.app; import android.metrics.LogMaker; import android.graphics.Rect; +import android.view.ViewRootImpl.ActivityConfigCallback; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillPopupWindow; @@ -6835,12 +6836,12 @@ public class Activity extends ContextThemeWrapper CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, - Window window) { + Window window, ActivityConfigCallback activityConfigCallback) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); - mWindow = new PhoneWindow(this, window); + mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 182982a638f1..3167ba7fe331 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2748,7 +2748,7 @@ public final class ActivityThread { activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, - r.referrer, r.voiceInteractor, window); + r.referrer, r.voiceInteractor, window, r.configCallback); if (customIntent != null) { activity.mIntent = customIntent; @@ -3783,12 +3783,6 @@ public final class ActivityThread { if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } - final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl(); - if (viewRoot != null) { - // TODO: Figure out the best place to set the callback. - // This looks like a place where decor view is already initialized. - viewRoot.setActivityConfigCallback(r.configCallback); - } } if (!r.onlyLocalRequest) { @@ -5156,13 +5150,8 @@ public final class ActivityThread { if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r); return; } - final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY; - if (movedToDifferentDisplay) { - if (r.activity.getDisplay().getDisplayId() == displayId) { - throw new IllegalArgumentException("Activity is already on the target display: " - + displayId); - } - } + final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY + && displayId != r.activity.getDisplay().getDisplayId(); // Perform updates. r.overrideConfig = data.overrideConfig; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index f9a3ea74a40f..d546f2771df8 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1146,10 +1146,11 @@ public class Instrumentation { IllegalAccessException { Activity activity = (Activity)clazz.newInstance(); ActivityThread aThread = null; - activity.attach(context, aThread, this, token, 0, application, intent, + activity.attach(context, aThread, this, token, 0 /* ident */, application, intent, info, title, parent, id, (Activity.NonConfigurationInstances)lastNonConfigurationInstance, - new Configuration(), null, null, null); + new Configuration(), null /* referrer */, null /* voiceInteractor */, + null /* window */, null /* activityConfigCallback */); return activity; } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 7b38863c9cc6..4d788e783f34 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -167,7 +167,7 @@ public final class CompanionDeviceManager { return Collections.emptyList(); } try { - return mService.getAssociations(mContext.getPackageName()); + return mService.getAssociations(mContext.getPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 495141d75a44..7798406c35e5 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -29,7 +29,7 @@ interface ICompanionDeviceManager { in IFindDeviceCallback callback, in String callingPackage); - List<String> getAssociations(String callingPackage); + List<String> getAssociations(String callingPackage, int userId); void disassociate(String deviceMacAddress, String callingPackage); //TODO add these diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 61920bd5cee4..3a6de9609b3c 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1385,6 +1385,36 @@ public abstract class WebSettings { */ public abstract boolean getOffscreenPreRaster(); + + /** + * Sets whether Safe Browsing is enabled. Safe browsing allows WebView to + * protect against malware and phishing attacks by verifying the links. + * + * Safe browsing is disabled by default. The recommended way to enable + * Safe browsing is using a manifest tag to change the default value to + * enabled for all WebViews. + * <p> + * <pre> + * <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" + * android:value="true" /> + * </pre> + * </p> + * + * This API overrides the manifest tag value for this WebView. + * + * @param enabled Whether Safe browsing is enabled. + */ + public abstract void setSafeBrowsingEnabled(boolean enabled); + + /** + * Gets whether Safe browsing is enabled. + * See {@link #setSafeBrowsingEnabled}. + * + * @return true if Safe browsing is enabled and false otherwise. + */ + public abstract boolean getSafeBrowsingEnabled(); + + /** * @hide */ diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 9760f8111ec3..7282492a4ccc 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1499,6 +1499,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // renderer about it. mBackdropFrameRenderer.onConfigurationChange(); } + mWindow.onViewRootImplSet(getViewRootImpl()); } @Override diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 6c9280a1ea25..7b966de86756 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -47,6 +47,7 @@ import android.view.ViewGroup; import android.view.ViewManager; import android.view.ViewParent; import android.view.ViewRootImpl; +import android.view.ViewRootImpl.ActivityConfigCallback; import android.view.Window; import android.view.WindowManager; import com.android.internal.R; @@ -287,6 +288,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private boolean mUseDecorContext = false; + /** @see ViewRootImpl#mActivityConfigCallback */ + private ActivityConfigCallback mActivityConfigCallback; + static class WindowManagerHolder { static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService("window")); @@ -302,7 +306,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { /** * Constructor for main window of an activity. */ - public PhoneWindow(Context context, Window preservedWindow) { + public PhoneWindow(Context context, Window preservedWindow, + ActivityConfigCallback activityConfigCallback) { this(context); // Only main activity windows use decor context, all the other windows depend on whatever // context that was given to them. @@ -323,6 +328,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0; mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_PICTURE_IN_PICTURE); + mActivityConfigCallback = activityConfigCallback; } @Override @@ -2060,6 +2066,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return mDecor; } + /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */ + void onViewRootImplSet(ViewRootImpl viewRoot) { + viewRoot.setActivityConfigCallback(mActivityConfigCallback); + } + static private final String FOCUSED_ID_TAG = "android:focusedViewId"; static private final String VIEWS_TAG = "android:views"; static private final String PANELS_TAG = "android:Panels"; diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 1ead5b3de1de..4e6857a72b43 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -114,6 +114,26 @@ public class Preconditions { } /** + * Ensures that an object reference passed as a parameter to the calling + * method is not null. + * + * @param reference an object reference + * @param messageTemplate a printf-style message template to use if the check fails; will + * be converted to a string using {@link String#format(String, Object...)} + * @param messageArgs arguments for {@code messageTemplate} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + public static @NonNull <T> T checkNotNull(final T reference, + final String messageTemplate, + final Object... messageArgs) { + if (reference == null) { + throw new NullPointerException(String.format(messageTemplate, messageArgs)); + } + return reference; + } + + /** * Ensures the truth of an expression involving the state of the calling * instance, but not involving any parameters to the calling method. * diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 738365d5b36f..5f585cc799c9 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -652,20 +652,20 @@ public class AccountManagerService return visibility; } - boolean isPrivileged = isPermittedForPackage(packageName, accounts.userId, + boolean isPrivileged = isPermittedForPackage(packageName, uid, accounts.userId, Manifest.permission.GET_ACCOUNTS_PRIVILEGED); // Device/Profile owner gets visibility by default. if (isProfileOwner(uid)) { return AccountManager.VISIBILITY_VISIBLE; } - // Apps with READ_CONTACTS permission get visibility by default even post O. - boolean canReadContacts = checkReadContactsPermission(packageName, accounts.userId); boolean preO = isPreOApplication(packageName); if ((signatureCheckResult != SIGNATURE_CHECK_MISMATCH) - || (preO && checkGetAccountsPermission(packageName, accounts.userId)) - || canReadContacts || isPrivileged) { + || (preO && checkGetAccountsPermission(packageName, uid, accounts.userId)) + || (checkReadContactsPermission(packageName, uid, accounts.userId) + && accountTypeManagesContacts(account.type, accounts.userId)) + || isPrivileged) { // Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature // match. visibility = getAccountVisibilityFromCache(account, @@ -5022,14 +5022,20 @@ public class AccountManagerService } } - private boolean isPermittedForPackage(String packageName, int userId, String... permissions) { + private boolean isPermittedForPackage(String packageName, int uid, int userId, + String... permissions) { final long identity = Binder.clearCallingIdentity(); try { IPackageManager pm = ActivityThread.getPackageManager(); for (String perm : permissions) { if (pm.checkPermission(perm, packageName, userId) == PackageManager.PERMISSION_GRANTED) { - return true; + // Checks runtime permission revocation. + final int opCode = AppOpsManager.permissionToOpCode(perm); + if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp( + opCode, uid, packageName) == AppOpsManager.MODE_ALLOWED) { + return true; + } } } } catch (RemoteException e) { @@ -5145,13 +5151,37 @@ public class AccountManagerService // Method checks visibility for applications targeing API level below {@link // android.os.Build.VERSION_CODES#O}, // returns true if the the app has GET_ACCOUNTS or GET_ACCOUNTS_PRIVILEGED permission. - private boolean checkGetAccountsPermission(String packageName, int userId) { - return isPermittedForPackage(packageName, userId, Manifest.permission.GET_ACCOUNTS, + private boolean checkGetAccountsPermission(String packageName, int uid, int userId) { + return isPermittedForPackage(packageName, uid, userId, Manifest.permission.GET_ACCOUNTS, Manifest.permission.GET_ACCOUNTS_PRIVILEGED); } - private boolean checkReadContactsPermission(String packageName, int userId) { - return isPermittedForPackage(packageName, userId, Manifest.permission.READ_CONTACTS); + private boolean checkReadContactsPermission(String packageName, int uid, int userId) { + return isPermittedForPackage(packageName, uid, userId, Manifest.permission.READ_CONTACTS); + } + + // Heuristic to check that account type may be associated with some contacts data and + // therefore READ_CONTACTS permission grants the access to account by default. + private boolean accountTypeManagesContacts(String accountType, int userId) { + if (accountType == null) { + return false; + } + long identityToken = Binder.clearCallingIdentity(); + Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> serviceInfos; + try { + serviceInfos = mAuthenticatorCache.getAllServices(userId); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + // Check contacts related permissions for authenticator. + for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo + : serviceInfos) { + if (accountType.equals(serviceInfo.type.type)) { + return isPermittedForPackage(serviceInfo.type.packageName, serviceInfo.uid, userId, + Manifest.permission.WRITE_CONTACTS); + } + } + return false; } /** diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 51011b586743..78887c6d8209 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -489,10 +489,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) { removeFromDisplay(); mTmpRect2.setEmpty(); - mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2); postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); adjustFocusToNextFocusableStackLocked("reparent", true /* allowFocusSelf */); mStackSupervisor.resumeFocusedStackTopActivityLocked(); + // Update visibility of activities before notifying WM. This way it won't try to resize + // windows that are no longer visible. + mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */, + !PRESERVE_WINDOWS); + mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2, onTop); } /** diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index c9bb9e547693..68e25c37dca8 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2775,9 +2775,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId=" + stackId); } - - ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */, - !PRESERVE_WINDOWS); // TODO(multi-display): resize stacks properly if moved from split-screen. } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 0e593bd02629..dad969cadf47 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -93,7 +93,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -125,12 +127,23 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering public final TetherInterfaceStateMachine stateMachine; public int lastState; public int lastError; + public TetherState(TetherInterfaceStateMachine sm) { stateMachine = sm; // Assume all state machines start out available and with no errors. lastState = IControlsTethering.STATE_AVAILABLE; lastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; } + + public boolean isCurrentlyServing() { + switch (lastState) { + case IControlsTethering.STATE_TETHERED: + case IControlsTethering.STATE_LOCAL_HOTSPOT: + return true; + default: + return false; + } + } } // used to synchronize public access to members @@ -146,11 +159,13 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering private final StateMachine mTetherMasterSM; private final OffloadController mOffloadController; private final UpstreamNetworkMonitor mUpstreamNetworkMonitor; + private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams; private volatile TetheringConfiguration mConfig; private String mCurrentUpstreamIface; private Notification.Builder mTetheredNotificationBuilder; private int mLastNotificationId; + private boolean mRndisEnabled; // track the RNDIS function enabled state private boolean mUsbTetherRequested; // true if USB tethering should be started // when RNDIS is enabled @@ -177,6 +192,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mOffloadController = new OffloadController(mTetherMasterSM.getHandler()); mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); + mForwardedDownstreams = new HashSet<>(); mStateReceiver = new StateReceiver(); IntentFilter filter = new IntentFilter(); @@ -514,6 +530,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } public int tether(String iface) { + return tether(iface, IControlsTethering.STATE_TETHERED); + } + + private int tether(String iface, int requestedState) { if (DBG) Log.d(TAG, "Tethering " + iface); synchronized (mPublicSync) { TetherState tetherState = mTetherStates.get(iface); @@ -527,7 +547,13 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring"); return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; } - tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's + // queue but not yet processed, this will be a no-op and it will not + // return an error. + // + // TODO: reexamine the threading and messaging model. + tetherState.stateMachine.sendMessage( + TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, requestedState); return ConnectivityManager.TETHER_ERROR_NO_ERROR; } } @@ -540,8 +566,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring"); return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; } - if (tetherState.lastState != IControlsTethering.STATE_TETHERED) { - Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring"); + if (!tetherState.isCurrentlyServing()) { + Log.e(TAG, "Tried to untether an inactive iface :" + iface + ", ignoring"); return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; } tetherState.stateMachine.sendMessage( @@ -568,6 +594,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } + // TODO: Figure out how to update for local hotspot mode interfaces. private void sendTetherStateChangedBroadcast() { if (!getConnectivityManager().isTetheringSupported()) return; @@ -745,7 +772,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mRndisEnabled = rndisEnabled; // start tethering if we have a request pending if (usbConnected && mRndisEnabled && mUsbTetherRequested) { - tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB); + tetherMatchingInterfaces( + IControlsTethering.STATE_TETHERED, + ConnectivityManager.TETHERING_USB); } mUsbTetherRequested = false; } @@ -760,9 +789,11 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering break; case WifiManager.WIFI_AP_STATE_ENABLED: // When the AP comes up and we've been requested to tether it, do so. - if (mWifiTetherRequested) { - tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI); - } + // Otherwise, assume it's a local-only hotspot request. + final int state = mWifiTetherRequested + ? IControlsTethering.STATE_TETHERED + : IControlsTethering.STATE_LOCAL_HOTSPOT; + tetherMatchingInterfaces(state, ConnectivityManager.TETHERING_WIFI); break; case WifiManager.WIFI_AP_STATE_DISABLED: case WifiManager.WIFI_AP_STATE_DISABLING: @@ -792,8 +823,16 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } - private void tetherMatchingInterfaces(boolean enable, int interfaceType) { - if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")"); + // TODO: Consider renaming to something more accurate in its description. + // This method: + // - allows requesting either tethering or local hotspot serving states + // - handles both enabling and disabling serving states + // - only tethers the first matching interface in listInterfaces() + // order of a given type + private void tetherMatchingInterfaces(int requestedState, int interfaceType) { + if (VDBG) { + Log.d(TAG, "tetherMatchingInterfaces(" + requestedState + ", " + interfaceType + ")"); + } String[] ifaces = null; try { @@ -816,7 +855,20 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering return; } - int result = (enable ? tether(chosenIface) : untether(chosenIface)); + final int result; + switch (requestedState) { + case IControlsTethering.STATE_UNAVAILABLE: + case IControlsTethering.STATE_AVAILABLE: + result = untether(chosenIface); + break; + case IControlsTethering.STATE_TETHERED: + case IControlsTethering.STATE_LOCAL_HOTSPOT: + result = tether(chosenIface, requestedState); + break; + default: + Log.wtf(TAG, "Unknown interface state: " + requestedState); + return; + } if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) { Log.e(TAG, "unable start or stop tethering on iface " + chosenIface); return; @@ -861,7 +913,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering if (mRndisEnabled) { final long ident = Binder.clearCallingIdentity(); try { - tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB); + tetherMatchingInterfaces(IControlsTethering.STATE_TETHERED, + ConnectivityManager.TETHERING_USB); } finally { Binder.restoreCallingIdentity(ident); } @@ -872,7 +925,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } else { final long ident = Binder.clearCallingIdentity(); try { - tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB); + tetherMatchingInterfaces(IControlsTethering.STATE_AVAILABLE, + ConnectivityManager.TETHERING_USB); } finally { Binder.restoreCallingIdentity(ident); } @@ -936,6 +990,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } + private boolean upstreamWanted() { + if (!mForwardedDownstreams.isEmpty()) return true; + + synchronized (mPublicSync) { + return mUsbTetherRequested || mWifiTetherRequested; + } + } + // Needed because the canonical source of upstream truth is just the // upstream interface name, |mCurrentUpstreamIface|. This is ripe for // future simplification, once the upstream Network is canonical. @@ -952,10 +1014,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering class TetherMasterSM extends StateMachine { private static final int BASE_MASTER = Protocol.BASE_TETHERING; - // an interface SM has requested Tethering - static final int CMD_TETHER_MODE_REQUESTED = BASE_MASTER + 1; - // an interface SM has unrequested Tethering - static final int CMD_TETHER_MODE_UNREQUESTED = BASE_MASTER + 2; + // an interface SM has requested Tethering/Local Hotspot + static final int EVENT_IFACE_SERVING_STATE_ACTIVE = BASE_MASTER + 1; + // an interface SM has unrequested Tethering/Local Hotspot + static final int EVENT_IFACE_SERVING_STATE_INACTIVE = BASE_MASTER + 2; // upstream connection change - do the right thing static final int CMD_UPSTREAM_CHANGED = BASE_MASTER + 3; // we don't have a valid upstream conn, check again after a delay @@ -1040,7 +1102,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering transitionTo(mSetIpForwardingEnabledErrorState); return false; } + // TODO: Randomize DHCPv4 ranges, especially in hotspot mode. try { + // TODO: Find a more accurate method name (startDHCPv4()?). mNMService.startTethering(cfg.dhcpRanges); } catch (Exception e) { try { @@ -1342,26 +1406,41 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } + private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) { + if (mNotifyList.indexOf(who) < 0) { + mNotifyList.add(who); + mIPv6TetheringCoordinator.addActiveDownstream(who, mode); + } + + if (mode == IControlsTethering.STATE_TETHERED) { + mForwardedDownstreams.add(who); + } else { + mForwardedDownstreams.remove(who); + } + } + + private void handleInterfaceServingStateInactive(TetherInterfaceStateMachine who) { + mNotifyList.remove(who); + mIPv6TetheringCoordinator.removeActiveDownstream(who); + mForwardedDownstreams.remove(who); + } + class InitialState extends TetherMasterUtilState { @Override public boolean processMessage(Message message) { maybeLogMessage(this, message.what); boolean retValue = true; switch (message.what) { - case CMD_TETHER_MODE_REQUESTED: + case EVENT_IFACE_SERVING_STATE_ACTIVE: TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); - if (mNotifyList.indexOf(who) < 0) { - mNotifyList.add(who); - mIPv6TetheringCoordinator.addActiveDownstream(who); - } + handleInterfaceServingStateActive(message.arg1, who); transitionTo(mTetherModeAliveState); break; - case CMD_TETHER_MODE_UNREQUESTED: + case EVENT_IFACE_SERVING_STATE_INACTIVE: who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); - mNotifyList.remove(who); - mIPv6TetheringCoordinator.removeActiveDownstream(who); + handleInterfaceServingStateInactive(who); break; default: retValue = false; @@ -1373,6 +1452,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering class TetherModeAliveState extends TetherMasterUtilState { final SimChangeListener simChange = new SimChangeListener(mContext); + boolean mUpstreamWanted = false; boolean mTryCell = true; @Override @@ -1383,9 +1463,11 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mUpstreamNetworkMonitor.start(); mOffloadController.start(); - // Better try something first pass or crazy tests cases will fail. - chooseUpstreamType(true); - mTryCell = false; + if (upstreamWanted()) { + mUpstreamWanted = true; + chooseUpstreamType(true); + mTryCell = false; + } } @Override @@ -1398,54 +1480,74 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering handleNewUpstreamNetworkState(null); } + private boolean updateUpstreamWanted() { + final boolean previousUpstreamWanted = mUpstreamWanted; + mUpstreamWanted = upstreamWanted(); + return previousUpstreamWanted; + } + @Override public boolean processMessage(Message message) { maybeLogMessage(this, message.what); boolean retValue = true; switch (message.what) { - case CMD_TETHER_MODE_REQUESTED: { + case EVENT_IFACE_SERVING_STATE_ACTIVE: { TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); - if (mNotifyList.indexOf(who) < 0) { - mNotifyList.add(who); - mIPv6TetheringCoordinator.addActiveDownstream(who); - } + handleInterfaceServingStateActive(message.arg1, who); who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, mCurrentUpstreamIface); + // If there has been a change and an upstream is now + // desired, kick off the selection process. + final boolean previousUpstreamWanted = updateUpstreamWanted(); + if (!previousUpstreamWanted && mUpstreamWanted) { + chooseUpstreamType(true); + } break; } - case CMD_TETHER_MODE_UNREQUESTED: { + case EVENT_IFACE_SERVING_STATE_INACTIVE: { TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); - if (mNotifyList.remove(who)) { - if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who); - if (mNotifyList.isEmpty()) { - turnOffMasterTetherSettings(); // transitions appropriately - } else { - if (DBG) { - Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() + - " live requests:"); - for (TetherInterfaceStateMachine o : mNotifyList) { - Log.d(TAG, " " + o); - } + handleInterfaceServingStateInactive(who); + + if (mNotifyList.isEmpty()) { + turnOffMasterTetherSettings(); // transitions appropriately + } else { + if (DBG) { + Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() + + " live requests:"); + for (TetherInterfaceStateMachine o : mNotifyList) { + Log.d(TAG, " " + o); } } - } else { - Log.e(TAG, "TetherModeAliveState UNREQUESTED has unknown who: " + who); } - mIPv6TetheringCoordinator.removeActiveDownstream(who); + // If there has been a change and an upstream is no + // longer desired, release any mobile requests. + final boolean previousUpstreamWanted = updateUpstreamWanted(); + if (previousUpstreamWanted && !mUpstreamWanted) { + mUpstreamNetworkMonitor.releaseMobileNetworkRequest(); + } break; } case CMD_UPSTREAM_CHANGED: + updateUpstreamWanted(); + if (!mUpstreamWanted) break; + // Need to try DUN immediately if Wi-Fi goes down. chooseUpstreamType(true); mTryCell = false; break; case CMD_RETRY_UPSTREAM: + updateUpstreamWanted(); + if (!mUpstreamWanted) break; + chooseUpstreamType(mTryCell); mTryCell = !mTryCell; break; case EVENT_UPSTREAM_CALLBACK: { + updateUpstreamWanted(); + if (!mUpstreamWanted) break; + final NetworkState ns = (NetworkState) message.obj; if (ns == null || !pertainsToCurrentUpstream(ns)) { @@ -1507,7 +1609,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering public boolean processMessage(Message message) { boolean retValue = true; switch (message.what) { - case CMD_TETHER_MODE_REQUESTED: + case EVENT_IFACE_SERVING_STATE_ACTIVE: TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; who.sendMessage(mErrorNotification); break; @@ -1615,12 +1717,16 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering case IControlsTethering.STATE_TETHERED: pw.print("TetheredState"); break; + case IControlsTethering.STATE_LOCAL_HOTSPOT: + pw.print("LocalHotspotState"); + break; default: pw.print("UnknownState"); break; } pw.println(" - lastError = " + tetherState.lastError); } + pw.println("Upstream wanted: " + upstreamWanted()); pw.decreaseIndent(); } pw.decreaseIndent(); @@ -1659,15 +1765,21 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering if (error == ConnectivityManager.TETHER_ERROR_MASTER_ERROR) { mTetherMasterSM.sendMessage(TetherMasterSM.CMD_CLEAR_ERROR, who); } + int which; switch (state) { case IControlsTethering.STATE_UNAVAILABLE: case IControlsTethering.STATE_AVAILABLE: - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who); + which = TetherMasterSM.EVENT_IFACE_SERVING_STATE_INACTIVE; break; case IControlsTethering.STATE_TETHERED: - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, who); + case IControlsTethering.STATE_LOCAL_HOTSPOT: + which = TetherMasterSM.EVENT_IFACE_SERVING_STATE_ACTIVE; break; + default: + Log.wtf(TAG, "Unknown interface state: " + state); + return; } + mTetherMasterSM.sendMessage(which, state, 0, who); sendTetherStateChangedBroadcast(); } diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java index 449b8a8354e7..f3914b7cb299 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java +++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java @@ -25,6 +25,7 @@ public interface IControlsTethering { public final int STATE_UNAVAILABLE = 0; public final int STATE_AVAILABLE = 1; public final int STATE_TETHERED = 2; + public final int STATE_LOCAL_HOTSPOT = 3; /** * Notify that |who| has changed its tethering state. This may be called from any thread. diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java index 9173febd0ef3..5f496ca59e6a 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java @@ -24,12 +24,17 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkState; import android.net.RouteInfo; +import android.net.util.NetworkConstants; import android.util.Log; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedList; +import java.util.Random; /** @@ -45,29 +50,65 @@ public class IPv6TetheringCoordinator { private static final boolean DBG = false; private static final boolean VDBG = false; + private static class Downstream { + public final TetherInterfaceStateMachine tism; + public final int mode; // IControlsTethering.STATE_* + // Used to append to a ULA /48, constructing a ULA /64 for local use. + public final short subnetId; + + Downstream(TetherInterfaceStateMachine tism, int mode, short subnetId) { + this.tism = tism; + this.mode = mode; + this.subnetId = subnetId; + } + } + private final ArrayList<TetherInterfaceStateMachine> mNotifyList; - private final LinkedList<TetherInterfaceStateMachine> mActiveDownstreams; + // NOTE: mActiveDownstreams is a list and not a hash data structure because + // we keep active downstreams in arrival order. This is done so /64s can + // be parceled out on a "first come, first served" basis and a /64 used by + // a downstream that is no longer active can be redistributed to any next + // waiting active downstream (again, in arrival order). + private final LinkedList<Downstream> mActiveDownstreams; + private final byte[] mUniqueLocalPrefix; + private short mNextSubnetId; private NetworkState mUpstreamNetworkState; public IPv6TetheringCoordinator(ArrayList<TetherInterfaceStateMachine> notifyList) { mNotifyList = notifyList; mActiveDownstreams = new LinkedList<>(); + mUniqueLocalPrefix = generateUniqueLocalPrefix(); + mNextSubnetId = 0; } - public void addActiveDownstream(TetherInterfaceStateMachine downstream) { - if (mActiveDownstreams.indexOf(downstream) == -1) { + public void addActiveDownstream(TetherInterfaceStateMachine downstream, int mode) { + if (findDownstream(downstream) == null) { // Adding a new downstream appends it to the list. Adding a // downstream a second time without first removing it has no effect. - mActiveDownstreams.offer(downstream); + // We never change the mode of a downstream except by first removing + // it and then re-adding it (with its new mode specified); + if (mActiveDownstreams.offer(new Downstream(downstream, mode, mNextSubnetId))) { + // Make sure subnet IDs are always positive. They are appended + // to a ULA /48 to make a ULA /64 for local use. + mNextSubnetId = (short) Math.max(0, mNextSubnetId + 1); + } updateIPv6TetheringInterfaces(); } } public void removeActiveDownstream(TetherInterfaceStateMachine downstream) { stopIPv6TetheringOn(downstream); - if (mActiveDownstreams.remove(downstream)) { + if (mActiveDownstreams.remove(findDownstream(downstream))) { updateIPv6TetheringInterfaces(); } + + // When tethering is stopping we can reset the subnet counter. + if (mNotifyList.isEmpty()) { + if (!mActiveDownstreams.isEmpty()) { + Log.wtf(TAG, "Tethering notify list empty, IPv6 downstreams non-empty."); + } + mNextSubnetId = 0; + } } public void updateUpstreamNetworkState(NetworkState ns) { @@ -123,20 +164,31 @@ public class IPv6TetheringCoordinator { } private LinkProperties getInterfaceIPv6LinkProperties(TetherInterfaceStateMachine sm) { - if (mUpstreamNetworkState == null) return null; - if (sm.interfaceType() == ConnectivityManager.TETHERING_BLUETOOTH) { // TODO: Figure out IPv6 support on PAN interfaces. return null; } + final Downstream ds = findDownstream(sm); + if (ds == null) return null; + + if (ds.mode == IControlsTethering.STATE_LOCAL_HOTSPOT) { + // Build a Unique Locally-assigned Prefix configuration. + return getUniqueLocalConfig(mUniqueLocalPrefix, ds.subnetId); + } + + // This downstream is in IControlsTethering.STATE_TETHERED mode. + if (mUpstreamNetworkState == null || mUpstreamNetworkState.linkProperties == null) { + return null; + } + // NOTE: Here, in future, we would have policies to decide how to divvy // up the available dedicated prefixes among downstream interfaces. // At this time we have no such mechanism--we only support tethering // IPv6 toward the oldest (first requested) active downstream. - final TetherInterfaceStateMachine currentActive = mActiveDownstreams.peek(); - if (currentActive != null && currentActive == sm) { + final Downstream currentActive = mActiveDownstreams.peek(); + if (currentActive != null && currentActive.tism == sm) { final LinkProperties lp = getIPv6OnlyLinkProperties( mUpstreamNetworkState.linkProperties); if (lp.hasIPv6DefaultRoute() && lp.hasGlobalIPv6Address()) { @@ -147,6 +199,13 @@ public class IPv6TetheringCoordinator { return null; } + Downstream findDownstream(TetherInterfaceStateMachine tism) { + for (Downstream ds : mActiveDownstreams) { + if (ds.tism == tism) return ds; + } + return null; + } + private static boolean canTetherIPv6(NetworkState ns) { // Broadly speaking: // @@ -263,6 +322,44 @@ public class IPv6TetheringCoordinator { !ip.isMulticastAddress(); } + private static LinkProperties getUniqueLocalConfig(byte[] ulp, short subnetId) { + final LinkProperties lp = new LinkProperties(); + + final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48); + lp.addRoute(new RouteInfo(local48, null, null)); + + final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64); + // Because this is a locally-generated ULA, we don't have an upstream + // address. But because the downstream IP address management code gets + // its prefix from the upstream's IP address, we create a fake one here. + lp.addLinkAddress(new LinkAddress(local64.getAddress(), 64)); + + lp.setMtu(NetworkConstants.ETHER_MTU); + return lp; + } + + private static IpPrefix makeUniqueLocalPrefix(byte[] in6addr, short subnetId, int prefixlen) { + final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length); + bytes[7] = (byte) (subnetId >> 8); + bytes[8] = (byte) subnetId; + return new IpPrefix(bytes, prefixlen); + } + + // Generates a Unique Locally-assigned Prefix: + // + // https://tools.ietf.org/html/rfc4193#section-3.1 + // + // The result is a /48 that can be used for local-only communications. + private static byte[] generateUniqueLocalPrefix() { + final byte[] ulp = new byte[6]; // 6 = 48bits / 8bits/byte + (new Random()).nextBytes(ulp); + + final byte[] in6addr = Arrays.copyOf(ulp, NetworkConstants.IPV6_ADDR_LEN); + in6addr[0] = (byte) 0xfd; // fc00::/7 and L=1 + + return in6addr; + } + private static String toDebugString(NetworkState ns) { if (ns == null) { return "NetworkState{null}"; diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java index 8c6430c8a2ae..c6a7925f2b5e 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java @@ -42,6 +42,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; +import java.util.Random; /** @@ -66,10 +67,17 @@ public class IPv6TetheringInterfaceServices { } public boolean start() { + // TODO: Refactor for testability (perhaps passing an android.system.Os + // instance and calling getifaddrs() directly). try { mNetworkInterface = NetworkInterface.getByName(mIfName); } catch (SocketException e) { - Log.e(TAG, "Failed to find NetworkInterface for " + mIfName, e); + Log.e(TAG, "Error looking up NetworkInterfaces for " + mIfName, e); + stop(); + return false; + } + if (mNetworkInterface == null) { + Log.e(TAG, "Failed to find NetworkInterface for " + mIfName); stop(); return false; } @@ -267,10 +275,10 @@ public class IPv6TetheringInterfaceServices { return localRoutes; } - // Given a prefix like 2001:db8::/64 return 2001:db8::1. + // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1. private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) { final byte[] dnsBytes = localPrefix.getRawAddress(); - dnsBytes[dnsBytes.length - 1] = 0x1; + dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte(); try { return Inet6Address.getByAddress(null, dnsBytes, 0); } catch (UnknownHostException e) { @@ -278,4 +286,11 @@ public class IPv6TetheringInterfaceServices { return null; } } + + private static byte getRandomNonZeroByte() { + final byte random = (byte) (new Random()).nextInt(); + // Don't pick the subnet-router anycast address, since that might be + // in use on the upstream already. + return (random != 0) ? random : 0x1; + } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index 710ab33874cd..1ffa86440ae2 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -78,6 +78,8 @@ public class TetherInterfaceStateMachine extends StateMachine { public static final int CMD_IPV6_TETHER_UPDATE = BASE_IFACE + 13; private final State mInitialState; + private final State mServingState; + private final State mLocalHotspotState; private final State mTetheredState; private final State mUnavailableState; @@ -105,10 +107,14 @@ public class TetherInterfaceStateMachine extends StateMachine { mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; mInitialState = new InitialState(); - addState(mInitialState); + mServingState = new ServingState(); + mLocalHotspotState = new LocalHotspotState(); mTetheredState = new TetheredState(); - addState(mTetheredState); mUnavailableState = new UnavailableState(); + addState(mInitialState); + addState(mServingState); + addState(mLocalHotspotState, mServingState); + addState(mTetheredState, mServingState); addState(mUnavailableState); setInitialState(mInitialState); @@ -172,12 +178,15 @@ public class TetherInterfaceStateMachine extends StateMachine { } } + private void sendInterfaceState(int newInterfaceState) { + mTetherController.notifyInterfaceStateChange( + mIfaceName, TetherInterfaceStateMachine.this, newInterfaceState, mLastError); + } + class InitialState extends State { @Override public void enter() { - mTetherController.notifyInterfaceStateChange( - mIfaceName, TetherInterfaceStateMachine.this, - IControlsTethering.STATE_AVAILABLE, mLastError); + sendInterfaceState(IControlsTethering.STATE_AVAILABLE); } @Override @@ -187,7 +196,16 @@ public class TetherInterfaceStateMachine extends StateMachine { switch (message.what) { case CMD_TETHER_REQUESTED: mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; - transitionTo(mTetheredState); + switch (message.arg1) { + case IControlsTethering.STATE_LOCAL_HOTSPOT: + transitionTo(mLocalHotspotState); + break; + case IControlsTethering.STATE_TETHERED: + transitionTo(mTetheredState); + break; + default: + Log.e(TAG, "Invalid tethering interface serving state specified."); + } break; case CMD_INTERFACE_DOWN: transitionTo(mUnavailableState); @@ -204,7 +222,7 @@ public class TetherInterfaceStateMachine extends StateMachine { } } - class TetheredState extends State { + class ServingState extends State { @Override public void enter() { if (!configureIfaceIp(true)) { @@ -225,11 +243,6 @@ public class TetherInterfaceStateMachine extends StateMachine { if (!mIPv6TetherSvc.start()) { Log.e(TAG, "Failed to start IPv6TetheringInterfaceServices"); } - - if (DBG) Log.d(TAG, "Tethered " + mIfaceName); - mTetherController.notifyInterfaceStateChange( - mIfaceName, TetherInterfaceStateMachine.this, - IControlsTethering.STATE_TETHERED, mLastError); } @Override @@ -238,7 +251,6 @@ public class TetherInterfaceStateMachine extends StateMachine { // of these operations, but it doesn't really change that we have to try them // all in sequence. mIPv6TetherSvc.stop(); - cleanupUpstream(); try { mNMService.untetherInterface(mIfaceName); @@ -250,6 +262,73 @@ public class TetherInterfaceStateMachine extends StateMachine { configureIfaceIp(false); } + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + switch (message.what) { + case CMD_TETHER_UNREQUESTED: + transitionTo(mInitialState); + if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName); + break; + case CMD_INTERFACE_DOWN: + transitionTo(mUnavailableState); + if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName); + break; + case CMD_IPV6_TETHER_UPDATE: + mIPv6TetherSvc.updateUpstreamIPv6LinkProperties( + (LinkProperties) message.obj); + break; + case CMD_IP_FORWARDING_ENABLE_ERROR: + case CMD_IP_FORWARDING_DISABLE_ERROR: + case CMD_START_TETHERING_ERROR: + case CMD_STOP_TETHERING_ERROR: + case CMD_SET_DNS_FORWARDERS_ERROR: + mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR; + transitionTo(mInitialState); + break; + default: + return false; + } + return true; + } + } + + class LocalHotspotState extends State { + @Override + public void enter() { + if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName); + sendInterfaceState(IControlsTethering.STATE_LOCAL_HOTSPOT); + } + + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + switch (message.what) { + case CMD_TETHER_REQUESTED: + Log.e(TAG, "CMD_TETHER_REQUESTED while in local hotspot mode."); + break; + case CMD_TETHER_CONNECTION_CHANGED: + // Ignored in local hotspot state. + break; + default: + return false; + } + return true; + } + } + + class TetheredState extends State { + @Override + public void enter() { + if (DBG) Log.d(TAG, "Tethered " + mIfaceName); + sendInterfaceState(IControlsTethering.STATE_TETHERED); + } + + @Override + public void exit() { + cleanupUpstream(); + } + private void cleanupUpstream() { if (mMyUpstreamIfaceName == null) return; @@ -285,13 +364,8 @@ public class TetherInterfaceStateMachine extends StateMachine { maybeLogMessage(this, message.what); boolean retValue = true; switch (message.what) { - case CMD_TETHER_UNREQUESTED: - transitionTo(mInitialState); - if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName); - break; - case CMD_INTERFACE_DOWN: - transitionTo(mUnavailableState); - if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName); + case CMD_TETHER_REQUESTED: + Log.e(TAG, "CMD_TETHER_REQUESTED while already tethering."); break; case CMD_TETHER_CONNECTION_CHANGED: String newUpstreamIfaceName = (String)(message.obj); @@ -317,18 +391,6 @@ public class TetherInterfaceStateMachine extends StateMachine { } mMyUpstreamIfaceName = newUpstreamIfaceName; break; - case CMD_IPV6_TETHER_UPDATE: - mIPv6TetherSvc.updateUpstreamIPv6LinkProperties( - (LinkProperties) message.obj); - break; - case CMD_IP_FORWARDING_ENABLE_ERROR: - case CMD_IP_FORWARDING_DISABLE_ERROR: - case CMD_START_TETHERING_ERROR: - case CMD_STOP_TETHERING_ERROR: - case CMD_SET_DNS_FORWARDERS_ERROR: - mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR; - transitionTo(mInitialState); - break; default: retValue = false; break; @@ -348,9 +410,7 @@ public class TetherInterfaceStateMachine extends StateMachine { @Override public void enter() { mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; - mTetherController.notifyInterfaceStateChange( - mIfaceName, TetherInterfaceStateMachine.this, - IControlsTethering.STATE_UNAVAILABLE, mLastError); + sendInterfaceState(IControlsTethering.STATE_UNAVAILABLE); } } } diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 62099293b31c..97a2d5ed6f5c 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -308,7 +308,8 @@ public class UpstreamNetworkMonitor { // Fetch (and cache) a ConnectivityManager only if and when we need one. private ConnectivityManager cm() { if (mCM == null) { - mCM = mContext.getSystemService(ConnectivityManager.class); + // MUST call the String variant to be able to write unittests. + mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); } return mCM; } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index c8028fe6d1de..2e499f175650 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -1003,22 +1003,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean meteredHint = info.getMeteredHint(); final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(info.getSSID()); - synchronized (mNetworkPoliciesSecondLock) { - NetworkPolicy policy = mNetworkPolicy.get(template); - if (policy == null && meteredHint) { - // policy doesn't exist, and AP is hinting that it's - // metered: create an inferred policy. - policy = newWifiPolicy(template, meteredHint); - addNetworkPolicyNL(policy); - - } else if (policy != null && policy.inferred) { - // policy exists, and was inferred: update its current - // metered state. - policy.metered = meteredHint; - - // since this is inferred for each wifi session, just update - // rules without persisting. - updateNetworkRulesNL(); + synchronized (mUidRulesFirstLock) { + synchronized (mNetworkPoliciesSecondLock) { + NetworkPolicy policy = mNetworkPolicy.get(template); + if (policy == null && meteredHint) { + // policy doesn't exist, and AP is hinting that it's + // metered: create an inferred policy. + policy = newWifiPolicy(template, meteredHint); + addNetworkPolicyAL(policy); + + } else if (policy != null && policy.inferred) { + // policy exists, and was inferred: update its current + // metered state. + policy.metered = meteredHint; + + // since this is inferred for each wifi session, just update + // rules without persisting. + updateNetworkRulesNL(); + } } } } @@ -1289,12 +1291,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // permission above. maybeRefreshTrustedTime(); - synchronized (mNetworkPoliciesSecondLock) { - ensureActiveMobilePolicyNL(); - normalizePoliciesNL(); - updateNetworkEnabledNL(); - updateNetworkRulesNL(); - updateNotificationsNL(); + synchronized (mUidRulesFirstLock) { + synchronized (mNetworkPoliciesSecondLock) { + ensureActiveMobilePolicyAL(); + normalizePoliciesNL(); + updateNetworkEnabledNL(); + updateNetworkRulesNL(); + updateNotificationsNL(); + } } } }; @@ -1477,7 +1481,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { maybeRefreshTrustedTime(); synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { - final boolean added = ensureActiveMobilePolicyNL(subId, subscriberId); + final boolean added = ensureActiveMobilePolicyAL(subId, subscriberId); if (added) return; final boolean updated = maybeUpdateMobilePolicyCycleNL(subId); if (!updated) return; @@ -1721,8 +1725,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we * have at least a default mobile policy defined. */ - private void ensureActiveMobilePolicyNL() { - if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyNL()"); + private void ensureActiveMobilePolicyAL() { + if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyAL()"); if (mSuppressDefaultPolicy) return; final TelephonyManager tele = TelephonyManager.from(mContext); @@ -1731,7 +1735,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int[] subIds = sub.getActiveSubscriptionIdList(); for (int subId : subIds) { final String subscriberId = tele.getSubscriberId(subId); - ensureActiveMobilePolicyNL(subId, subscriberId); + ensureActiveMobilePolicyAL(subId, subscriberId); } } @@ -1743,7 +1747,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * @param subscriberId that we check for an existing policy * @return true if a mobile network policy was added, or false one already existed. */ - private boolean ensureActiveMobilePolicyNL(int subId, String subscriberId) { + private boolean ensureActiveMobilePolicyAL(int subId, String subscriberId) { // Poke around to see if we already have a policy final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true); @@ -1761,7 +1765,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId) + "; generating default policy"); final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId); - addNetworkPolicyNL(policy); + addNetworkPolicyAL(policy); return true; } @@ -2265,7 +2269,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - void addNetworkPolicyNL(NetworkPolicy policy) { + void addNetworkPolicyAL(NetworkPolicy policy) { NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName()); policies = ArrayUtils.appendElement(NetworkPolicy.class, policies, policy); setNetworkPolicies(policies); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 21be74242aab..23058bd6a9b6 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1662,7 +1662,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return stack; } - void moveStackToDisplay(TaskStack stack) { + void moveStackToDisplay(TaskStack stack, boolean onTop) { final DisplayContent prevDc = stack.getDisplayContent(); if (prevDc == null) { throw new IllegalStateException("Trying to move stackId=" + stack.mStackId @@ -1674,7 +1674,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } prevDc.mTaskStackContainers.removeStackFromDisplay(stack); - mTaskStackContainers.addStackToDisplay(stack, true /* onTop */); + mTaskStackContainers.addStackToDisplay(stack, onTop); } @Override diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java index 5d0384e34e93..bf024cfdd7c3 100644 --- a/services/core/java/com/android/server/wm/StackWindowController.java +++ b/services/core/java/com/android/server/wm/StackWindowController.java @@ -100,7 +100,7 @@ public class StackWindowController } } - public void reparent(int displayId, Rect outStackBounds) { + public void reparent(int displayId, Rect outStackBounds, boolean onTop) { synchronized (mWindowMap) { if (mContainer == null) { throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId @@ -113,7 +113,7 @@ public class StackWindowController + " to unknown displayId=" + displayId); } - targetDc.moveStackToDisplay(mContainer); + targetDc.moveStackToDisplay(mContainer, onTop); getRawBounds(outStackBounds); } } diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java index ba1621d7d6bc..cb3123ce466a 100644 --- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java +++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java @@ -16,6 +16,8 @@ package android.net.ip; +import static android.net.util.NetworkConstants.IPV6_MIN_MTU; +import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; import static android.system.OsConstants.*; import android.net.IpPrefix; @@ -69,7 +71,6 @@ public class RouterAdvertisementDaemon { private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName(); private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133); private static final byte ICMPV6_ND_ROUTER_ADVERT = asByte(134); - private static final int IPV6_MIN_MTU = 1280; private static final int MIN_RA_HEADER_SIZE = 16; // Summary of various timers and lifetimes. @@ -543,6 +544,14 @@ public class RouterAdvertisementDaemon { +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ + final HashSet<Inet6Address> filteredDnses = new HashSet<>(); + for (Inet6Address dns : dnses) { + if ((new LinkAddress(dns, RFC7421_PREFIX_LENGTH)).isGlobalPreferred()) { + filteredDnses.add(dns); + } + } + if (filteredDnses.isEmpty()) return; + final byte ND_OPTION_RDNSS = 25; final byte RDNSS_NUM_8OCTETS = asByte(dnses.size() * 2 + 1); ra.put(ND_OPTION_RDNSS) @@ -550,7 +559,7 @@ public class RouterAdvertisementDaemon { .putShort(asShort(0)) .putInt(lifetime); - for (Inet6Address dns : dnses) { + for (Inet6Address dns : filteredDnses) { // NOTE: If the full of list DNS servers doesn't fit in the packet, // this code will cause a buffer overflow and the RA won't include // this instance of the option at all. diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index 26f3050468dd..a012e0cb0547 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -36,6 +36,7 @@ public final class NetworkConstants { * * See also: * - https://tools.ietf.org/html/rfc894 + * - https://tools.ietf.org/html/rfc2464 * - https://tools.ietf.org/html/rfc7042 * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml @@ -57,6 +58,8 @@ public final class NetworkConstants { FF, FF, FF, FF, FF, FF }; + public static final int ETHER_MTU = 1500; + /** * ARP constants. * @@ -97,6 +100,7 @@ public final class NetworkConstants { public static final int IPV6_SRC_ADDR_OFFSET = 8; public static final int IPV6_DST_ADDR_OFFSET = 24; public static final int IPV6_ADDR_LEN = 16; + public static final int IPV6_MIN_MTU = 1280; public static final int RFC7421_PREFIX_LENGTH = 64; /** diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java index 7790698d02e9..d0dfc6cc4fe8 100644 --- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java +++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java @@ -17,6 +17,7 @@ package com.android.server.print; +import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest; @@ -50,6 +51,7 @@ import android.util.ExceptionUtils; import android.util.Slog; import android.util.Xml; +import com.android.internal.app.IAppOpsService; import com.android.internal.content.PackageMonitor; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -98,12 +100,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private IDeviceIdleController mIdleController; private IFindDeviceCallback mFindDeviceCallback; private ServiceConnection mServiceConnection; + private IAppOpsService mAppOpsManager; public CompanionDeviceManagerService(Context context) { super(context); mImpl = new CompanionDeviceManagerImpl(); mIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + mAppOpsManager = IAppOpsService.Stub.asInterface( + ServiceManager.getService(Context.APP_OPS_SERVICE)); registerPackageMonitor(); } @@ -182,13 +187,14 @@ public class CompanionDeviceManagerService extends SystemService implements Bind public void associate( AssociationRequest request, IFindDeviceCallback callback, - String callingPackage) { + String callingPackage) throws RemoteException { if (DEBUG) { Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback + ", callingPackage = " + callingPackage + ")"); } checkNotNull(request, "Request cannot be null"); checkNotNull(callback, "Callback cannot be null"); + checkCallerIsSystemOr(callingPackage); final long callingIdentity = Binder.clearCallingIdentity(); try { //TODO bindServiceAsUser @@ -203,20 +209,40 @@ public class CompanionDeviceManagerService extends SystemService implements Bind @Override - public List<String> getAssociations(String callingPackage) { + public List<String> getAssociations(String callingPackage, int userId) + throws RemoteException { + checkCallerIsSystemOr(callingPackage, userId); return CollectionUtils.map( - readAllAssociations(getUserId(), callingPackage), + readAllAssociations(userId, callingPackage), a -> a.deviceAddress); } @Override - public void disassociate(String deviceMacAddress, String callingPackage) { - updateAssociations((associations) -> ArrayUtils.remove(associations, - new Association(getUserId(), checkNotNull(deviceMacAddress), callingPackage))); + public void disassociate(String deviceMacAddress, String callingPackage) + throws RemoteException { + checkNotNull(deviceMacAddress); + checkCallerIsSystemOr(callingPackage); + updateAssociations(associations -> ArrayUtils.remove(associations, + new Association(getCallingUserId(), deviceMacAddress, callingPackage))); + } + + private void checkCallerIsSystemOr(String pkg) throws RemoteException { + checkCallerIsSystemOr(pkg, getCallingUserId()); + } + + private void checkCallerIsSystemOr(String pkg, int userId) throws RemoteException { + if (getCallingUserId() == UserHandle.USER_SYSTEM) { + return; + } + + checkArgument(getCallingUserId() == userId, + "Must be called by either same user or system"); + + mAppOpsManager.checkPackage(Binder.getCallingUid(), pkg); } } - private int getUserId() { + private int getCallingUserId() { return UserHandle.getUserId(Binder.getCallingUid()); } @@ -320,11 +346,11 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private void recordAssociation(String priviledgedPackage, String deviceAddress) { updateAssociations((associations) -> ArrayUtils.add(associations, - new Association(getUserId(), deviceAddress, priviledgedPackage))); + new Association(getCallingUserId(), deviceAddress, priviledgedPackage))); } private void updateAssociations(Function<ArrayList<Association>, List<Association>> update) { - updateAssociations(update, getUserId()); + updateAssociations(update, getCallingUserId()); } private void updateAssociations(Function<ArrayList<Association>, List<Association>> update, diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index d7d365e72480..0270bb955988 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -181,7 +181,7 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(dc, token.getDisplayContent()); // Move stack to first display. - sDisplayContent.moveStackToDisplay(stack); + sDisplayContent.moveStackToDisplay(stack, true /* onTop */); assertEquals(sDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); assertEquals(sDisplayContent, stack.getParent().getParent()); assertEquals(sDisplayContent, stack.getDisplayContent()); diff --git a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java index 13098f64bfac..61f7f5708e89 100644 --- a/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/StackWindowControllerTests.java @@ -103,7 +103,7 @@ public class StackWindowControllerTests extends WindowTestsBase { final TaskStack stack2 = stack2Controller.mContainer; // Reparent - stack1Controller.reparent(dc.getDisplayId(), new Rect()); + stack1Controller.reparent(dc.getDisplayId(), new Rect(), true /* onTop */); assertEquals(dc, stack1.getDisplayContent()); final int stack1PositionInParent = stack1.getParent().mChildren.indexOf(stack1); final int stack2PositionInParent = stack1.getParent().mChildren.indexOf(stack2); diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index b5a87ca8f110..80b73d3677b0 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -640,8 +640,11 @@ public class UsbDeviceManager { // Set the new USB configuration. setUsbConfig(oemFunctions); - // Start up dependent services. - updateUsbStateBroadcastIfNeeded(true); + if (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP) + || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP)) { + // Start up dependent services. + updateUsbStateBroadcastIfNeeded(true); + } if (!waitForState(oemFunctions)) { Slog.e(TAG, "Failed to switch USB config to " + functions); diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index a9f68c88c8c5..e527d57f7367 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -16,22 +16,43 @@ package com.android.server.connectivity; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; import android.content.res.Resources; +import android.hardware.usb.UsbManager; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.InterfaceConfiguration; +import android.net.NetworkRequest; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Handler; import android.os.INetworkManagementService; import android.os.PersistableBundle; import android.os.test.TestLooper; +import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.CarrierConfigManager; +import com.android.internal.util.test.BroadcastInterceptingContext; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,34 +65,60 @@ public class TetheringTest { private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; @Mock private Context mContext; + @Mock private ConnectivityManager mConnectivityManager; @Mock private INetworkManagementService mNMService; @Mock private INetworkStatsService mStatsService; @Mock private INetworkPolicyManager mPolicyManager; @Mock private MockableSystemProperties mSystemProperties; @Mock private Resources mResources; + @Mock private UsbManager mUsbManager; + @Mock private WifiManager mWifiManager; @Mock private CarrierConfigManager mCarrierConfigManager; // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. private final PersistableBundle mCarrierConfig = new PersistableBundle(); private final TestLooper mLooper = new TestLooper(); + private final String mTestIfname = "test_wlan0"; + private BroadcastInterceptingContext mServiceContext; private Tethering mTethering; + private class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + + @Override + public Resources getResources() { return mResources; } + + @Override + public Object getSystemService(String name) { + if (Context.CONNECTIVITY_SERVICE.equals(name)) return mConnectivityManager; + if (Context.WIFI_SERVICE.equals(name)) return mWifiManager; + return super.getSystemService(name); + } + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - when(mContext.getResources()).thenReturn(mResources); when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs)) .thenReturn(new String[0]); when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs)) - .thenReturn(new String[0]); + .thenReturn(new String[]{ "test_wlan\\d" }); when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs)) .thenReturn(new String[0]); when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types)) .thenReturn(new int[0]); - mTethering = new Tethering(mContext, mNMService, mStatsService, mPolicyManager, + when(mNMService.listInterfaces()) + .thenReturn(new String[]{ "test_rmnet_data0", mTestIfname }); + when(mNMService.getInterfaceConfig(anyString())) + .thenReturn(new InterfaceConfiguration()); + + mServiceContext = new MockContext(mContext); + mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager, mLooper.getLooper(), mSystemProperties); } @@ -126,4 +173,144 @@ public class TetheringTest { .thenReturn(new String[] {"malformedApp"}); assertTrue(!mTethering.isTetherProvisioningRequired()); } + + private void sendWifiApStateChanged(int state) { + final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, state); + mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + + @Test + public void workingLocalOnlyHotspot() throws Exception { + when(mConnectivityManager.isTetheringSupported()).thenReturn(true); + when(mWifiManager.setWifiApEnabled(any(WifiConfiguration.class), anyBoolean())) + .thenReturn(true); + + // Emulate externally-visible WifiManager effects, causing the + // per-interface state machine to start up, and telling us that + // hotspot mode is to be started. + mTethering.interfaceStatusChanged(mTestIfname, true); + sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED); + mLooper.dispatchAll(); + + verify(mNMService, times(1)).listInterfaces(); + verify(mNMService, times(1)).getInterfaceConfig(mTestIfname); + verify(mNMService, times(1)) + .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).tetherInterface(mTestIfname); + verify(mNMService, times(1)).setIpForwardingEnabled(true); + verify(mNMService, times(1)).startTethering(any(String[].class)); + verifyNoMoreInteractions(mNMService); + // UpstreamNetworkMonitor will be started, and will register two callbacks: + // a "listen all" and a "track default". + verify(mConnectivityManager, times(1)).registerNetworkCallback( + any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class)); + verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback( + any(NetworkCallback.class), any(Handler.class)); + // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). + verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); + verifyNoMoreInteractions(mConnectivityManager); + + // Emulate externally-visible WifiManager effects, when hotspot mode + // is being torn down. + sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED); + mTethering.interfaceRemoved(mTestIfname); + mLooper.dispatchAll(); + + verify(mNMService, times(1)).untetherInterface(mTestIfname); + // TODO: Why is {g,s}etInterfaceConfig() called more than once? + verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname); + verify(mNMService, atLeastOnce()) + .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).stopTethering(); + verify(mNMService, times(1)).setIpForwardingEnabled(false); + verifyNoMoreInteractions(mNMService); + // Asking for the last error after the per-interface state machine + // has been reaped yields an unknown interface error. + assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, + mTethering.getLastTetherError(mTestIfname)); + } + + @Test + public void workingWifiTethering() throws Exception { + when(mConnectivityManager.isTetheringSupported()).thenReturn(true); + when(mWifiManager.setWifiApEnabled(any(WifiConfiguration.class), anyBoolean())) + .thenReturn(true); + + // Emulate pressing the WiFi tethering button. + mTethering.startTethering(ConnectivityManager.TETHERING_WIFI, null, false); + mLooper.dispatchAll(); + verify(mWifiManager, times(1)).setWifiApEnabled(null, true); + verifyNoMoreInteractions(mWifiManager); + verifyNoMoreInteractions(mConnectivityManager); + verifyNoMoreInteractions(mNMService); + + // Emulate externally-visible WifiManager effects, causing the + // per-interface state machine to start up, and telling us that + // tethering mode is to be started. + mTethering.interfaceStatusChanged(mTestIfname, true); + sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_ENABLED); + mLooper.dispatchAll(); + + verify(mNMService, times(1)).listInterfaces(); + verify(mNMService, times(1)).getInterfaceConfig(mTestIfname); + verify(mNMService, times(1)) + .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).tetherInterface(mTestIfname); + verify(mNMService, times(1)).setIpForwardingEnabled(true); + verify(mNMService, times(1)).startTethering(any(String[].class)); + verifyNoMoreInteractions(mNMService); + // UpstreamNetworkMonitor will be started, and will register two callbacks: + // a "listen all" and a "track default". + verify(mConnectivityManager, times(1)).registerNetworkCallback( + any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class)); + verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback( + any(NetworkCallback.class), any(Handler.class)); + // In tethering mode, in the default configuration, an explicit request + // for a mobile network is also made. + verify(mConnectivityManager, atLeastOnce()).getNetworkInfo(anyInt()); + verify(mConnectivityManager, times(1)).requestNetwork( + any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(), + any(Handler.class)); + // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast(). + verify(mConnectivityManager, atLeastOnce()).isTetheringSupported(); + verifyNoMoreInteractions(mConnectivityManager); + + ///// + // We do not currently emulate any upstream being found. + // + // This is why there are no calls to verify mNMService.enableNat() or + // mNMService.startInterfaceForwarding(). + ///// + + // Emulate pressing the WiFi tethering button. + mTethering.stopTethering(ConnectivityManager.TETHERING_WIFI); + mLooper.dispatchAll(); + verify(mWifiManager, times(1)).setWifiApEnabled(null, false); + verifyNoMoreInteractions(mWifiManager); + verifyNoMoreInteractions(mConnectivityManager); + verifyNoMoreInteractions(mNMService); + + // Emulate externally-visible WifiManager effects, when tethering mode + // is being torn down. + sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED); + mTethering.interfaceRemoved(mTestIfname); + mLooper.dispatchAll(); + + verify(mNMService, times(1)).untetherInterface(mTestIfname); + // TODO: Why is {g,s}etInterfaceConfig() called more than once? + verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname); + verify(mNMService, atLeastOnce()) + .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class)); + verify(mNMService, times(1)).stopTethering(); + verify(mNMService, times(1)).setIpForwardingEnabled(false); + verifyNoMoreInteractions(mNMService); + // Asking for the last error after the per-interface state machine + // has been reaped yields an unknown interface error. + assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE, + mTethering.getLastTetherError(mTestIfname)); + } + + // TODO: Test that a request for hotspot mode doesn't interface with an + // already operating tethering mode interface. } diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index 32e1b96cf798..caf1a5583a5b 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -32,6 +32,7 @@ import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE; +import static com.android.server.connectivity.tethering.IControlsTethering.STATE_LOCAL_HOTSPOT; import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED; import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE; @@ -80,7 +81,7 @@ public class TetherInterfaceStateMachineTest { private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception { initStateMachine(interfaceType); - dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); if (upstreamIface != null) { dispatchTetherConnectionChanged(upstreamIface); } @@ -138,7 +139,7 @@ public class TetherInterfaceStateMachineTest { public void canBeTethered() throws Exception { initStateMachine(TETHERING_BLUETOOTH); - dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); InOrder inOrder = inOrder(mTetherHelper, mNMService); inOrder.verify(mNMService).tetherInterface(IFACE_NAME); inOrder.verify(mTetherHelper).notifyInterfaceStateChange( @@ -162,7 +163,7 @@ public class TetherInterfaceStateMachineTest { public void canBeTetheredAsUsb() throws Exception { initStateMachine(TETHERING_USB); - dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); InOrder inOrder = inOrder(mTetherHelper, mNMService); inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); @@ -272,7 +273,7 @@ public class TetherInterfaceStateMachineTest { initStateMachine(TETHERING_USB); doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); - dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper); usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); usbTeardownOrder.verify(mNMService).setInterfaceConfig( @@ -310,6 +311,17 @@ public class TetherInterfaceStateMachineTest { * Send a command to the state machine under test, and run the event loop to idle. * * @param command One of the TetherInterfaceStateMachine.CMD_* constants. + * @param obj An additional argument to pass. + */ + private void dispatchCommand(int command, int arg1) { + mTestedSm.sendMessage(command, arg1); + mLooper.dispatchAll(); + } + + /** + * Send a command to the state machine under test, and run the event loop to idle. + * + * @param command One of the TetherInterfaceStateMachine.CMD_* constants. */ private void dispatchCommand(int command) { mTestedSm.sendMessage(command); |