From: markchien Date: Wed, 27 Feb 2019 06:56:11 +0000 (+0800) Subject: Add tethering event callback API X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=26299ed5fd11282c37d5524abacd1901a8f9dfc3;p=android-x86%2Fframeworks-base.git Add tethering event callback API Provide OnTetheringEventCallback for system app to know tethering's upstream. Bug: 125583822 Test: -build, flash, boot -atest FrameworksNetTests Change-Id: I7ca81b27c9b805cc01884509f5b20d9d0a24cd36 Merged-in: I7ca81b27c9b805cc01884509f5b20d9d0a24cd36 --- diff --git a/Android.bp b/Android.bp index b099babcf4fc..312ad6be043c 100644 --- a/Android.bp +++ b/Android.bp @@ -202,6 +202,7 @@ java_defaults { "core/java/android/net/INetworkStatsService.aidl", "core/java/android/net/INetworkStatsSession.aidl", "core/java/android/net/ITestNetworkManager.aidl", + "core/java/android/net/ITetheringEventCallback.aidl", "core/java/android/net/ITetheringStatsProvider.aidl", "core/java/android/net/nsd/INsdManager.aidl", "core/java/android/nfc/IAppCallback.aidl", diff --git a/api/system-current.txt b/api/system-current.txt index 24df5f1ecb16..1d6b595bc966 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3100,11 +3100,13 @@ package android.net { method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); + method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(android.net.Network, android.os.Bundle); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); + method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; field public static final int TETHERING_BLUETOOTH = 2; // 0x2 @@ -3125,6 +3127,11 @@ package android.net { method public void onEntitlementResult(int); } + public abstract static class ConnectivityManager.OnTetheringEventCallback { + ctor public ConnectivityManager.OnTetheringEventCallback(); + method public void onUpstreamChanged(@Nullable android.net.Network); + } + public final class IpPrefix implements android.os.Parcelable { ctor public IpPrefix(java.net.InetAddress, int); ctor public IpPrefix(String); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 524077b02f1a..249357e3633d 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -56,6 +56,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.SparseIntArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.telephony.ITelephony; import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.Preconditions; @@ -2542,6 +2543,94 @@ public class ConnectivityManager { } /** + * Callback for use with {@link registerTetheringEventCallback} to find out tethering + * upstream status. + * + *@hide + */ + @SystemApi + public abstract static class OnTetheringEventCallback { + + /** + * Called when tethering upstream changed. This can be called multiple times and can be + * called any time. + * + * @param network the {@link Network} of tethering upstream. Null means tethering doesn't + * have any upstream. + */ + public void onUpstreamChanged(@Nullable Network network) {} + } + + @GuardedBy("mTetheringEventCallbacks") + private final ArrayMap + mTetheringEventCallbacks = new ArrayMap<>(); + + /** + * Start listening to tethering change events. Any new added callback will receive the last + * tethering status right away. If callback is registered when tethering loses its upstream or + * disabled, {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called + * with a null argument. The same callback object cannot be registered twice. + * + * @param executor the executor on which callback will be invoked. + * @param callback the callback to be called when tethering has change events. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void registerTetheringEventCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull final OnTetheringEventCallback callback) { + Preconditions.checkNotNull(callback, "OnTetheringEventCallback cannot be null."); + + synchronized (mTetheringEventCallbacks) { + Preconditions.checkArgument(!mTetheringEventCallbacks.containsKey(callback), + "callback was already registered."); + ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() { + @Override + public void onUpstreamChanged(Network network) throws RemoteException { + Binder.withCleanCallingIdentity(() -> + executor.execute(() -> { + callback.onUpstreamChanged(network); + })); + } + }; + try { + String pkgName = mContext.getOpPackageName(); + Log.i(TAG, "registerTetheringUpstreamCallback:" + pkgName); + mService.registerTetheringEventCallback(remoteCallback, pkgName); + mTetheringEventCallbacks.put(callback, remoteCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Remove tethering event callback previously registered with + * {@link #registerTetheringEventCallback}. + * + * @param callback previously registered callback. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void unregisterTetheringEventCallback( + @NonNull final OnTetheringEventCallback callback) { + synchronized (mTetheringEventCallbacks) { + ITetheringEventCallback remoteCallback = mTetheringEventCallbacks.remove(callback); + Preconditions.checkNotNull(remoteCallback, "callback was not registered."); + try { + String pkgName = mContext.getOpPackageName(); + Log.i(TAG, "unregisterTetheringEventCallback:" + pkgName); + mService.unregisterTetheringEventCallback(remoteCallback, pkgName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + + /** * Get the list of regular expressions that define any tetherable * USB network interfaces. If USB tethering is not supported by the * device, this list should be empty. diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index ad903d960627..9fc56b4e3d36 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -19,6 +19,7 @@ package android.net; import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.LinkProperties; +import android.net.ITetheringEventCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; @@ -214,4 +215,7 @@ interface IConnectivityManager void getLatestTetheringEntitlementResult(int type, in ResultReceiver receiver, boolean showEntitlementUi, String callerPkg); + + void registerTetheringEventCallback(ITetheringEventCallback callback, String callerPkg); + void unregisterTetheringEventCallback(ITetheringEventCallback callback, String callerPkg); } diff --git a/core/java/android/net/ITetheringEventCallback.aidl b/core/java/android/net/ITetheringEventCallback.aidl new file mode 100644 index 000000000000..d502088542a6 --- /dev/null +++ b/core/java/android/net/ITetheringEventCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.Network; + +/** + * Callback class for receiving tethering changed events + * @hide + */ +oneway interface ITetheringEventCallback +{ + void onUpstreamChanged(in Network network); +} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8390263dbd06..39da90f2bc4a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -71,6 +71,7 @@ import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.ITetheringEventCallback; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.LinkProperties; @@ -3764,6 +3765,22 @@ public class ConnectivityService extends IConnectivityManager.Stub mTethering.getLatestTetheringEntitlementResult(type, receiver, showEntitlementUi); } + /** Register tethering event callback. */ + @Override + public void registerTetheringEventCallback(ITetheringEventCallback callback, + String callerPkg) { + ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg); + mTethering.registerTetheringEventCallback(callback); + } + + /** Unregister tethering event callback. */ + @Override + public void unregisterTetheringEventCallback(ITetheringEventCallback callback, + String callerPkg) { + ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg); + mTethering.unregisterTetheringEventCallback(callback); + } + // Called when we lose the default network and have no replacement yet. // This will automatically be cleared after X seconds or a new default network // becomes CONNECTED, whichever happens first. The timer is started by the diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 3b4b6f85372b..35704d404ff3 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -62,6 +62,7 @@ import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.ITetheringEventCallback; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -82,6 +83,7 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; @@ -184,6 +186,9 @@ public class Tethering extends BaseNetworkObserver { private final VersionedBroadcastListener mDefaultSubscriptionChange; private final TetheringDependencies mDeps; private final EntitlementManager mEntitlementMgr; + private final Handler mHandler; + private final RemoteCallbackList mTetheringEventCallbacks = + new RemoteCallbackList<>(); private volatile TetheringConfiguration mConfig; private InterfaceSet mCurrentUpstreamIfaceSet; @@ -193,6 +198,7 @@ public class Tethering extends BaseNetworkObserver { private boolean mRndisEnabled; // track the RNDIS function enabled state // True iff. WiFi tethering should be started when soft AP is ready. private boolean mWifiTetherRequested; + private Network mTetherUpstream; public Tethering(Context context, INetworkManagementService nmService, INetworkStatsService statsService, INetworkPolicyManager policyManager, @@ -213,9 +219,9 @@ public class Tethering extends BaseNetworkObserver { mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps); mTetherMasterSM.start(); - final Handler smHandler = mTetherMasterSM.getHandler(); - mOffloadController = new OffloadController(smHandler, - mDeps.getOffloadHardwareInterface(smHandler, mLog), + mHandler = mTetherMasterSM.getHandler(); + mOffloadController = new OffloadController(mHandler, + mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(), mNMService, mLog); mUpstreamNetworkMonitor = deps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, @@ -227,7 +233,7 @@ public class Tethering extends BaseNetworkObserver { mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, mLog, systemProperties); mCarrierConfigChange = new VersionedBroadcastListener( - "CarrierConfigChangeListener", mContext, smHandler, filter, + "CarrierConfigChangeListener", mContext, mHandler, filter, (Intent ignored) -> { mLog.log("OBSERVED carrier config change"); updateConfiguration(); @@ -237,7 +243,7 @@ public class Tethering extends BaseNetworkObserver { filter = new IntentFilter(); filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); mDefaultSubscriptionChange = new VersionedBroadcastListener( - "DefaultSubscriptionChangeListener", mContext, smHandler, filter, + "DefaultSubscriptionChangeListener", mContext, mHandler, filter, (Intent ignored) -> { mLog.log("OBSERVED default data subscription change"); updateConfiguration(); @@ -248,14 +254,13 @@ public class Tethering extends BaseNetworkObserver { // Load tethering configuration. updateConfiguration(); - startStateMachineUpdaters(); + startStateMachineUpdaters(mHandler); } - private void startStateMachineUpdaters() { + private void startStateMachineUpdaters(Handler handler) { mCarrierConfigChange.startListening(); mDefaultSubscriptionChange.startListening(); - final Handler handler = mTetherMasterSM.getHandler(); IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(CONNECTIVITY_ACTION); @@ -1229,8 +1234,13 @@ public class Tethering extends BaseNetworkObserver { sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } } - mUpstreamNetworkMonitor.setCurrentUpstream((ns != null) ? ns.network : null); setUpstreamNetwork(ns); + final Network newUpstream = (ns != null) ? ns.network : null; + if (mTetherUpstream != newUpstream) { + mTetherUpstream = newUpstream; + mUpstreamNetworkMonitor.setCurrentUpstream(mTetherUpstream); + reportUpstreamChanged(mTetherUpstream); + } } protected void setUpstreamNetwork(NetworkState ns) { @@ -1413,6 +1423,10 @@ public class Tethering extends BaseNetworkObserver { mUpstreamNetworkMonitor.stop(); notifyDownstreamsOfNewUpstreamIface(null); handleNewUpstreamNetworkState(null); + if (mTetherUpstream != null) { + mTetherUpstream = null; + reportUpstreamChanged(null); + } } private boolean updateUpstreamWanted() { @@ -1684,6 +1698,40 @@ public class Tethering extends BaseNetworkObserver { } } + /** Register tethering event callback */ + public void registerTetheringEventCallback(ITetheringEventCallback callback) { + mHandler.post(() -> { + try { + callback.onUpstreamChanged(mTetherUpstream); + } catch (RemoteException e) { + // Not really very much to do here. + } + mTetheringEventCallbacks.register(callback); + }); + } + + /** Unregister tethering event callback */ + public void unregisterTetheringEventCallback(ITetheringEventCallback callback) { + mHandler.post(() -> { + mTetheringEventCallbacks.unregister(callback); + }); + } + + private void reportUpstreamChanged(Network network) { + final int length = mTetheringEventCallbacks.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + try { + mTetheringEventCallbacks.getBroadcastItem(i).onUpstreamChanged(network); + } catch (RemoteException e) { + // Not really very much to do here. + } + } + } finally { + mTetheringEventCallbacks.finishBroadcast(); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { // Binder.java closes the resource for us. diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index a12b0a0fbcf0..fdba72333f0b 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -68,6 +68,7 @@ import android.hardware.usb.UsbManager; import android.net.INetd; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.ITetheringEventCallback; import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; @@ -123,6 +124,7 @@ import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.net.Inet6Address; import java.util.ArrayList; +import java.util.Arrays; import java.util.Vector; @RunWith(AndroidJUnit4.class) @@ -918,6 +920,67 @@ public class TetheringTest { expectedInteractionsWithShowNotification); } + private class TestTetheringEventCallback extends ITetheringEventCallback.Stub { + private final ArrayList mActualUpstreams = new ArrayList<>(); + + public void expectUpstreamChanged(Network... networks) { + final ArrayList expectedUpstreams = + new ArrayList(Arrays.asList(networks)); + for (Network upstream : expectedUpstreams) { + // throws OOB if no expectations + assertEquals(mActualUpstreams.remove(0), upstream); + } + assertNoCallback(); + } + + @Override + public void onUpstreamChanged(Network network) { + mActualUpstreams.add(network); + } + + public void assertNoCallback() { + assertTrue(mActualUpstreams.isEmpty()); + } + } + + @Test + public void testRegisterTetheringEventCallback() throws Exception { + TestTetheringEventCallback callback1 = new TestTetheringEventCallback(); + TestTetheringEventCallback callback2 = new TestTetheringEventCallback(); + + // 1. Register one callback and run usb tethering. + mTethering.registerTetheringEventCallback(callback1); + mLooper.dispatchAll(); + callback1.expectUpstreamChanged(new Network[] {null}); + NetworkState upstreamState = buildMobileDualStackUpstreamState(); + runUsbTethering(upstreamState); + callback1.expectUpstreamChanged(upstreamState.network); + // 2. Register second callback. + mTethering.registerTetheringEventCallback(callback2); + mLooper.dispatchAll(); + callback2.expectUpstreamChanged(upstreamState.network); + // 3. Disable usb tethering. + mTethering.stopTethering(TETHERING_USB); + mLooper.dispatchAll(); + sendUsbBroadcast(false, false, false); + mLooper.dispatchAll(); + callback1.expectUpstreamChanged(new Network[] {null}); + callback2.expectUpstreamChanged(new Network[] {null}); + // 4. Unregister first callback and run hotspot. + mTethering.unregisterTetheringEventCallback(callback1); + mLooper.dispatchAll(); + when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState); + when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) + .thenReturn(upstreamState); + when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true); + mTethering.startTethering(TETHERING_WIFI, null, false); + mLooper.dispatchAll(); + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + mLooper.dispatchAll(); + callback1.assertNoCallback(); + callback2.expectUpstreamChanged(upstreamState.network); + } // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface.