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

Commit 87ab6a78 authored by Alina Zaidi's avatar Alina Zaidi
Browse files

[Dev option] Create DesktopModeFlags to check enabled status of DW subfeatures.

The class just reads flags if DW dev option flag is disabled

Update DesktopModeStatus callpoint for Flags::enableDesktopWindowingMode to use DesktopModeFlags.

Test: Added tests
Bug: 348193756
Flag:  com.android.window.flags.show_desktop_windowing_dev_option

Change-Id: I369b77bc1b12b3d2b6e208abae4e698be95f14b0
parent c2b3e96c
Loading
Loading
Loading
Loading
+3 −17
Original line number Diff line number Diff line
@@ -16,21 +16,19 @@

package com.android.wm.shell.shared;

import static android.provider.Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES;

import android.annotation.NonNull;
import android.content.Context;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;

/**
 * Constants for desktop mode feature
 */
// TODO(b/237575897): Move this file to the `com.android.wm.shell.shared.desktopmode` package
public class DesktopModeStatus {

    private static final String TAG = "DesktopModeStatus";
@@ -178,19 +176,7 @@ public class DesktopModeStatus {
    public static boolean canEnterDesktopMode(@NonNull Context context) {
        if (!isDeviceEligibleForDesktopMode(context)) return false;

        // If dev option has ever been manually toggled by the user, return its value
        // TODO(b/348193756) : Move the logic for DW override based on toggle overides to a common
        //  infrastructure and add caching for the computation
        int defaultOverrideState = -1;
        int toggleState = Settings.Global.getInt(context.getContentResolver(),
                DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, defaultOverrideState);
        if (toggleState != defaultOverrideState) {
            Log.d(TAG, "Using Desktop mode dev option overridden state");
            return toggleState != 0;
        }

        // Return Desktop windowing flag value
        return isDesktopModeFlagEnabled();
        return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context);
    }

    /**
+110 −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.wm.shell.shared.desktopmode

import android.content.Context
import android.provider.Settings
import android.util.Log
import com.android.window.flags.Flags
import com.android.wm.shell.shared.DesktopModeStatus

/*
 * A shared class to check desktop mode flags state.
 *
 * The class computes whether a Desktop Windowing flag should be enabled by using the aconfig flag
 * value and the developer option override state (if applicable).
 **/
enum class DesktopModeFlags(
    // Function called to obtain aconfig flag value.
    private val flagFunction: () -> Boolean,
    // Whether the flag state should be affected by developer option.
    private val shouldOverrideByDevOption: Boolean
) {
  // All desktop mode related flags will be added here
  DESKTOP_WINDOWING_MODE(DesktopModeStatus::isDesktopModeFlagEnabled, true);

  private val TAG = "DesktopModeFlags"

  // Cache for toggle override, which is initialized once on its first access. It needs to be refreshed
  // only on reboots as overridden state takes effect on reboots.
  private var cachedToggleOverride: ToggleOverride? = null

  /**
   * Determines state of flag based on the actual flag and desktop mode developer option
   * overrides.
   *
   * Note, this method makes sure that a constant developer toggle overrides is read until reboot.
   */
  fun isEnabled(context: Context): Boolean =
      if (!Flags.showDesktopWindowingDevOption() ||
          !shouldOverrideByDevOption ||
          context.contentResolver == null) {
        flagFunction()
      } else {
        when (getToggleOverride(context)) {
          ToggleOverride.OVERRIDE_UNSET -> flagFunction()
          ToggleOverride.OVERRIDE_OFF -> false
          ToggleOverride.OVERRIDE_ON -> true
        }
      }

  private fun getToggleOverride(context: Context): ToggleOverride {
    val override = cachedToggleOverride ?: run {
      // Cache toggle override the first time we encounter context. Override does not change
      // with context, as context is just used to fetch a system property.

      // TODO(b/348193756): Cache a persistent value for Settings.Global until reboot. Current
      //  cache will change with process restart.
      val toggleOverride =
          Settings.Global.getInt(
              context.contentResolver,
              Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES,
              ToggleOverride.OVERRIDE_UNSET.setting)

      val newOverride =
          settingToToggleOverrideMap[toggleOverride]
              ?: run {
                Log.w(TAG, "Unknown toggleOverride $toggleOverride")
                ToggleOverride.OVERRIDE_UNSET
              }
      cachedToggleOverride = newOverride
      Log.d(TAG, "Toggle override initialized to: $newOverride")
      newOverride
    }

    return override
  }

  // TODO(b/348193756): Share ToggleOverride enum with Settings
  // 'DesktopModePreferenceController'
  /**
   * Override state of desktop mode developer option toggle.
   *
   * @property setting The integer value that is associated with the developer option toggle
   *   override
   */
  enum class ToggleOverride(val setting: Int) {
    /** No override is set. */
    OVERRIDE_UNSET(-1),
    /** Override to off. */
    OVERRIDE_OFF(0),
    /** Override to on. */
    OVERRIDE_ON(1)
  }

  private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting }
}
+1 −0
Original line number Diff line number Diff line
file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -264,7 +264,7 @@ class DesktopTasksControllerTest : ShellTestCase() {

  @Test
  fun instantiate_flagOff_doNotAddInitCallback() {
    whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(false)
    whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
    clearInvocations(shellInit)

    createController()
+225 −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.wm.shell.shared.desktopmode

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON
import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness

/**
 * Test class for [DesktopModeFlags]
 *
 * Usage: atest WMShellUnitTests:DesktopModeFlagsTest
 */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DesktopModeFlagsTest : ShellTestCase() {

  @JvmField @Rule val setFlagsRule = SetFlagsRule()

  @Before
  fun setUp() {
    resetCache()
  }

  // TODO(b/348193756): Add tests
  // isEnabled_flagNotOverridable_overrideOff_featureFlagOn_returnsTrue and
  // isEnabled_flagNotOverridable_overrideOn_featureFlagOff_returnsFalse after adding non
  // overridable flags to DesktopModeFlags.

  @Test
  @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
  @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() {
    setOverride(OVERRIDE_OFF.setting)

    // In absence of dev options, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
  }

  @Test
  @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() {
    setOverride(OVERRIDE_ON.setting)

    // In absence of dev options, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_unsetOverride_featureFlagOn_returnsTrue() {
    setOverride(OVERRIDE_UNSET.setting)

    // For overridableFlag, for unset overrides, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_unsetOverride_featureFlagOff_returnsFalse() {
    setOverride(OVERRIDE_UNSET.setting)

    // For overridableFlag, for unset overrides, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_noOverride_featureFlagOn_returnsTrue() {
    setOverride(null)

    // For overridableFlag, in absence of overrides, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_noOverride_featureFlagOff_returnsFalse() {
    setOverride(null)

    // For overridableFlag, in absence of overrides, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_unrecognizableOverride_featureFlagOn_returnsTrue() {
    setOverride(-2)

    // For overridableFlag, for recognizable overrides, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_unrecognizableOverride_featureFlagOff_returnsFalse() {
    setOverride(-2)

    // For overridableFlag, for recognizable overrides, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_overrideOff_featureFlagOn_returnsFalse() {
    setOverride(OVERRIDE_OFF.setting)

    // For overridableFlag, follow override if they exist
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_overrideOn_featureFlagOff_returnsTrue() {
    setOverride(OVERRIDE_ON.setting)

    // For overridableFlag, follow override if they exist
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() {
    setOverride(OVERRIDE_OFF.setting)

    // For overridableFlag, follow override if they exist
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()

    setOverride(OVERRIDE_ON.setting)

    // Keep overrides constant through the process
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
  @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() {
    setOverride(OVERRIDE_ON.setting)

    // For overridableFlag, follow override if they exist
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()

    setOverride(OVERRIDE_OFF.setting)

    // Keep overrides constant through the process
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()
  }

  @Test
  @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
  fun isEnabled_flagOverridable_noOverride_featureFlagOnThenOff_returnsTrueAndFalse() {
    setOverride(null)
    // For overridableFlag, in absence of overrides, follow flag
    assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue()

    val mockitoSession: StaticMockitoSession =
        ExtendedMockito.mockitoSession()
            .strictness(Strictness.LENIENT)
            .spyStatic(DesktopModeStatus::class.java)
            .startMocking()
    try {
      // No caching of flags
      whenever(DesktopModeStatus.isDesktopModeFlagEnabled()).thenReturn(false)
      assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse()
    } finally {
      mockitoSession.finishMocking()
    }
  }

  private fun setOverride(setting: Int?) {
    val contentResolver = mContext.contentResolver
    val key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES
    if (setting == null) {
      Settings.Global.putString(contentResolver, key, null)
    } else {
      Settings.Global.putInt(contentResolver, key, setting)
    }
  }

  private fun resetCache() {
    val cacheToggleOverride =
        DESKTOP_WINDOWING_MODE::class.java.getDeclaredField("cachedToggleOverride")
    cacheToggleOverride.isAccessible = true
    cacheToggleOverride.set(DESKTOP_WINDOWING_MODE, null)
  }
}
Loading