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

Commit 7d76a0a2 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Long-tail of AttributionSource plumbing.

Wires up AttributionSource across the remaining long-tail of
Bluetooth AIDL interfaces, ensuring that developers can accurately
make calls chained back to a specific Context.

Moves "for data delivery" permission checks to happen in a single
location on each interface to ensure they're performed consistently
with the new AttributionSource arguments.  Note that "for data
delivery" isn't the best name; it's designed to represent that the
requested action was performed and should result in the relevant
appop being noted for the caller.

This change has the positive side effect of ensuring that all
interfaces are consistently enforcing the BLUETOOTH_CONNECT
permission, even in the case where BLUETOOTH_PRIVILEGED is also
required; this is what ensures that revoking the "Nearby devices"
permission takes effect for all callers.

Additionally, standardizing on enforcing permissions closer to the
AIDL entry point reduces the need for @RequiresPermission annotations
to be carried around inside the Bluetooth stack.

Bug: 183626112
Test: atest BluetoothInstrumentationTests
Change-Id: I389e9938c0f0e6f5fa686182ff913fcfa66d1d4b
parent de3b1db4
Loading
Loading
Loading
Loading
+68 −21
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -49,12 +50,15 @@ import android.os.Binder;
import android.os.Build;
import android.os.ParcelUuid;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Telephony;
import android.util.Log;

import com.android.bluetooth.btservice.ProfileService;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

@@ -392,6 +396,20 @@ public final class Utils {
                "Need DUMP permission");
    }

    public static AttributionSource getCallingAttributionSource() {
        int callingUid = Binder.getCallingUid();
        if (callingUid == android.os.Process.ROOT_UID) {
            callingUid = android.os.Process.SYSTEM_UID;
        }
        try {
            return new AttributionSource(callingUid,
                    AppGlobals.getPackageManager().getPackagesForUid(callingUid)[0], null);
        } catch (RemoteException e) {
            throw new IllegalStateException("Failed to resolve AttributionSource", e);
        }
    }

    @SuppressLint("AndroidFrameworkRequiresPermission")
    private static boolean checkPermissionForPreflight(Context context, String permission) {
        final int result = PermissionChecker.checkCallingOrSelfPermissionForPreflight(
                context, permission);
@@ -408,6 +426,7 @@ public final class Utils {
        }
    }

    @SuppressLint("AndroidFrameworkRequiresPermission")
    private static boolean checkPermissionForDataDelivery(Context context, String permission,
            AttributionSource attributionSource, String message) {
        final int result = PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
@@ -434,6 +453,7 @@ public final class Utils {
     *
     * <p>Should be used in situations where the app op should not be noted.
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    public static boolean checkConnectPermissionForPreflight(Context context) {
        return checkPermissionForPreflight(context, BLUETOOTH_CONNECT);
@@ -447,6 +467,7 @@ public final class Utils {
     * <p>Should be used in situations where data will be delivered and hence the app op should
     * be noted.
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    public static boolean checkConnectPermissionForDataDelivery(
            Context context, AttributionSource attributionSource, String message) {
@@ -460,6 +481,7 @@ public final class Utils {
     *
     * <p>Should be used in situations where the app op should not be noted.
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
    public static boolean checkScanPermissionForPreflight(Context context) {
        return checkPermissionForPreflight(context, BLUETOOTH_SCAN);
@@ -472,6 +494,7 @@ public final class Utils {
     * <p>Should be used in situations where data will be delivered and hence the app op should
     * be noted.
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
    public static boolean checkScanPermissionForDataDelivery(
            Context context, AttributionSource attributionSource, String message) {
@@ -486,6 +509,7 @@ public final class Utils {
     * <p>
     * Should be used in situations where the app op should not be noted.
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
    public static boolean checkAdvertisePermissionForPreflight(Context context) {
        return checkPermissionForPreflight(context, BLUETOOTH_ADVERTISE);
@@ -499,6 +523,7 @@ public final class Utils {
     * Should be used in situations where data will be delivered and hence the
     * app op should be noted.
     */
    @SuppressLint("AndroidFrameworkRequiresPermission")
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
    public static boolean checkAdvertisePermissionForDataDelivery(
            Context context, AttributionSource attributionSource, String message) {
@@ -539,23 +564,7 @@ public final class Utils {
        return false;
    }

    public static boolean callerIsSystemOrActiveUser(String tag, String method) {
        if (!checkCaller()) {
          Log.w(TAG, method + "() - Not allowed for non-active user and non-system user");
          return false;
        }
        return true;
    }

    public static boolean callerIsSystemOrActiveOrManagedUser(Context context, String tag, String method) {
        if (!checkCallerAllowManagedProfiles(context)) {
          Log.w(TAG, method + "() - Not allowed for non-active user and non-system and non-managed user");
          return false;
        }
        return true;
    }

    public static boolean checkCaller() {
    public static boolean checkCallerIsSystemOrActiveUser() {
        int callingUser = UserHandle.getCallingUserId();
        int callingUid = Binder.getCallingUid();
        return (sForegroundUserId == callingUser)
@@ -563,9 +572,21 @@ public final class Utils {
                || (UserHandle.getAppId(Process.SYSTEM_UID) == UserHandle.getAppId(callingUid));
    }

    public static boolean checkCallerAllowManagedProfiles(Context mContext) {
        if (mContext == null) {
            return checkCaller();
    public static boolean checkCallerIsSystemOrActiveUser(String tag) {
        final boolean res = checkCallerIsSystemOrActiveUser();
        if (!res) {
            Log.w(TAG, tag + " - Not allowed for non-active user and non-system user");
        }
        return res;
    }

    public static boolean callerIsSystemOrActiveUser(String tag, String method) {
        return checkCallerIsSystemOrActiveUser(tag + "." + method + "()");
    }

    public static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context) {
        if (context == null) {
            return checkCallerIsSystemOrActiveUser();
        }
        int callingUser = UserHandle.getCallingUserId();
        int callingUid = Binder.getCallingUid();
@@ -573,7 +594,7 @@ public final class Utils {
        // Use the Bluetooth process identity when making call to get parent user
        long ident = Binder.clearCallingIdentity();
        try {
            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
            UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
            UserInfo ui = um.getProfileParent(callingUser);
            int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;

@@ -589,6 +610,32 @@ public final class Utils {
        }
    }

    public static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context, String tag) {
        final boolean res = checkCallerIsSystemOrActiveOrManagedUser(context);
        if (!res) {
            Log.w(TAG, tag + " - Not allowed for"
                    + " non-active user and non-system and non-managed user");
        }
        return res;
    }

    public static boolean callerIsSystemOrActiveOrManagedUser(Context context, String tag,
            String method) {
        return checkCallerIsSystemOrActiveOrManagedUser(context, tag + "." + method + "()");
    }

    public static boolean checkServiceAvailable(ProfileService service, String tag) {
        if (service == null) {
            Log.w(TAG, tag + " - Not present");
            return false;
        }
        if (!service.isAvailable()) {
            Log.w(TAG, tag + " - Not available");
            return false;
        }
        return true;
    }

    /**
     * Checks whether location is off and must be on for us to perform some operation
     */
+0 −1
Original line number Diff line number Diff line
@@ -205,7 +205,6 @@ public class A2dpNativeInterface {
        sendMessageToService(event);
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    private boolean isMandatoryCodecPreferred(byte[] address) {
        A2dpService service = A2dpService.getA2dpService();
        if (service != null) {
+87 −126

File changed.

Preview size limit exceeded, changes collapsed.

+0 −2
Original line number Diff line number Diff line
@@ -617,7 +617,6 @@ final class A2dpStateMachine extends StateMachine {

    // NOTE: This event is processed in any state
    @VisibleForTesting
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
        BluetoothCodecConfig prevCodecConfig = null;
        BluetoothCodecStatus prevCodecStatus = mCodecStatus;
@@ -767,7 +766,6 @@ final class A2dpStateMachine extends StateMachine {
        return Integer.toString(state);
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    public void dump(StringBuilder sb) {
        boolean isActive = Objects.equals(mDevice, mA2dpService.getActiveDevice());
        ProfileService.println(sb,
+29 −51
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothA2dpSink;
import android.content.AttributionSource;
import android.media.AudioManager;
import android.util.Log;

@@ -32,7 +33,6 @@ import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -185,17 +185,15 @@ public class A2dpSinkService extends ProfileService {
            implements IProfileServiceBinder {
        private A2dpSinkService mService;

        private A2dpSinkService getService() {
            if (!Utils.checkCaller()) {
                Log.w(TAG, "A2dp call not allowed for non-active user");
        @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
        private A2dpSinkService getService(AttributionSource source) {
            if (!Utils.checkCallerIsSystemOrActiveUser(TAG)
                    || !Utils.checkServiceAvailable(mService, TAG)
                    || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
                return null;
            }

            if (mService != null) {
            return mService;
        }
            return null;
        }

        A2dpSinkServiceBinder(A2dpSinkService svc) {
            mService = svc;
@@ -207,8 +205,8 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public boolean connect(BluetoothDevice device) {
            A2dpSinkService service = getService();
        public boolean connect(BluetoothDevice device, AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return false;
            }
@@ -216,8 +214,8 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public boolean disconnect(BluetoothDevice device) {
            A2dpSinkService service = getService();
        public boolean disconnect(BluetoothDevice device, AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return false;
            }
@@ -225,8 +223,8 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public List<BluetoothDevice> getConnectedDevices() {
            A2dpSinkService service = getService();
        public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return new ArrayList<BluetoothDevice>(0);
            }
@@ -234,8 +232,9 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
            A2dpSinkService service = getService();
        public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states,
                AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return new ArrayList<BluetoothDevice>(0);
            }
@@ -243,8 +242,8 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public int getConnectionState(BluetoothDevice device) {
            A2dpSinkService service = getService();
        public int getConnectionState(BluetoothDevice device, AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return BluetoothProfile.STATE_DISCONNECTED;
            }
@@ -252,8 +251,9 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
            A2dpSinkService service = getService();
        public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
                AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return false;
            }
@@ -261,8 +261,8 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public int getConnectionPolicy(BluetoothDevice device) {
            A2dpSinkService service = getService();
        public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
            }
@@ -270,8 +270,8 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public boolean isA2dpPlaying(BluetoothDevice device) {
            A2dpSinkService service = getService();
        public boolean isA2dpPlaying(BluetoothDevice device, AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return false;
            }
@@ -279,8 +279,9 @@ public class A2dpSinkService extends ProfileService {
        }

        @Override
        public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
            A2dpSinkService service = getService();
        public BluetoothAudioConfig getAudioConfig(BluetoothDevice device,
                AttributionSource source) {
            A2dpSinkService service = getService(source);
            if (service == null) {
                return null;
            }
@@ -331,11 +332,7 @@ public class A2dpSinkService extends ProfileService {
     *
     * @return true if disconnect is successful, false otherwise.
     */
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    public boolean disconnect(BluetoothDevice device) {
        if (!Utils.checkConnectPermissionForPreflight(this)) {
            return false;
        }
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
@@ -363,11 +360,7 @@ public class A2dpSinkService extends ProfileService {
        mDeviceStateMap.remove(stateMachine.getDevice());
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    public List<BluetoothDevice> getConnectedDevices() {
        if (!Utils.checkConnectPermissionForPreflight(this)) {
            return Collections.emptyList();
        }
        return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
    }

@@ -384,12 +377,8 @@ public class A2dpSinkService extends ProfileService {
        return existingStateMachine;
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
        if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
        if (!Utils.checkConnectPermissionForPreflight(this)) {
            return Collections.emptyList();
        }
        List<BluetoothDevice> deviceList = new ArrayList<>();
        BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
        int connectionState;
@@ -416,11 +405,7 @@ public class A2dpSinkService extends ProfileService {
     * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or
     * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
     */
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    public int getConnectionState(BluetoothDevice device) {
        if (!Utils.checkConnectPermissionForPreflight(this)) {
            return BluetoothProfile.STATE_DISCONNECTED;
        }
        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
        return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
                : stateMachine.getState();
@@ -441,10 +426,7 @@ public class A2dpSinkService extends ProfileService {
     * @param connectionPolicy is the connection policy to set to for this profile
     * @return true if connectionPolicy is set, false on error
     */
    @RequiresPermission(allOf = {
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
        enforceCallingOrSelfPermission(
                BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
@@ -492,11 +474,7 @@ public class A2dpSinkService extends ProfileService {
        }
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
        if (!Utils.checkConnectPermissionForPreflight(this)) {
            return null;
        }
        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
        // a state machine instance doesn't exist. maybe it is already gone?
        if (stateMachine == null) {
Loading