Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a930eb9 authored by Mark Chien's avatar Mark Chien Committed by Automerger Merge Worker
Browse files

Merge "Add new callback to notify Tethering when PAN is ready" am: 3d5958e9

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/1882486

Change-Id: I67df766972f5eaf8865a4f52d4bd7fda1f3104c4
parents 2dacaa0d 3d5958e9
Loading
Loading
Loading
Loading
+64 −104
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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;

/**
@@ -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;
@@ -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();
    }
@@ -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);
@@ -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;

@@ -151,6 +169,7 @@ public class PanService extends ProfileService {
    @Override
    protected boolean stop() {
        mAdapterService = null;
        mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback);
        mHandler.removeCallbacksAndMessages(null);
        return true;
    }
@@ -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);
@@ -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);
@@ -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);
        }
@@ -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;
@@ -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) {
@@ -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>();

+116 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

/**
@@ -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;
@@ -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
@@ -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);
@@ -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;
    }

    /**
+20 −2
Original line number Diff line number Diff line
@@ -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;

@@ -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.
@@ -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());

@@ -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());

+2 −1
Original line number Diff line number Diff line
@@ -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;

@@ -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)")