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

Commit 5682fb80 authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Ivan Podogov
Browse files

Add Bluetooth toggle prompts - framework

If permission review is enabled toggling bluetoth on or off
results in a user prompt to collect consent. This applies
only to legacy apps, i.e. ones that don't support runtime
permissions as they target SDK 22.

Also added a configuration resource which controls whether
permission review mode is enabled. By default it is not and
an OEM can change this via an overlay. For now we also keep
the old mechanism to toggle review mode via a build property
which is still used and will be removed when clients have
transitioned.

bug:28715749

Change-Id: I94c5828ad6c8aa6b363622a26ff9da4fc2e2fac7
parent 3b250a51
Loading
Loading
Loading
Loading
+28 −4
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
@@ -254,6 +255,29 @@ public final class BluetoothAdapter {
    public static final String ACTION_REQUEST_ENABLE =
            "android.bluetooth.adapter.action.REQUEST_ENABLE";

    /**
     * Activity Action: Show a system activity that allows the user to turn off
     * Bluetooth. This is used only if permission review is enabled which is for
     * apps targeting API less than 23 require a permission review before any of
     * the app's components can run.
     * <p>This system activity will return once Bluetooth has completed turning
     * off, or the user has decided not to turn Bluetooth off.
     * <p>Notification of the result of this activity is posted using the
     * {@link android.app.Activity#onActivityResult} callback. The
     * <code>resultCode</code>
     * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been
     * turned off or {@link android.app.Activity#RESULT_CANCELED} if the user
     * has rejected the request or an error has occurred.
     * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
     * for global notification whenever Bluetooth is turned on or off.
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
     *
     * @hide
     */
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_REQUEST_DISABLE =
            "android.bluetooth.adapter.action.REQUEST_DISABLE";

    /**
     * Activity Action: Show a system activity that allows user to enable BLE scans even when
     * Bluetooth is turned off.<p>
@@ -772,7 +796,7 @@ public final class BluetoothAdapter {
                return true;
            }
            if (DBG) Log.d(TAG, "enableBLE(): Calling enable");
            return mManagerService.enable();
            return mManagerService.enable(ActivityThread.currentPackageName());
        } catch (RemoteException e) {
            Log.e(TAG, "", e);
        }
@@ -899,7 +923,7 @@ public final class BluetoothAdapter {
            return true;
        }
        try {
            return mManagerService.enable();
            return mManagerService.enable(ActivityThread.currentPackageName());
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return false;
    }
@@ -931,7 +955,7 @@ public final class BluetoothAdapter {
    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public boolean disable() {
        try {
            return mManagerService.disable(true);
            return mManagerService.disable(ActivityThread.currentPackageName(), true);
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return false;
    }
@@ -949,7 +973,7 @@ public final class BluetoothAdapter {
    public boolean disable(boolean persist) {

        try {
            return mManagerService.disable(persist);
            return mManagerService.disable(ActivityThread.currentPackageName(), persist);
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        return false;
    }
+2 −2
Original line number Diff line number Diff line
@@ -34,9 +34,9 @@ interface IBluetoothManager
    void registerStateChangeCallback(in IBluetoothStateChangeCallback callback);
    void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback);
    boolean isEnabled();
    boolean enable();
    boolean enable(String packageName);
    boolean enableNoAutoConnect();
    boolean disable(boolean persist);
    boolean disable(String packageName, boolean persist);
    int getState();
    IBluetoothGatt getBluetoothGatt();

+69 −14
Original line number Diff line number Diff line
@@ -35,10 +35,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -151,6 +153,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
    private final Map <Integer, ProfileServiceConnections> mProfileServices =
            new HashMap <Integer, ProfileServiceConnections>();

    private final boolean mPermissionReviewRequired;

    private void registerForAirplaneMode(IntentFilter filter) {
        final ContentResolver resolver = mContext.getContentResolver();
        final String airplaneModeRadios = Settings.Global.getString(resolver,
@@ -244,6 +248,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
        mHandler = new BluetoothHandler(IoThread.get().getLooper());

        mContext = context;

        mPermissionReviewRequired = Build.PERMISSIONS_REVIEW_REQUIRED
                    || context.getResources().getBoolean(
                com.android.internal.R.bool.config_permissionReviewRequired);

        mBluetooth = null;
        mBluetoothBinder = null;
        mBluetoothGatt = null;
@@ -654,15 +663,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
        return true;
    }

    public boolean enable() {
        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
            (!checkIfCallerIsForegroundUser())) {
    public boolean enable(String packageName) throws RemoteException {
        final int callingUid = Binder.getCallingUid();
        final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;

        if (!callerSystem) {
            if (!checkIfCallerIsForegroundUser()) {
                Slog.w(TAG, "enable(): not allowed for non-active and non system user");
                return false;
            }

            mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                    "Need BLUETOOTH ADMIN permission");

            if (!isEnabled() && mPermissionReviewRequired
                    && startConsentUiIfNeeded(packageName, callingUid,
                            BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
                return false;
            }
        }

        if (DBG) {
            Slog.d(TAG,"enable():  mBluetooth =" + mBluetooth +
                    " mBinding = " + mBinding + " mState = " + mState);
@@ -678,16 +698,26 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
        return true;
    }

    public boolean disable(boolean persist) {
        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                                "Need BLUETOOTH ADMIN permissicacheNameAndAddresson");
    public boolean disable(String packageName, boolean persist) throws RemoteException {
        final int callingUid = Binder.getCallingUid();
        final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;

        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
            (!checkIfCallerIsForegroundUser())) {
        if (!callerSystem) {
            if (!checkIfCallerIsForegroundUser()) {
                Slog.w(TAG, "disable(): not allowed for non-active and non system user");
                return false;
            }

            mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                    "Need BLUETOOTH ADMIN permission");

            if (isEnabled() && mPermissionReviewRequired
                    && startConsentUiIfNeeded(packageName, callingUid,
                            BluetoothAdapter.ACTION_REQUEST_DISABLE)) {
                return false;
            }
        }

        if (DBG) {
            Slog.d(TAG,"disable(): mBluetooth = " + mBluetooth +
                " mBinding = " + mBinding);
@@ -706,6 +736,31 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
        return true;
    }

    private boolean startConsentUiIfNeeded(String packageName,
            int callingUid, String intentAction) throws RemoteException {
        try {
            // Validate the package only if we are going to use it
            ApplicationInfo applicationInfo = mContext.getPackageManager()
                    .getApplicationInfoAsUser(packageName,
                            PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                            UserHandle.getUserId(callingUid));
            if (applicationInfo.uid != callingUid) {
                throw new SecurityException("Package " + callingUid
                        + " not in uid " + callingUid);
            }

            // Legacy apps in permission review mode trigger a user prompt
            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                Intent intent = new Intent(intentAction);
                mContext.startActivity(intent);
                return true;
            }
        } catch (PackageManager.NameNotFoundException e) {
            throw new RemoteException(e.getMessage());
        }
        return false;
    }

    public void unbindAndFinish() {
        if (DBG) {
            Slog.d(TAG,"unbindAndFinish(): " + mBluetooth +