diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 524077b02f..249357e363 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; @@ -2541,6 +2542,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 diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index ad903d9606..9fc56b4e3d 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/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 8390263dbd..39da90f2bc 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