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
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5632e30..fe69d02b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -370,6 +370,13 @@
}
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"
description: "Allow users to swipe over the status bar chip to open the shade"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index ba59398..62297b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -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.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 @@
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 @@
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 @@
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 @@
@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 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.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
new file mode 100644
index 0000000..0577f49
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicyRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * 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)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
new file mode 100644
index 0000000..cdd5296
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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)
+ }
+}