Loading android/app/src/com/android/bluetooth/pan/PanService.java +64 −104 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.TETHER_PRIVILEGED; import android.annotation.RequiresPermission; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothPan.LocalPanRole; Loading @@ -28,19 +27,15 @@ import android.bluetooth.BluetoothPan.RemotePanRole; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothPan; import android.content.AttributionSource; import android.content.Context; import android.content.Intent; import android.content.res.Resources.NotFoundException; import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.ITetheredInterfaceCallback; import android.net.TetheringInterface; import android.net.TetheringManager; import android.os.Handler; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.HandlerExecutor; import android.os.Message; import android.os.ServiceManager; import android.os.RemoteException; import android.os.UserManager; import android.util.Log; Loading @@ -51,12 +46,13 @@ import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.modules.utils.SynchronousResultReceiver; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** Loading @@ -74,12 +70,13 @@ public class PanService extends ProfileService { private static final int BLUETOOTH_PREFIX_LENGTH = 24; private HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices; private ArrayList<String> mBluetoothIfaceAddresses; private int mMaxPanDevices; private String mPanIfName; private String mNapIfaceAddr; private boolean mIsTethering = false; private boolean mNativeAvailable; private List<ITetheredInterfaceCallback> mBluetoothTetheringCallbacks; private TetheringManager mTetheringManager; private DatabaseManager mDatabaseManager; @VisibleForTesting UserManager mUserManager; Loading @@ -94,6 +91,24 @@ public class PanService extends ProfileService { private AdapterService mAdapterService; TetheringManager.TetheringEventCallback mTetheringCallback = new TetheringManager.TetheringEventCallback() { @Override public void onError(TetheringInterface iface, int error) { if (mIsTethering && iface.getType() == TetheringManager.TETHERING_BLUETOOTH) { // tethering is fail because of @TetheringIfaceError error. Log.e(TAG, "Error setting up tether interface: " + error); for (Map.Entry device : mPanDevices.entrySet()) { disconnectPanNative(Utils.getByteAddress( (BluetoothDevice) device.getKey())); } mPanDevices.clear(); mIsTethering = false; } } }; static { classInitNative(); } Loading Loading @@ -129,8 +144,8 @@ public class PanService extends ProfileService { mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(), "DatabaseManager cannot be null when PanService starts"); mBluetoothTetheringCallbacks = new ArrayList<>(); mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>(); mBluetoothIfaceAddresses = new ArrayList<String>(); try { mMaxPanDevices = getResources().getInteger( com.android.internal.R.integer.config_max_pan_devices); Loading @@ -142,6 +157,9 @@ public class PanService extends ProfileService { mUserManager = getSystemService(UserManager.class); mTetheringManager = getSystemService(TetheringManager.class); mTetheringManager.registerTetheringEventCallback( new HandlerExecutor(BackgroundThread.getHandler()), mTetheringCallback); setPanService(this); mStarted = true; Loading @@ -151,6 +169,7 @@ public class PanService extends ProfileService { @Override protected boolean stop() { mAdapterService = null; mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback); mHandler.removeCallbacksAndMessages(null); return true; } Loading Loading @@ -337,7 +356,8 @@ public class PanService extends ProfileService { } @Override public void setBluetoothTethering(boolean value, AttributionSource source, public void setBluetoothTethering(ITetheredInterfaceCallback callback, boolean value, AttributionSource source, SynchronousResultReceiver receiver) { try { PanService service = getService(source); Loading @@ -345,7 +365,7 @@ public class PanService extends ProfileService { Log.d(TAG, "setBluetoothTethering: " + value + ", pkgName: " + source.getPackageName() + ", mTetherOn: " + service.mTetherOn); service.setBluetoothTethering(value, source.getPackageName(), service.setBluetoothTethering(callback, value, source.getPackageName(), source.getAttributionTag()); } receiver.send(null); Loading Loading @@ -426,8 +446,8 @@ public class PanService extends ProfileService { android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) void setBluetoothTethering(boolean value, final String pkgName, final String callingAttributionTag) { void setBluetoothTethering(ITetheredInterfaceCallback callback, boolean value, final String pkgName, final String callingAttributionTag) { if (DBG) { Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + mTetherOn); } Loading @@ -440,6 +460,16 @@ public class PanService extends ProfileService { if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING) && value) { throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user."); } if (callback != null) { if (value) { mBluetoothTetheringCallbacks.add(callback); } else { mBluetoothTetheringCallbacks.remove(callback); } } else if (mBluetoothTetheringCallbacks.isEmpty()) { Log.e(TAG, "setBluetoothTethering: " + value + ", Error: no callbacks registered."); return; } if (mTetherOn != value) { //drop any existing panu or pan-nap connection when changing the tethering state mTetherOn = value; Loading Loading @@ -635,22 +665,29 @@ public class PanService extends ProfileService { return; } Log.d(TAG, "handlePanDeviceStateChange LOCAL_NAP_ROLE:REMOTE_PANU_ROLE"); if (mNapIfaceAddr == null) { mNapIfaceAddr = startTethering(iface); if (mNapIfaceAddr == null) { Log.e(TAG, "Error seting up tether interface"); mPanDevices.remove(device); disconnectPanNative(Utils.getByteAddress(device)); return; if (!mIsTethering) { mIsTethering = true; try { for (ITetheredInterfaceCallback cb : mBluetoothTetheringCallbacks) { cb.onAvailable(iface); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } else if (state == BluetoothProfile.STATE_DISCONNECTED) { mPanDevices.remove(device); Log.i(TAG, "remote(PANU) is disconnected, Remaining connected PANU devices: " + mPanDevices.size()); if (mNapIfaceAddr != null && mPanDevices.size() == 0) { stopTethering(iface); mNapIfaceAddr = null; if (mIsTethering && mPanDevices.size() == 0) { try { for (ITetheredInterfaceCallback cb : mBluetoothTetheringCallbacks) { cb.onUnavailable(); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mIsTethering = false; } } } else if (mStarted) { Loading Loading @@ -686,83 +723,6 @@ public class PanService extends ProfileService { sendBroadcast(intent, BLUETOOTH_CONNECT); } private String startTethering(String iface) { return configureBtIface(true, iface); } private String stopTethering(String iface) { return configureBtIface(false, iface); } private String configureBtIface(boolean enable, String iface) { Log.i(TAG, "configureBtIface: " + iface + " enable: " + enable); IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); TetheringManager tm = getBaseContext().getSystemService(TetheringManager.class); String[] bluetoothRegexs = tm.getTetherableBluetoothRegexs(); // bring toggle the interfaces String[] currentIfaces = new String[0]; try { currentIfaces = service.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return null; } boolean found = false; for (String currIface : currentIfaces) { if (currIface.equals(iface)) { found = true; break; } } if (!found) { return null; } InterfaceConfiguration ifcg = null; String address = null; try { ifcg = service.getInterfaceConfig(iface); if (ifcg != null) { InetAddress addr = null; LinkAddress linkAddr = ifcg.getLinkAddress(); if (linkAddr == null || (addr = linkAddr.getAddress()) == null || addr.equals( InetAddresses.parseNumericAddress("0.0.0.0")) || addr.equals( InetAddresses.parseNumericAddress("::0"))) { address = BLUETOOTH_IFACE_ADDR_START; addr = InetAddresses.parseNumericAddress(address); } ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH)); if (enable) { ifcg.setInterfaceUp(); } else { ifcg.setInterfaceDown(); } service.setInterfaceConfig(iface, ifcg); if (enable) { int tetherStatus = tm.tether(iface); if (tetherStatus != ConnectivityManager.TETHER_ERROR_NO_ERROR) { Log.e(TAG, "Error tethering " + iface + " tetherStatus: " + tetherStatus); return null; } } else { int untetherStatus = tm.untether(iface); Log.i(TAG, "Untethered: " + iface + " untetherStatus: " + untetherStatus); } } } catch (Exception e) { Log.e(TAG, "Error configuring interface " + iface + ", :" + e); return null; } return address; } private List<BluetoothDevice> getConnectedPanDevices() { List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); Loading framework/java/android/bluetooth/BluetoothPan.java +116 −2 Original line number Diff line number Diff line Loading @@ -16,10 +16,12 @@ package android.bluetooth; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.bluetooth.BluetoothUtils.getSyncTimeout; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; Loading @@ -30,6 +32,9 @@ import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; import android.content.Context; import android.net.ITetheredInterfaceCallback; import android.net.TetheringManager.TetheredInterfaceCallback; import android.net.TetheringManager.TetheredInterfaceRequest; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; Loading @@ -41,6 +46,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; /** Loading Loading @@ -183,6 +190,49 @@ public final class BluetoothPan implements BluetoothProfile { */ public static final int PAN_OPERATION_SUCCESS = 1004; /** * Request class used by Tethering to notify that the interface is closed. * * @see #requestTetheredInterface * @hide */ public class BluetoothTetheredInterfaceRequest implements TetheredInterfaceRequest { private IBluetoothPan mService; private ITetheredInterfaceCallback mCb; private BluetoothTetheredInterfaceRequest(@NonNull IBluetoothPan service, @NonNull ITetheredInterfaceCallback cb) { this.mService = service; this.mCb = cb; } /** * Called when the Tethering interface has been released. */ @RequiresPermission(allOf = { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) @Override public void release() { if (mService == null || mCb == null) { throw new IllegalStateException( "The tethered interface has already been released."); } try { final SynchronousResultReceiver recv = new SynchronousResultReceiver(); mService.setBluetoothTethering(mCb, false, mAttributionSource, recv); recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); } catch (RemoteException | TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } finally { mService = null; mCb = null; } } } private final Context mContext; private final BluetoothAdapter mAdapter; Loading Loading @@ -450,9 +500,12 @@ public final class BluetoothPan implements BluetoothProfile { } /** * Turns on/off bluetooth tethering * Turns on/off bluetooth tethering. * * @param value is whether to enable or disable bluetooth tethering * * @deprecated Use {@link #requestTetheredInterface} with * {@link TetheredInterfaceCallback} instead. * @hide */ @SystemApi Loading @@ -462,6 +515,7 @@ public final class BluetoothPan implements BluetoothProfile { android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) @Deprecated public void setBluetoothTethering(boolean value) { String pkgName = mContext.getOpPackageName(); if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); Loading @@ -472,12 +526,72 @@ public final class BluetoothPan implements BluetoothProfile { } else if (isEnabled()) { try { final SynchronousResultReceiver recv = new SynchronousResultReceiver(); service.setBluetoothTethering(value, mAttributionSource, recv); service.setBluetoothTethering(null, value, mAttributionSource, recv); recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); } catch (RemoteException | TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } } } /** * Turns on Bluetooth tethering. * * <p>When one or more devices are connected, the PAN service will trigger * {@link TetheredInterfaceCallback#onAvailable} to inform the caller that * it is ready to tether. On the contrary, when all devices have been disconnected, * the PAN service will trigger {@link TetheredInterfaceCallback#onUnavailable}. * <p>To turn off Bluetooth tethering, the caller must use * {@link TetheredInterfaceRequest#release} method. * * @param executor thread to execute callback methods * @param callback is the tethering callback to indicate PAN service is ready * or not to tether to one or more devices * * @return new instance of {@link TetheredInterfaceRequest} which can be * used to turn off Bluetooth tethering or {@code null} if service * is not enabled * @hide */ @SystemApi(client = MODULE_LIBRARIES) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) @Nullable public TetheredInterfaceRequest requestTetheredInterface( @NonNull final Executor executor, @NonNull final TetheredInterfaceCallback callback) { Objects.requireNonNull(callback, "Callback must be non-null"); Objects.requireNonNull(executor, "Executor must be non-null"); final IBluetoothPan service = getService(); if (service == null) { Log.w(TAG, "Proxy not attached to service"); if (DBG) log(Log.getStackTraceString(new Throwable())); } else if (isEnabled()) { final ITetheredInterfaceCallback cbInternal = new ITetheredInterfaceCallback.Stub() { @Override public void onAvailable(String iface) { executor.execute(() -> callback.onAvailable(iface)); } @Override public void onUnavailable() { executor.execute(() -> callback.onUnavailable()); } }; try { final SynchronousResultReceiver recv = new SynchronousResultReceiver(); service.setBluetoothTethering(cbInternal, true, mAttributionSource, recv); recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); return new BluetoothTetheredInterfaceRequest(service, cbInternal); } catch (RemoteException | TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } } return null; } /** Loading framework/tests/src/android/bluetooth/BluetoothTestUtils.java +20 −2 Original line number Diff line number Diff line Loading @@ -23,6 +23,9 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.net.TetheringManager; import android.net.TetheringManager.TetheredInterfaceCallback; import android.net.TetheringManager.TetheredInterfaceRequest; import android.os.Environment; import android.util.Log; Loading Loading @@ -407,6 +410,8 @@ public class BluetoothTestUtils extends Assert { private BluetoothPan mPan = null; private BluetoothMapClient mMce = null; private String mMsgHandle = null; private TetheredInterfaceCallback mPanCallback = null; private TetheredInterfaceRequest mBluetoothIfaceRequest; /** * Creates a utility instance for testing Bluetooth. Loading Loading @@ -740,7 +745,17 @@ public class BluetoothTestUtils extends Assert { assertNotNull(mPan); long start = System.currentTimeMillis(); mPan.setBluetoothTethering(true); mPanCallback = new TetheringManager.TetheredInterfaceCallback() { @Override public void onAvailable(String iface) { } @Override public void onUnavailable() { } }; mBluetoothIfaceRequest = mPan.requestTetheredInterface(mContext.getMainExecutor(), mPanCallback); long stop = System.currentTimeMillis(); assertTrue(mPan.isTetheringOn()); Loading @@ -758,7 +773,10 @@ public class BluetoothTestUtils extends Assert { assertNotNull(mPan); long start = System.currentTimeMillis(); mPan.setBluetoothTethering(false); if (mBluetoothIfaceRequest != null) { mBluetoothIfaceRequest.release(); mBluetoothIfaceRequest = null; } long stop = System.currentTimeMillis(); assertFalse(mPan.isTetheringOn()); Loading system/binder/android/bluetooth/IBluetoothPan.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; import android.content.AttributionSource; import android.net.ITetheredInterfaceCallback; import com.android.modules.utils.SynchronousResultReceiver; Loading @@ -31,7 +32,7 @@ oneway interface IBluetoothPan { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void isTetheringOn(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED,android.Manifest.permission.TETHER_PRIVILEGED})") void setBluetoothTethering(boolean value, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); void setBluetoothTethering(ITetheredInterfaceCallback callback, boolean value, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void connect(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") Loading Loading
android/app/src/com/android/bluetooth/pan/PanService.java +64 −104 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.TETHER_PRIVILEGED; import android.annotation.RequiresPermission; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothPan.LocalPanRole; Loading @@ -28,19 +27,15 @@ import android.bluetooth.BluetoothPan.RemotePanRole; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothPan; import android.content.AttributionSource; import android.content.Context; import android.content.Intent; import android.content.res.Resources.NotFoundException; import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.ITetheredInterfaceCallback; import android.net.TetheringInterface; import android.net.TetheringManager; import android.os.Handler; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.HandlerExecutor; import android.os.Message; import android.os.ServiceManager; import android.os.RemoteException; import android.os.UserManager; import android.util.Log; Loading @@ -51,12 +46,13 @@ import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.modules.utils.SynchronousResultReceiver; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** Loading @@ -74,12 +70,13 @@ public class PanService extends ProfileService { private static final int BLUETOOTH_PREFIX_LENGTH = 24; private HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices; private ArrayList<String> mBluetoothIfaceAddresses; private int mMaxPanDevices; private String mPanIfName; private String mNapIfaceAddr; private boolean mIsTethering = false; private boolean mNativeAvailable; private List<ITetheredInterfaceCallback> mBluetoothTetheringCallbacks; private TetheringManager mTetheringManager; private DatabaseManager mDatabaseManager; @VisibleForTesting UserManager mUserManager; Loading @@ -94,6 +91,24 @@ public class PanService extends ProfileService { private AdapterService mAdapterService; TetheringManager.TetheringEventCallback mTetheringCallback = new TetheringManager.TetheringEventCallback() { @Override public void onError(TetheringInterface iface, int error) { if (mIsTethering && iface.getType() == TetheringManager.TETHERING_BLUETOOTH) { // tethering is fail because of @TetheringIfaceError error. Log.e(TAG, "Error setting up tether interface: " + error); for (Map.Entry device : mPanDevices.entrySet()) { disconnectPanNative(Utils.getByteAddress( (BluetoothDevice) device.getKey())); } mPanDevices.clear(); mIsTethering = false; } } }; static { classInitNative(); } Loading Loading @@ -129,8 +144,8 @@ public class PanService extends ProfileService { mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(), "DatabaseManager cannot be null when PanService starts"); mBluetoothTetheringCallbacks = new ArrayList<>(); mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>(); mBluetoothIfaceAddresses = new ArrayList<String>(); try { mMaxPanDevices = getResources().getInteger( com.android.internal.R.integer.config_max_pan_devices); Loading @@ -142,6 +157,9 @@ public class PanService extends ProfileService { mUserManager = getSystemService(UserManager.class); mTetheringManager = getSystemService(TetheringManager.class); mTetheringManager.registerTetheringEventCallback( new HandlerExecutor(BackgroundThread.getHandler()), mTetheringCallback); setPanService(this); mStarted = true; Loading @@ -151,6 +169,7 @@ public class PanService extends ProfileService { @Override protected boolean stop() { mAdapterService = null; mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback); mHandler.removeCallbacksAndMessages(null); return true; } Loading Loading @@ -337,7 +356,8 @@ public class PanService extends ProfileService { } @Override public void setBluetoothTethering(boolean value, AttributionSource source, public void setBluetoothTethering(ITetheredInterfaceCallback callback, boolean value, AttributionSource source, SynchronousResultReceiver receiver) { try { PanService service = getService(source); Loading @@ -345,7 +365,7 @@ public class PanService extends ProfileService { Log.d(TAG, "setBluetoothTethering: " + value + ", pkgName: " + source.getPackageName() + ", mTetherOn: " + service.mTetherOn); service.setBluetoothTethering(value, source.getPackageName(), service.setBluetoothTethering(callback, value, source.getPackageName(), source.getAttributionTag()); } receiver.send(null); Loading Loading @@ -426,8 +446,8 @@ public class PanService extends ProfileService { android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) void setBluetoothTethering(boolean value, final String pkgName, final String callingAttributionTag) { void setBluetoothTethering(ITetheredInterfaceCallback callback, boolean value, final String pkgName, final String callingAttributionTag) { if (DBG) { Log.d(TAG, "setBluetoothTethering: " + value + ", mTetherOn: " + mTetherOn); } Loading @@ -440,6 +460,16 @@ public class PanService extends ProfileService { if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING) && value) { throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user."); } if (callback != null) { if (value) { mBluetoothTetheringCallbacks.add(callback); } else { mBluetoothTetheringCallbacks.remove(callback); } } else if (mBluetoothTetheringCallbacks.isEmpty()) { Log.e(TAG, "setBluetoothTethering: " + value + ", Error: no callbacks registered."); return; } if (mTetherOn != value) { //drop any existing panu or pan-nap connection when changing the tethering state mTetherOn = value; Loading Loading @@ -635,22 +665,29 @@ public class PanService extends ProfileService { return; } Log.d(TAG, "handlePanDeviceStateChange LOCAL_NAP_ROLE:REMOTE_PANU_ROLE"); if (mNapIfaceAddr == null) { mNapIfaceAddr = startTethering(iface); if (mNapIfaceAddr == null) { Log.e(TAG, "Error seting up tether interface"); mPanDevices.remove(device); disconnectPanNative(Utils.getByteAddress(device)); return; if (!mIsTethering) { mIsTethering = true; try { for (ITetheredInterfaceCallback cb : mBluetoothTetheringCallbacks) { cb.onAvailable(iface); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } else if (state == BluetoothProfile.STATE_DISCONNECTED) { mPanDevices.remove(device); Log.i(TAG, "remote(PANU) is disconnected, Remaining connected PANU devices: " + mPanDevices.size()); if (mNapIfaceAddr != null && mPanDevices.size() == 0) { stopTethering(iface); mNapIfaceAddr = null; if (mIsTethering && mPanDevices.size() == 0) { try { for (ITetheredInterfaceCallback cb : mBluetoothTetheringCallbacks) { cb.onUnavailable(); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mIsTethering = false; } } } else if (mStarted) { Loading Loading @@ -686,83 +723,6 @@ public class PanService extends ProfileService { sendBroadcast(intent, BLUETOOTH_CONNECT); } private String startTethering(String iface) { return configureBtIface(true, iface); } private String stopTethering(String iface) { return configureBtIface(false, iface); } private String configureBtIface(boolean enable, String iface) { Log.i(TAG, "configureBtIface: " + iface + " enable: " + enable); IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); TetheringManager tm = getBaseContext().getSystemService(TetheringManager.class); String[] bluetoothRegexs = tm.getTetherableBluetoothRegexs(); // bring toggle the interfaces String[] currentIfaces = new String[0]; try { currentIfaces = service.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return null; } boolean found = false; for (String currIface : currentIfaces) { if (currIface.equals(iface)) { found = true; break; } } if (!found) { return null; } InterfaceConfiguration ifcg = null; String address = null; try { ifcg = service.getInterfaceConfig(iface); if (ifcg != null) { InetAddress addr = null; LinkAddress linkAddr = ifcg.getLinkAddress(); if (linkAddr == null || (addr = linkAddr.getAddress()) == null || addr.equals( InetAddresses.parseNumericAddress("0.0.0.0")) || addr.equals( InetAddresses.parseNumericAddress("::0"))) { address = BLUETOOTH_IFACE_ADDR_START; addr = InetAddresses.parseNumericAddress(address); } ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH)); if (enable) { ifcg.setInterfaceUp(); } else { ifcg.setInterfaceDown(); } service.setInterfaceConfig(iface, ifcg); if (enable) { int tetherStatus = tm.tether(iface); if (tetherStatus != ConnectivityManager.TETHER_ERROR_NO_ERROR) { Log.e(TAG, "Error tethering " + iface + " tetherStatus: " + tetherStatus); return null; } } else { int untetherStatus = tm.untether(iface); Log.i(TAG, "Untethered: " + iface + " untetherStatus: " + untetherStatus); } } } catch (Exception e) { Log.e(TAG, "Error configuring interface " + iface + ", :" + e); return null; } return address; } private List<BluetoothDevice> getConnectedPanDevices() { List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); Loading
framework/java/android/bluetooth/BluetoothPan.java +116 −2 Original line number Diff line number Diff line Loading @@ -16,10 +16,12 @@ package android.bluetooth; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.bluetooth.BluetoothUtils.getSyncTimeout; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; Loading @@ -30,6 +32,9 @@ import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; import android.content.Context; import android.net.ITetheredInterfaceCallback; import android.net.TetheringManager.TetheredInterfaceCallback; import android.net.TetheringManager.TetheredInterfaceRequest; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; Loading @@ -41,6 +46,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; /** Loading Loading @@ -183,6 +190,49 @@ public final class BluetoothPan implements BluetoothProfile { */ public static final int PAN_OPERATION_SUCCESS = 1004; /** * Request class used by Tethering to notify that the interface is closed. * * @see #requestTetheredInterface * @hide */ public class BluetoothTetheredInterfaceRequest implements TetheredInterfaceRequest { private IBluetoothPan mService; private ITetheredInterfaceCallback mCb; private BluetoothTetheredInterfaceRequest(@NonNull IBluetoothPan service, @NonNull ITetheredInterfaceCallback cb) { this.mService = service; this.mCb = cb; } /** * Called when the Tethering interface has been released. */ @RequiresPermission(allOf = { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) @Override public void release() { if (mService == null || mCb == null) { throw new IllegalStateException( "The tethered interface has already been released."); } try { final SynchronousResultReceiver recv = new SynchronousResultReceiver(); mService.setBluetoothTethering(mCb, false, mAttributionSource, recv); recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); } catch (RemoteException | TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } finally { mService = null; mCb = null; } } } private final Context mContext; private final BluetoothAdapter mAdapter; Loading Loading @@ -450,9 +500,12 @@ public final class BluetoothPan implements BluetoothProfile { } /** * Turns on/off bluetooth tethering * Turns on/off bluetooth tethering. * * @param value is whether to enable or disable bluetooth tethering * * @deprecated Use {@link #requestTetheredInterface} with * {@link TetheredInterfaceCallback} instead. * @hide */ @SystemApi Loading @@ -462,6 +515,7 @@ public final class BluetoothPan implements BluetoothProfile { android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) @Deprecated public void setBluetoothTethering(boolean value) { String pkgName = mContext.getOpPackageName(); if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); Loading @@ -472,12 +526,72 @@ public final class BluetoothPan implements BluetoothProfile { } else if (isEnabled()) { try { final SynchronousResultReceiver recv = new SynchronousResultReceiver(); service.setBluetoothTethering(value, mAttributionSource, recv); service.setBluetoothTethering(null, value, mAttributionSource, recv); recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); } catch (RemoteException | TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } } } /** * Turns on Bluetooth tethering. * * <p>When one or more devices are connected, the PAN service will trigger * {@link TetheredInterfaceCallback#onAvailable} to inform the caller that * it is ready to tether. On the contrary, when all devices have been disconnected, * the PAN service will trigger {@link TetheredInterfaceCallback#onUnavailable}. * <p>To turn off Bluetooth tethering, the caller must use * {@link TetheredInterfaceRequest#release} method. * * @param executor thread to execute callback methods * @param callback is the tethering callback to indicate PAN service is ready * or not to tether to one or more devices * * @return new instance of {@link TetheredInterfaceRequest} which can be * used to turn off Bluetooth tethering or {@code null} if service * is not enabled * @hide */ @SystemApi(client = MODULE_LIBRARIES) @RequiresBluetoothConnectPermission @RequiresPermission(allOf = { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED, }) @Nullable public TetheredInterfaceRequest requestTetheredInterface( @NonNull final Executor executor, @NonNull final TetheredInterfaceCallback callback) { Objects.requireNonNull(callback, "Callback must be non-null"); Objects.requireNonNull(executor, "Executor must be non-null"); final IBluetoothPan service = getService(); if (service == null) { Log.w(TAG, "Proxy not attached to service"); if (DBG) log(Log.getStackTraceString(new Throwable())); } else if (isEnabled()) { final ITetheredInterfaceCallback cbInternal = new ITetheredInterfaceCallback.Stub() { @Override public void onAvailable(String iface) { executor.execute(() -> callback.onAvailable(iface)); } @Override public void onUnavailable() { executor.execute(() -> callback.onUnavailable()); } }; try { final SynchronousResultReceiver recv = new SynchronousResultReceiver(); service.setBluetoothTethering(cbInternal, true, mAttributionSource, recv); recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); return new BluetoothTetheredInterfaceRequest(service, cbInternal); } catch (RemoteException | TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } } return null; } /** Loading
framework/tests/src/android/bluetooth/BluetoothTestUtils.java +20 −2 Original line number Diff line number Diff line Loading @@ -23,6 +23,9 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.net.TetheringManager; import android.net.TetheringManager.TetheredInterfaceCallback; import android.net.TetheringManager.TetheredInterfaceRequest; import android.os.Environment; import android.util.Log; Loading Loading @@ -407,6 +410,8 @@ public class BluetoothTestUtils extends Assert { private BluetoothPan mPan = null; private BluetoothMapClient mMce = null; private String mMsgHandle = null; private TetheredInterfaceCallback mPanCallback = null; private TetheredInterfaceRequest mBluetoothIfaceRequest; /** * Creates a utility instance for testing Bluetooth. Loading Loading @@ -740,7 +745,17 @@ public class BluetoothTestUtils extends Assert { assertNotNull(mPan); long start = System.currentTimeMillis(); mPan.setBluetoothTethering(true); mPanCallback = new TetheringManager.TetheredInterfaceCallback() { @Override public void onAvailable(String iface) { } @Override public void onUnavailable() { } }; mBluetoothIfaceRequest = mPan.requestTetheredInterface(mContext.getMainExecutor(), mPanCallback); long stop = System.currentTimeMillis(); assertTrue(mPan.isTetheringOn()); Loading @@ -758,7 +773,10 @@ public class BluetoothTestUtils extends Assert { assertNotNull(mPan); long start = System.currentTimeMillis(); mPan.setBluetoothTethering(false); if (mBluetoothIfaceRequest != null) { mBluetoothIfaceRequest.release(); mBluetoothIfaceRequest = null; } long stop = System.currentTimeMillis(); assertFalse(mPan.isTetheringOn()); Loading
system/binder/android/bluetooth/IBluetoothPan.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.bluetooth; import android.bluetooth.BluetoothDevice; import android.content.AttributionSource; import android.net.ITetheredInterfaceCallback; import com.android.modules.utils.SynchronousResultReceiver; Loading @@ -31,7 +32,7 @@ oneway interface IBluetoothPan { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") void isTetheringOn(in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED,android.Manifest.permission.TETHER_PRIVILEGED})") void setBluetoothTethering(boolean value, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); void setBluetoothTethering(ITetheredInterfaceCallback callback, boolean value, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void connect(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") Loading