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

Commit e26eb8c5 authored by William Escande's avatar William Escande Committed by Automerger Merge Worker
Browse files

Merge changes Ia7f7ef15,Id40dd4a8,I09d0e440,I52ffe9cb into main am: 0ba20e52

parents 846a4f21 0ba20e52
Loading
Loading
Loading
Loading
+0 −22
Original line number Diff line number Diff line
package: "com.android.bluetooth.flags"
container: "com.android.btservices"

flag {
    name: "airplane_ressources_in_app"
    namespace: "bluetooth"
    description: "User notification is done within app, not server"
    bug: "303552318"
}

flag {
    name: "auto_on_feature"
    is_exported: true
@@ -29,18 +22,3 @@ flag {
    description: "Remove complexity and non necessary initialization when simply binding"
    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"
    description: "Use the new implemention of satellite mode"
    bug: "289584302"
}
+0 −378
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.content.pm.PackageManager;
import android.content.res.Resources;
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 final BluetoothNotificationManager mNotificationManager;

    private boolean mIsAirplaneModeOn;

    @VisibleForTesting int mToastCount = 0;

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

        mBluetoothManager = service;
        mFeatureFlags = featureFlags;
        mNotificationManager = notificationManager;
        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 (mFeatureFlags.airplaneRessourcesInApp()) {
                if (isWifiEnabledOnApm()) {
                    mBluetoothManager.sendToggleNotification(APM_WIFI_BT_NOTIFICATION);
                } else {
                    mBluetoothManager.sendToggleNotification(APM_BT_NOTIFICATION);
                }
                return;
            }
            if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) {
                try {
                    sendApmNotification(
                            "bluetooth_and_wifi_stays_on_title",
                            "bluetooth_and_wifi_stays_on_message",
                            APM_WIFI_BT_NOTIFICATION);
                } catch (Exception e) {
                    Log.e(TAG, "APM enhancement BT and Wi-Fi stays on notification not shown");
                }
            } else if (!isWifiEnabledOnApm() && isFirstTimeNotification(APM_BT_NOTIFICATION)) {
                try {
                    sendApmNotification(
                            "bluetooth_stays_on_title",
                            "bluetooth_stays_on_message",
                            APM_BT_NOTIFICATION);
                } catch (Exception e) {
                    Log.e(TAG, "APM enhancement BT stays on notification not shown");
                }
            }
        }
    }

    @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 send APM notification */
    public void sendApmNotification(String titleId, String messageId, String notificationState)
            throws PackageManager.NameNotFoundException {
        String btPackageName = mAirplaneHelper.getBluetoothPackageName();
        if (btPackageName == null) {
            Log.e(
                    TAG,
                    "Unable to find Bluetooth package name with " + "APM notification resources");
            return;
        }
        Resources resources =
                mContext.getPackageManager().getResourcesForApplication(btPackageName);
        int title = resources.getIdentifier(titleId, "string", btPackageName);
        int message = resources.getIdentifier(messageId, "string", btPackageName);
        mNotificationManager.sendApmNotification(
                resources.getString(title), resources.getString(message));
        mAirplaneHelper.setSettingsSecureInt(notificationState, NOTIFICATION_SHOWN);
    }

    /** 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 (mFeatureFlags.airplaneRessourcesInApp()) {
                if (isOn) {
                    mBluetoothManager.sendToggleNotification(APM_BT_ENABLED_NOTIFICATION);
                }
                return;
            }
            if (isOn && isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) {
                // waive WRITE_SECURE_SETTINGS permission check
                final long callingIdentity = Binder.clearCallingIdentity();
                try {
                    sendApmNotification(
                            "bluetooth_enabled_apm_title",
                            "bluetooth_enabled_apm_message",
                            APM_BT_ENABLED_NOTIFICATION);
                } catch (Exception e) {
                    Log.e(TAG, "APM enhancement BT enabled notification not shown");
                } finally {
                    Binder.restoreCallingIdentity(callingIdentity);
                }
            }
        }
    }

    /** Return whether APM notification has been shown */
    private boolean isFirstTimeNotification(String name) {
        // waive WRITE_SECURE_SETTINGS permission check
        final long callingIdentity = Binder.clearCallingIdentity();
        try {
            return mAirplaneHelper.getSettingsSecureInt(name, NOTIFICATION_NOT_SHOWN)
                    == NOTIFICATION_NOT_SHOWN;
        } finally {
            Binder.restoreCallingIdentity(callingIdentity);
        }
    }

    /** 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);
        }
    }
}
+27 −106

File changed.

Preview size limit exceeded, changes collapsed.

+0 −136
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.pm.PackageManager;
import android.content.res.Resources;
import android.os.Process;
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;
    }

    /**
     * Helper method to retrieve BT package name with APM resources
     */
    public String getBluetoothPackageName() {
        if (mBluetoothPackageName != null) {
            return mBluetoothPackageName;
        }
        var allPackages = mContext.getPackageManager().getPackagesForUid(Process.BLUETOOTH_UID);
        for (String candidatePackage : allPackages) {
            Resources resources;
            try {
                resources = mContext.getPackageManager()
                        .getResourcesForApplication(candidatePackage);
            } catch (PackageManager.NameNotFoundException e) {
                // ignore, try next package
                Log.e(TAG, "Could not find package " + candidatePackage);
                continue;
            } catch (Exception e) {
                Log.e(TAG, "Error while loading package" + e);
                continue;
            }
            if (resources.getIdentifier("bluetooth_and_wifi_stays_on_title",
                    "string", candidatePackage) == 0) {
                continue;
            }
            mBluetoothPackageName = candidatePackage;
        }
        return mBluetoothPackageName;
    }
}
+0 −175

File deleted.

Preview size limit exceeded, changes collapsed.

Loading