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

Commit 1e6fe49f authored by William Escande's avatar William Escande
Browse files

Send airplane notification from app

Stop using the airplane notification ressources from Bluetooth app
inside the system server and instead start an independant service in the
app to do it

Bug: 290403852
Bug: 262605980
Bug: 303552318
Test: atest ServiceBluetoothTest
Test: atest BluetoothAirplaneModeListenerTest
Test: Manual: change airplane mode and observe the notification
Change-Id: I804826273d6b8ef5ddb6c03444e78474ed88f58e
parent 7b1776a2
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -206,6 +206,7 @@ android_app {
    srcs: [
    srcs: [
        ":statslog-bluetooth-java-gen",
        ":statslog-bluetooth-java-gen",
        ":statslog-bt-restricted-java-gen",
        ":statslog-bt-restricted-java-gen",
        ":system-messages-proto-src",
        "proto/keystore.proto",
        "proto/keystore.proto",
        "src/**/*.java",
        "src/**/*.java",
    ],
    ],
+10 −0
Original line number Original line Diff line number Diff line
@@ -103,6 +103,16 @@
            </intent-filter>
            </intent-filter>
        </service>
        </service>


        <service android:process="@string/process"
             android:name="com.android.bluetooth.airplane.NotificationHelperService"
             android:label="Airplane Notification Helper"
             android:exported="true"
             android:permission="android.permission.BLUETOOTH_PRIVILEGED">
            <intent-filter>
                <action android:name="android.bluetooth.airplane.action.SEND_NOTIFICATION"/>
            </intent-filter>
        </service>

        <!--  Advanced Audio Distribution Profile (A2DP) source Profile Service  -->
        <!--  Advanced Audio Distribution Profile (A2DP) source Profile Service  -->
        <service android:process="@string/process"
        <service android:process="@string/process"
             android:name="com.android.bluetooth.a2dp.A2dpService"
             android:name="com.android.bluetooth.a2dp.A2dpService"
+142 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2023 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.bluetooth.airplane;

import static java.util.Objects.requireNonNull;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Pair;

import com.android.bluetooth.R;
import com.android.internal.messages.SystemMessageProto.SystemMessage;

import java.util.Map;

public class NotificationHelperService extends Service {
    private static final String TAG = NotificationHelperService.class.getSimpleName();

    // Keeps track of whether wifi and bt remains on notification was shown
    private static final String APM_WIFI_BT_NOTIFICATION = "apm_wifi_bt_notification";
    // Keeps track of whether bt remains on notification was shown
    private static final String APM_BT_NOTIFICATION = "apm_bt_notification";
    // Keeps track of whether user enabling bt notification was shown
    private static final String APM_BT_ENABLED_NOTIFICATION = "apm_bt_enabled_notification";

    private static final String NOTIFICATION_TAG = "com.android.bluetooth";
    private static final String APM_NOTIFICATION_CHANNEL = "apm_notification_channel";
    private static final String APM_NOTIFICATION_GROUP = "apm_notification_group";
    private static final String HELP_PAGE_URL =
            "https://support.google.com/pixelphone/answer/12639358";

    private static final Map<String, Pair<Integer /* titleId */, Integer /* messageId */>>
            NOTIFICATION_MAP =
                    Map.of(
                            APM_WIFI_BT_NOTIFICATION,
                            Pair.create(
                                    R.string.bluetooth_and_wifi_stays_on_title,
                                    R.string.bluetooth_and_wifi_stays_on_message),
                            APM_BT_NOTIFICATION,
                            Pair.create(
                                    R.string.bluetooth_stays_on_title,
                                    R.string.bluetooth_stays_on_message),
                            APM_BT_ENABLED_NOTIFICATION,
                            Pair.create(
                                    R.string.bluetooth_enabled_apm_title,
                                    R.string.bluetooth_enabled_apm_message));

    @Override
    public IBinder onBind(Intent intent) {
        return null; // This is not a bound service
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        sendAirplaneModeNotification(
                intent.getStringExtra("android.bluetooth.airplane.extra.NOTIFICATION_STATE"));
        return Service.START_NOT_STICKY;
    }

    private void sendAirplaneModeNotification(String notificationState) {
        String logHeader = "sendAirplaneModeNotification(" + notificationState + "): ";
        Pair<Integer, Integer> notificationContent = NOTIFICATION_MAP.get(notificationState);
        if (notificationContent == null) {
            Log.e(TAG, logHeader + "unknown action");
            return;
        }

        if (!isFirstTimeNotification(notificationState)) {
            Log.d(TAG, logHeader + "already displayed");
            return;
        }
        Settings.Secure.putInt(getContentResolver(), notificationState, 1);

        Log.d(TAG, logHeader + "sending");

        NotificationManager notificationManager =
                requireNonNull(getSystemService(NotificationManager.class));
        for (StatusBarNotification notification : notificationManager.getActiveNotifications()) {
            if (NOTIFICATION_TAG.equals(notification.getTag())) {
                notificationManager.cancel(NOTIFICATION_TAG, notification.getId());
            }
        }

        notificationManager.createNotificationChannel(
                new NotificationChannel(
                        APM_NOTIFICATION_CHANNEL,
                        APM_NOTIFICATION_GROUP,
                        NotificationManager.IMPORTANCE_HIGH));

        String title = getString(notificationContent.first);
        String message = getString(notificationContent.second);

        notificationManager.notify(
                NOTIFICATION_TAG,
                SystemMessage.ID.NOTE_BT_APM_NOTIFICATION_VALUE,
                new Notification.Builder(this, APM_NOTIFICATION_CHANNEL)
                        .setAutoCancel(true)
                        .setLocalOnly(true)
                        .setContentTitle(title)
                        .setContentText(message)
                        .setContentIntent(
                                PendingIntent.getActivity(
                                        this,
                                        PendingIntent.FLAG_UPDATE_CURRENT,
                                        new Intent(Intent.ACTION_VIEW)
                                                .setData(Uri.parse(HELP_PAGE_URL))
                                                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
                                        PendingIntent.FLAG_IMMUTABLE))
                        .setVisibility(Notification.VISIBILITY_PUBLIC)
                        .setStyle(new Notification.BigTextStyle().bigText(message))
                        .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
                        .build());
    }

    /** Return whether the notification has been shown */
    private boolean isFirstTimeNotification(String name) {
        return Settings.Secure.getInt(getContentResolver(), name, 0) == 0;
    }
}
+8 −0
Original line number Original line Diff line number Diff line
package: "com.android.bluetooth.flags"
package: "com.android.bluetooth.flags"


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

flag {
flag {
    name: "use_new_satellite_mode"
    name: "use_new_satellite_mode"
    namespace: "bluetooth"
    namespace: "bluetooth"
    description: "Use the new implemention of satellite mode"
    description: "Use the new implemention of satellite mode"
    bug: "289584302"
    bug: "289584302"
}
}
+19 −1
Original line number Original line Diff line number Diff line
@@ -31,6 +31,7 @@ import android.provider.Settings;
import android.util.Log;
import android.util.Log;


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


/**
/**
@@ -91,6 +92,7 @@ class BluetoothAirplaneModeListener extends Handler {
    private long mApmEnabledTime = 0;
    private long mApmEnabledTime = 0;


    private final BluetoothManagerService mBluetoothManager;
    private final BluetoothManagerService mBluetoothManager;
    private final FeatureFlags mFeatureFlags;
    private final Context mContext;
    private final Context mContext;
    private BluetoothModeChangeHelper mAirplaneHelper;
    private BluetoothModeChangeHelper mAirplaneHelper;
    private final BluetoothNotificationManager mNotificationManager;
    private final BluetoothNotificationManager mNotificationManager;
@@ -103,10 +105,12 @@ class BluetoothAirplaneModeListener extends Handler {
            BluetoothManagerService service,
            BluetoothManagerService service,
            Looper looper,
            Looper looper,
            Context context,
            Context context,
            BluetoothNotificationManager notificationManager) {
            BluetoothNotificationManager notificationManager,
            FeatureFlags featureFlags) {
        super(looper);
        super(looper);


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


@@ -222,6 +226,14 @@ class BluetoothAirplaneModeListener extends Handler {
            }
            }
            return;
            return;
        } else {
        } else {
            if (mFeatureFlags.airplaneRessourcesInApp()) {
                if (isWifiEnabledOnApm()) {
                    mBluetoothManager.sendAirplaneModeNotification(APM_WIFI_BT_NOTIFICATION);
                } else {
                    mBluetoothManager.sendAirplaneModeNotification(APM_BT_NOTIFICATION);
                }
                return;
            }
            if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) {
            if (isWifiEnabledOnApm() && isFirstTimeNotification(APM_WIFI_BT_NOTIFICATION)) {
                try {
                try {
                    sendApmNotification(
                    sendApmNotification(
@@ -317,6 +329,12 @@ class BluetoothAirplaneModeListener extends Handler {
        if (isApmEnhancementEnabled()) {
        if (isApmEnhancementEnabled()) {
            setSettingsSecureInt(BLUETOOTH_APM_STATE, isOn ? BLUETOOTH_ON_APM : BLUETOOTH_OFF_APM);
            setSettingsSecureInt(BLUETOOTH_APM_STATE, isOn ? BLUETOOTH_ON_APM : BLUETOOTH_OFF_APM);
            setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED);
            setSettingsSecureInt(APM_USER_TOGGLED_BLUETOOTH, USED);
            if (mFeatureFlags.airplaneRessourcesInApp()) {
                if (isOn) {
                    mBluetoothManager.sendAirplaneModeNotification(APM_BT_ENABLED_NOTIFICATION);
                }
                return;
            }
            if (isOn && isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) {
            if (isOn && isFirstTimeNotification(APM_BT_ENABLED_NOTIFICATION)) {
                // waive WRITE_SECURE_SETTINGS permission check
                // waive WRITE_SECURE_SETTINGS permission check
                final long callingIdentity = Binder.clearCallingIdentity();
                final long callingIdentity = Binder.clearCallingIdentity();
Loading