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

Commit 45456511 authored by William Escande's avatar William Escande
Browse files

SystemServer: rm shipped flag - new_airplane_mode

Bug: 309033118
Fix: 309033118
Test: atest ServiceBluetoothTests
Change-Id: I09d0e4400b822a9092c950f03732911355b10154
parent d5d4a683
Loading
Loading
Loading
Loading
+0 −7
Original line number Diff line number Diff line
@@ -23,13 +23,6 @@ flag {
    bug: "328698375"
}

flag {
    name: "use_new_airplane_mode"
    namespace: "bluetooth"
    description: "Use the new implemention of airplane mode"
    bug: "309033118"
}

flag {
    name: "use_new_satellite_mode"
    namespace: "bluetooth"
+0 −303
Original line number Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.bluetooth;

import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;

import com.android.bluetooth.BluetoothStatsLog;
import com.android.bluetooth.flags.FeatureFlags;
import com.android.internal.annotations.VisibleForTesting;

/**
 * The BluetoothAirplaneModeListener handles system airplane mode change callback and checks whether
 * we need to inform BluetoothManagerService on this change.
 *
 * <p>The information of airplane mode turns on would not be passed to the BluetoothManagerService
 * when Bluetooth is on and Bluetooth is in one of the following situations:
 *
 * <ul>
 *   <li>Bluetooth A2DP is connected.
 *   <li>Bluetooth Hearing Aid profile is connected.
 *   <li>Bluetooth LE Audio is connected
 * </ul>
 */
class BluetoothAirplaneModeListener extends Handler {
    private static final String TAG = BluetoothAirplaneModeListener.class.getSimpleName();

    @VisibleForTesting static final String TOAST_COUNT = "bluetooth_airplane_toast_count";

    // keeps track of whether wifi should remain on in airplane mode
    public static final String WIFI_APM_STATE = "wifi_apm_state";
    // keeps track of whether wifi and bt remains on notification was shown
    public static final String APM_WIFI_BT_NOTIFICATION = "apm_wifi_bt_notification";
    // keeps track of whether bt remains on notification was shown
    public static final String APM_BT_NOTIFICATION = "apm_bt_notification";
    // keeps track of whether airplane mode enhancement feature is enabled
    public static final String APM_ENHANCEMENT = "apm_enhancement_enabled";
    // keeps track of whether user changed bt state in airplane mode
    public static final String APM_USER_TOGGLED_BLUETOOTH = "apm_user_toggled_bluetooth";
    // keeps track of whether bt should remain on in airplane mode
    public static final String BLUETOOTH_APM_STATE = "bluetooth_apm_state";
    // keeps track of whether user enabling bt notification was shown
    public static final String APM_BT_ENABLED_NOTIFICATION = "apm_bt_enabled_notification";

    public static final int NOTIFICATION_NOT_SHOWN = 0;
    public static final int NOTIFICATION_SHOWN = 1;
    public static final int UNUSED = 0;
    public static final int USED = 1;

    private static final int BLUETOOTH_OFF_APM = 0;
    private static final int BLUETOOTH_ON_APM = 1;

    @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times

    /* Tracks the bluetooth state before entering airplane mode*/
    private boolean mIsBluetoothOnBeforeApmToggle = false;
    /* Tracks the bluetooth state after entering airplane mode*/
    private boolean mIsBluetoothOnAfterApmToggle = false;
    /* Tracks whether user toggled bluetooth in airplane mode */
    private boolean mUserToggledBluetoothDuringApm = false;
    /* Tracks whether user toggled bluetooth in airplane mode within one minute */
    private boolean mUserToggledBluetoothDuringApmWithinMinute = false;
    /* Tracks whether media profile was connected before entering airplane mode */
    private boolean mIsMediaProfileConnectedBeforeApmToggle = false;
    /* Tracks when airplane mode has been enabled */
    private long mApmEnabledTime = 0;

    private final BluetoothManagerService mBluetoothManager;
    private final FeatureFlags mFeatureFlags;
    private final Context mContext;
    private BluetoothModeChangeHelper mAirplaneHelper;

    private boolean mIsAirplaneModeOn;

    @VisibleForTesting int mToastCount = 0;

    BluetoothAirplaneModeListener(
            BluetoothManagerService service,
            Looper looper,
            Context context,
            FeatureFlags featureFlags) {
        super(looper);

        mBluetoothManager = service;
        mFeatureFlags = featureFlags;
        mContext = context;

        String airplaneModeRadios =
                Settings.Global.getString(
                        mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_RADIOS);
        if (airplaneModeRadios != null
                && !airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH)) {
            Log.w(TAG, "BluetoothAirplaneModeListener: blocked by AIRPLANE_MODE_RADIOS");
            mIsAirplaneModeOn = false;
            return;
        }

        mIsAirplaneModeOn = isGlobalAirplaneModeOn(mContext);

        mContext.getContentResolver()
                .registerContentObserver(
                        Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
                        true,
                        new ContentObserver(this) {
                            @Override
                            public void onChange(boolean selfChange) {
                                // This is called on the looper and doesn't need a lock
                                boolean isGlobalAirplaneModeOn = isGlobalAirplaneModeOn(mContext);
                                if (mIsAirplaneModeOn == isGlobalAirplaneModeOn) {
                                    Log.d(
                                            TAG,
                                            "Ignore airplane mode change:"
                                                    + (" mIsAirplaneModeOn=" + mIsAirplaneModeOn));
                                    return;
                                }
                                mIsAirplaneModeOn = isGlobalAirplaneModeOn;
                                handleAirplaneModeChange(mIsAirplaneModeOn);
                            }
                        });
    }

    /** Do not use outside of this class to avoid async issues */
    private static boolean isGlobalAirplaneModeOn(Context ctx) {
        return BluetoothServerProxy.getInstance()
                        .settingsGlobalGetInt(
                                ctx.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0)
                == 1;
    }

    /** return true if airplaneMode is currently On */
    boolean isAirplaneModeOn() {
        return mIsAirplaneModeOn;
    }

    /** Call after boot complete */
    @VisibleForTesting
    void start(BluetoothModeChangeHelper helper) {
        Log.i(TAG, "start");
        mAirplaneHelper = helper;
        mToastCount = mAirplaneHelper.getSettingsInt(TOAST_COUNT);
    }

    @VisibleForTesting
    boolean shouldPopToast() {
        if (mToastCount >= MAX_TOAST_COUNT) {
            return false;
        }
        mToastCount++;
        mAirplaneHelper.setSettingsInt(TOAST_COUNT, mToastCount);
        return true;
    }

    @VisibleForTesting
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    void handleAirplaneModeChange(boolean isAirplaneModeOn) {
        if (mAirplaneHelper == null) {
            return;
        }
        if (isAirplaneModeOn) {
            mApmEnabledTime = SystemClock.elapsedRealtime();
            mIsBluetoothOnBeforeApmToggle = mAirplaneHelper.isBluetoothOn();
            mIsMediaProfileConnectedBeforeApmToggle = mBluetoothManager.isMediaProfileConnected();
            mIsBluetoothOnAfterApmToggle =
                    shouldSkipAirplaneModeChange(mIsMediaProfileConnectedBeforeApmToggle);
            if (mIsBluetoothOnAfterApmToggle) {
                Log.i(TAG, "Ignore airplane mode change");
                // Airplane mode enabled when Bluetooth is being used for audio/hearing aid.
                // Bluetooth is not disabled in such case, only state is changed to
                // BLUETOOTH_ON_AIRPLANE mode.
                mAirplaneHelper.setSettingsInt(
                        Settings.Global.BLUETOOTH_ON,
                        BluetoothManagerService.BLUETOOTH_ON_AIRPLANE);
                displayUserNotificationIfNeeded();
                return;
            }
        } else {
            BluetoothStatsLog.write(
                    BluetoothStatsLog.AIRPLANE_MODE_SESSION_REPORTED,
                    BluetoothStatsLog.AIRPLANE_MODE_SESSION_REPORTED__PACKAGE_NAME__BLUETOOTH,
                    mIsBluetoothOnBeforeApmToggle,
                    mIsBluetoothOnAfterApmToggle,
                    mAirplaneHelper.isBluetoothOn(),
                    isBluetoothToggledOnApm(),
                    mUserToggledBluetoothDuringApm,
                    mUserToggledBluetoothDuringApmWithinMinute,
                    mIsMediaProfileConnectedBeforeApmToggle);
            mUserToggledBluetoothDuringApm = false;
            mUserToggledBluetoothDuringApmWithinMinute = false;
        }
        mBluetoothManager.onAirplaneModeChanged(isAirplaneModeOn);
    }

    private void displayUserNotificationIfNeeded() {
        if (!isApmEnhancementEnabled() || !isBluetoothToggledOnApm()) {
            if (shouldPopToast()) {
                mAirplaneHelper.showToastMessage();
            }
            return;
        } else {
            if (isWifiEnabledOnApm()) {
                mBluetoothManager.sendToggleNotification(APM_WIFI_BT_NOTIFICATION);
            } else {
                mBluetoothManager.sendToggleNotification(APM_BT_NOTIFICATION);
            }
        }
    }

    @VisibleForTesting
    boolean shouldSkipAirplaneModeChange(boolean isMediaProfileConnected) {
        boolean apmEnhancementUsed = isApmEnhancementEnabled() && isBluetoothToggledOnApm();

        // APM feature disabled or user has not used the feature yet by changing BT state in APM
        // BT will only remain on in APM when media profile is connected
        if (!apmEnhancementUsed && mAirplaneHelper.isBluetoothOn() && isMediaProfileConnected) {
            return true;
        }
        // APM feature enabled and user has used the feature by changing BT state in APM
        // BT will only remain on in APM based on user's last action in APM
        if (apmEnhancementUsed
                && mAirplaneHelper.isBluetoothOn()
                && mAirplaneHelper.isBluetoothOnAPM()) {
            return true;
        }
        // APM feature enabled and user has not used the feature yet by changing BT state in APM
        // BT will only remain on in APM if the default value is set to on
        if (isApmEnhancementEnabled()
                && !isBluetoothToggledOnApm()
                && mAirplaneHelper.isBluetoothOn()
                && mAirplaneHelper.isBluetoothOnAPM()) {
            return true;
        }
        return false;
    }

    private boolean isApmEnhancementEnabled() {
        return mAirplaneHelper.getSettingsInt(APM_ENHANCEMENT) == 1;
    }

    private boolean isBluetoothToggledOnApm() {
        return mAirplaneHelper.getSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, UNUSED) == USED;
    }

    private boolean isWifiEnabledOnApm() {
        return mAirplaneHelper.getSettingsInt(Settings.Global.WIFI_ON) != 0
                && mAirplaneHelper.getSettingsSecureInt(WIFI_APM_STATE, 0) == 1;
    }

    /** Helper method to update whether user toggled Bluetooth in airplane mode */
    public void notifyUserToggledBluetooth(boolean isOn) {
        if (!mIsAirplaneModeOn) {
            // User not in Airplane mode, discard event
            return;
        }
        if (!mUserToggledBluetoothDuringApm) {
            mUserToggledBluetoothDuringApmWithinMinute =
                    SystemClock.elapsedRealtime() - mApmEnabledTime < 60000;
        }
        mUserToggledBluetoothDuringApm = true;
        if (isApmEnhancementEnabled()) {
            setSettingsSecureInt(BLUETOOTH_APM_STATE, isOn ? BLUETOOTH_ON_APM : BLUETOOTH_OFF_APM);
            setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED);
            if (isOn) {
                mBluetoothManager.sendToggleNotification(APM_BT_ENABLED_NOTIFICATION);
            }
        }
    }

    /** Set the Settings Secure Int value for foreground user */
    private void setSettingsSecureInt(String name, int value) {
        // waive WRITE_SECURE_SETTINGS permission check
        final long callingIdentity = Binder.clearCallingIdentity();
        try {
            Context userContext =
                    mContext.createContextAsUser(
                            UserHandle.of(ActivityManager.getCurrentUser()), 0);
            Settings.Secure.putInt(userContext.getContentResolver(), name, value);
        } finally {
            Binder.restoreCallingIdentity(callingIdentity);
        }
    }
}
+24 −67
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;

import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
import static com.android.server.bluetooth.BluetoothAirplaneModeListener.APM_ENHANCEMENT;

import static java.util.Objects.requireNonNull;

@@ -182,10 +181,6 @@ class BluetoothManagerService {
    // and Airplane mode will have higher priority.
    @VisibleForTesting static final int BLUETOOTH_ON_AIRPLANE = 2;

    // APM enhancement feature is enabled by default
    // Set this value to 0 to disable the feature
    private static final int DEFAULT_APM_ENHANCEMENT_STATE = 1;

    private final Context mContext;
    private final Looper mLooper;

@@ -209,14 +204,10 @@ class BluetoothManagerService {

    private List<Integer> mSupportedProfileList = new ArrayList<>();

    // TODO(b/309033118): remove BluetoothAirplaneModeListener once use_new_airplane_mode ship
    private final BluetoothAirplaneModeListener mBluetoothAirplaneModeListener;

    // TODO(b/289584302): remove BluetoothSatelliteModeListener once use_new_satellite_mode ship
    private BluetoothSatelliteModeListener mBluetoothSatelliteModeListener;

    private final boolean mUseNewSatelliteMode;
    private final boolean mUseNewAirplaneMode;

    // used inside handler thread
    private boolean mQuietEnable = false;
@@ -720,15 +711,6 @@ class BluetoothManagerService {
            mEnableExternal = true;
        }

        // Caching is necessary to prevent caller requiring the READ_DEVICE_CONFIG permission
        mUseNewAirplaneMode = mFeatureFlags.useNewAirplaneMode();
        if (mUseNewAirplaneMode) {
            mBluetoothAirplaneModeListener = null;
        } else {
            mBluetoothAirplaneModeListener =
                    new BluetoothAirplaneModeListener(this, mLooper, mContext, mFeatureFlags);
        }

        // Caching is necessary to prevent caller requiring the READ_DEVICE_CONFIG permission
        mUseNewSatelliteMode = mFeatureFlags.useNewSatelliteMode();
        if (!mUseNewSatelliteMode) {
@@ -757,11 +739,8 @@ class BluetoothManagerService {

    /** Returns true if airplane mode is currently on */
    private boolean isAirplaneModeOn() {
        if (mUseNewAirplaneMode) {
        return AirplaneModeListener.isOn();
    }
        return mBluetoothAirplaneModeListener.isAirplaneModeOn();
    }

    /** Returns true if satellite mode is turned on. */
    private boolean isSatelliteModeOn() {
@@ -1223,9 +1202,6 @@ class BluetoothManagerService {
        synchronized (mReceiver) {
            mQuietEnableExternal = false;
            mEnableExternal = true;
            if (!mUseNewAirplaneMode) {
                mBluetoothAirplaneModeListener.notifyUserToggledBluetooth(true);
            } else {
            // TODO(b/288450479): Remove clearCallingIdentity when threading is fixed
            final long callingIdentity = Binder.clearCallingIdentity();
            try {
@@ -1234,7 +1210,6 @@ class BluetoothManagerService {
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
            }
            }
            sendEnableMsg(
                    false,
                    BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST,
@@ -1252,9 +1227,6 @@ class BluetoothManagerService {
                        + (" mState=" + mState));

        synchronized (mReceiver) {
            if (!mUseNewAirplaneMode) {
                mBluetoothAirplaneModeListener.notifyUserToggledBluetooth(false);
            } else {
            // TODO(b/288450479): Remove clearCallingIdentity when threading is fixed
            final long callingIdentity = Binder.clearCallingIdentity();
            try {
@@ -1263,7 +1235,6 @@ class BluetoothManagerService {
            } finally {
                Binder.restoreCallingIdentity(callingIdentity);
            }
            }

            if (persist) {
                setBluetoothPersistedState(BLUETOOTH_OFF);
@@ -1313,7 +1284,6 @@ class BluetoothManagerService {
                requireNonNull(
                        mContext.createContextAsUser(userHandle, 0),
                        "Current User Context cannot be null");
        if (mUseNewAirplaneMode) {
        AirplaneModeListener.initialize(
                mLooper,
                mContentResolver,
@@ -1323,7 +1293,6 @@ class BluetoothManagerService {
                this::isMediaProfileConnected,
                this::getCurrentUserContext,
                TimeSource.Monotonic.INSTANCE);
        }

        if (mUseNewSatelliteMode) {
            SatelliteModeListener.initialize(
@@ -1355,18 +1324,6 @@ class BluetoothManagerService {
        }

        autoOnHiddenListener();

        if (!mUseNewAirplaneMode) {
            mBluetoothAirplaneModeListener.start(new BluetoothModeChangeHelper(mContext));
            setApmEnhancementState();
        }
    }

    /** set APM enhancement feature state */
    @VisibleForTesting
    void setApmEnhancementState() {
        Settings.Global.putInt(
                mContext.getContentResolver(), APM_ENHANCEMENT, DEFAULT_APM_ENHANCEMENT_STATE);
    }

    /** Called when switching to a different foreground user. */
+0 −104
Original line number Diff line number Diff line
/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.bluetooth;

import static com.android.server.bluetooth.BluetoothAirplaneModeListener.BLUETOOTH_APM_STATE;

import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.Settings;
import android.widget.Toast;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Helper class that handles callout and callback methods without
 * complex logic.
 */
public class BluetoothModeChangeHelper {
    private static final String TAG = BluetoothModeChangeHelper.class.getSimpleName();

    private final BluetoothAdapter mAdapter;
    private final Context mContext;

    private String mBluetoothPackageName;

    BluetoothModeChangeHelper(Context context) {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mContext = context;
    }

    @VisibleForTesting
    public boolean isBluetoothOn() {
        final BluetoothAdapter adapter = mAdapter;
        if (adapter == null) {
            return false;
        }
        return adapter.isLeEnabled();
    }

    @VisibleForTesting
    public int getSettingsInt(String name) {
        return Settings.Global.getInt(mContext.getContentResolver(),
                name, 0);
    }

    @VisibleForTesting
    public void setSettingsInt(String name, int value) {
        Settings.Global.putInt(mContext.getContentResolver(),
                name, value);
    }

    /**
     * Helper method to get Settings Secure Int value
     */
    public int getSettingsSecureInt(String name, int def) {
        Context userContext = mContext.createContextAsUser(
                UserHandle.of(ActivityManager.getCurrentUser()), 0);
        return Settings.Secure.getInt(userContext.getContentResolver(), name, def);
    }

    /**
     * Helper method to set Settings Secure Int value
     */
    public void setSettingsSecureInt(String name, int value) {
        Context userContext = mContext.createContextAsUser(
                UserHandle.of(ActivityManager.getCurrentUser()), 0);
        Settings.Secure.putInt(userContext.getContentResolver(), name, value);
    }

    @VisibleForTesting
    public void showToastMessage() {
        Resources r = mContext.getResources();
        final CharSequence text = r.getString(Resources.getSystem().getIdentifier(
                "bluetooth_airplane_mode_toast", "string", "android"));
        Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
    }

    /**
     * Helper method to check whether BT should be enabled on APM
     */
    public boolean isBluetoothOnAPM() {
        Context userContext = mContext.createContextAsUser(
                UserHandle.of(ActivityManager.getCurrentUser()), 0);
        return Settings.Secure.getInt(userContext.getContentResolver(),
                BLUETOOTH_APM_STATE, 0) == 1;
    }
}
+0 −243

File deleted.

Preview size limit exceeded, changes collapsed.

Loading