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

Commit e3d259ba authored by amehfooz's avatar amehfooz
Browse files

Use AirplaneModeInteractor to update airplane icon in the status bar

Change-Id: I0dd3da2d42c68f79d6f49ebe73f5ce8abdc6bbb3
Bug: 264539100
Test: Made sure the airplane icon is updated in the status bar
when it is toggled from the QS Tile and the settings app.
This was done on a Pixel tablet.
Flag: com.android.systemui.status_bar_signal_policy_refactor
parent 2c2767ec
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -369,6 +369,13 @@ flag {
    bug: "354930838"
}

flag {
   name: "status_bar_signal_policy_refactor"
   namespace: "systemui"
   description: "Use a settings observer for airplane mode and make StatusBarSignalPolicy startable"
   bug: "264539100"
}

flag {
    name: "status_bar_swipe_over_chip"
    namespace: "systemui"
+38 −6
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.statusbar.phone;

import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;

import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
@@ -29,10 +31,12 @@ import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.CarrierConfigTracker;
import com.android.systemui.util.kotlin.JavaAdapter;

import java.util.ArrayList;
import java.util.List;
@@ -61,15 +65,14 @@ public class StatusBarSignalPolicy implements SignalCallback,
    private final Handler mHandler = Handler.getMain();
    private final CarrierConfigTracker mCarrierConfigTracker;
    private final TunerService mTunerService;
    private final JavaAdapter mJavaAdapter;
    private final AirplaneModeInteractor mAirplaneModeInteractor;

    private boolean mHideAirplane;
    private boolean mHideMobile;
    private boolean mHideEthernet;
    private boolean mActivityEnabled;

    // Track as little state as possible, and only for padding purposes
    private boolean mIsAirplaneMode = false;

    private ArrayList<CallIndicatorIconState> mCallIndicatorStates = new ArrayList<>();
    private boolean mInitialized;

@@ -80,15 +83,19 @@ public class StatusBarSignalPolicy implements SignalCallback,
            CarrierConfigTracker carrierConfigTracker,
            NetworkController networkController,
            SecurityController securityController,
            TunerService tunerService
            TunerService tunerService,
            JavaAdapter javaAdapter,
            AirplaneModeInteractor airplaneModeInteractor
    ) {
        mContext = context;

        mIconController = iconController;
        mCarrierConfigTracker = carrierConfigTracker;
        mJavaAdapter = javaAdapter;
        mNetworkController = networkController;
        mSecurityController = securityController;
        mTunerService = tunerService;
        mAirplaneModeInteractor = airplaneModeInteractor;

        mSlotAirplane = mContext.getString(com.android.internal.R.string.status_bar_airplane);
        mSlotMobile   = mContext.getString(com.android.internal.R.string.status_bar_mobile);
@@ -109,6 +116,12 @@ public class StatusBarSignalPolicy implements SignalCallback,
        mTunerService.addTunable(this, StatusBarIconController.ICON_HIDE_LIST);
        mNetworkController.addCallback(this);
        mSecurityController.addCallback(this);

        if (statusBarSignalPolicyRefactor()) {
            mJavaAdapter.alwaysCollectFlow(
                    mAirplaneModeInteractor.isAirplaneMode(),
                    this::updateAirplaneModeIcon);
        }
    }

    public void destroy() {
@@ -222,15 +235,19 @@ public class StatusBarSignalPolicy implements SignalCallback,

    @Override
    public void setIsAirplaneMode(IconState icon) {
        if (statusBarSignalPolicyRefactor()) {
            return;
        }

        if (DEBUG) {
            Log.d(TAG, "setIsAirplaneMode: "
                    + "icon = " + (icon == null ? "" : icon.toString()));
        }
        mIsAirplaneMode = icon.visible && !mHideAirplane;
        boolean isAirplaneMode = icon.visible && !mHideAirplane;
        int resId = icon.icon;
        String description = icon.contentDescription;

        if (mIsAirplaneMode && resId > 0) {
        if (isAirplaneMode && resId > 0) {
            mIconController.setIcon(mSlotAirplane, resId, description);
            mIconController.setIconVisibility(mSlotAirplane, true);
        } else {
@@ -238,6 +255,21 @@ public class StatusBarSignalPolicy implements SignalCallback,
        }
    }

    public void updateAirplaneModeIcon(boolean isAirplaneModeOn) {
        if (StatusBarSignalPolicyRefactor.isUnexpectedlyInLegacyMode()) {
            return;
        }

        boolean isAirplaneMode = isAirplaneModeOn && !mHideAirplane;
        mIconController.setIconVisibility(mSlotAirplane, isAirplaneMode);
        if (isAirplaneMode) {
            mIconController.setIcon(
                    mSlotAirplane,
                    TelephonyIcons.FLIGHT_MODE_ICON,
                    mContext.getString(R.string.accessibility_airplane_mode));
        }
    }

    /**
     * Stores the statusbar state for no Calling & SMS.
     */
+53 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.systemui.statusbar.phone

import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils

/** Helper for reading or using the status_bar_signal_policy_refactor flag state. */
@Suppress("NOTHING_TO_INLINE")
object StatusBarSignalPolicyRefactor {
    /** The aconfig flag name */
    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR

    /** A token used for dependency declaration */
    val token: FlagToken
        get() = FlagToken(FLAG_NAME, isEnabled)

    /** Is the refactor enabled */
    @JvmStatic
    inline val isEnabled
        get() = Flags.statusBarSignalPolicyRefactor()

    /**
     * Called to ensure code is only run when the flag is enabled. This protects users from the
     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
     * build to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun isUnexpectedlyInLegacyMode() =
        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)

    /**
     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
     * the flag is enabled to ensure that the refactor author catches issues in testing.
     */
    @JvmStatic
    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
}
+153 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.systemui.statusbar

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.Flags.FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.statusbar.connectivity.IconState
import com.android.systemui.statusbar.connectivity.NetworkController
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy_Factory
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
import com.android.systemui.statusbar.policy.securityController
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.verifyZeroInteractions
import kotlin.test.Test

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class StatusBarSignalPolicyTest : SysuiTestCase() {
    private val kosmos = Kosmos().also { it.testCase = this }

    private lateinit var underTest: StatusBarSignalPolicy

    private val testScope = TestScope()

    private val javaAdapter = JavaAdapter(testScope.backgroundScope)
    private val airplaneModeInteractor = kosmos.airplaneModeInteractor
    private val securityController = kosmos.securityController

    private val tunerService = mock<TunerService>()
    private val statusBarIconController = mock<StatusBarIconController>()
    private val networkController = mock<NetworkController>()
    private val carrierConfigTracker = mock<CarrierConfigTracker>()

    private var slotAirplane: String? = null

    @Before
    fun setup() {
        underTest =
            StatusBarSignalPolicy_Factory.newInstance(
                mContext,
                statusBarIconController,
                carrierConfigTracker,
                networkController,
                securityController,
                tunerService,
                javaAdapter,
                airplaneModeInteractor,
            )

        slotAirplane = mContext.getString(R.string.status_bar_airplane)
    }

    @Test
    @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
    fun airplaneModeViaInteractor_statusBarSignalPolicyRefactorFlagEnabled_iconUpdated() =
        testScope.runTest {
            underTest.init()
            airplaneModeInteractor.setIsAirplaneMode(true)
            runCurrent()
            verify(statusBarIconController).setIconVisibility(slotAirplane, true)

            airplaneModeInteractor.setIsAirplaneMode(false)
            runCurrent()
            verify(statusBarIconController).setIconVisibility(slotAirplane, false)
        }

    @Test
    @EnableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
    fun airplaneModeViaSignalCallback_statusBarSignalPolicyRefactorFlagEnabled_iconNotUpdated() =
        testScope.runTest {
            underTest.init()
            runCurrent()
            clearInvocations(statusBarIconController)

            // Make sure the legacy code path does not change airplane mode when the refactor
            // flag is enabled.
            underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
            runCurrent()
            verifyZeroInteractions(statusBarIconController)

            underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
            runCurrent()
            verifyZeroInteractions(statusBarIconController)
        }

    @Test
    @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
    fun airplaneModeViaSignalCallback_statusBarSignalPolicyRefactorFlagDisabled_iconUpdated() =
        testScope.runTest {
            underTest.init()

            underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
            runCurrent()
            verify(statusBarIconController).setIconVisibility(slotAirplane, true)

            underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
            runCurrent()
            verify(statusBarIconController).setIconVisibility(slotAirplane, false)
        }

    @Test
    @DisableFlags(FLAG_STATUS_BAR_SIGNAL_POLICY_REFACTOR)
    fun airplaneModeViaInteractor_statusBarSignalPolicyRefactorFlagDisabled_iconNotUpdated() =
        testScope.runTest {
            underTest.init()

            // Make sure changing airplane mode from airplaneModeRepository does nothing
            // if the StatusBarSignalPolicyRefactor is not enabled.
            airplaneModeInteractor.setIsAirplaneMode(true)
            runCurrent()
            verifyZeroInteractions(statusBarIconController)

            airplaneModeInteractor.setIsAirplaneMode(false)
            runCurrent()
            verifyZeroInteractions(statusBarIconController)
        }
}