Snap for 12637843 from 453aa677e6
to 25Q1-release
Change-Id: I752fe2604a67322c82f340e18e20b74c7e6de159
This commit is contained in:
@@ -199,10 +199,11 @@
|
|||||||
android:title="@string/enable_terminal_title"
|
android:title="@string/enable_terminal_title"
|
||||||
android:summary="@string/enable_terminal_summary" />
|
android:summary="@string/enable_terminal_summary" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<Preference
|
||||||
android:key="enable_linux_terminal"
|
android:key="linux_terminal"
|
||||||
android:title="@string/enable_linux_terminal_title"
|
android:title="@string/enable_linux_terminal_title"
|
||||||
android:summary="@string/enable_linux_terminal_summary" />
|
android:summary="@string/enable_linux_terminal_summary"
|
||||||
|
android:fragment="com.android.settings.development.linuxterminal.LinuxTerminalDashboardFragment" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:key="bugreport_in_power"
|
android:key="bugreport_in_power"
|
||||||
|
27
res/xml/linux_terminal_settings.xml
Normal file
27
res/xml/linux_terminal_settings.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:settings="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:title="@string/enable_linux_terminal_title">
|
||||||
|
|
||||||
|
<com.android.settings.widget.SettingsMainSwitchPreference
|
||||||
|
android:key="enable_linux_terminal"
|
||||||
|
android:title="@string/enable_linux_terminal_summary"
|
||||||
|
settings:controller="com.android.settings.development.linuxterminal.EnableLinuxTerminalPreferenceController" />
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
@@ -24,7 +24,8 @@
|
|||||||
|
|
||||||
<com.android.settingslib.widget.TopIntroPreference
|
<com.android.settingslib.widget.TopIntroPreference
|
||||||
android:key="gesture_one_handed_mode_intro"
|
android:key="gesture_one_handed_mode_intro"
|
||||||
android:title="@string/one_handed_mode_intro_text"/>
|
android:title="@string/one_handed_mode_intro_text"
|
||||||
|
settings:searchable="false"/>
|
||||||
|
|
||||||
<com.android.settingslib.widget.IllustrationPreference
|
<com.android.settingslib.widget.IllustrationPreference
|
||||||
android:key="one_handed_header"
|
android:key="one_handed_header"
|
||||||
|
@@ -19,12 +19,18 @@ package com.android.settings.accessibility;
|
|||||||
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
|
||||||
|
|
||||||
import android.app.settings.SettingsEnums;
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.FeatureFlagUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.dashboard.RestrictedDashboardFragment;
|
import com.android.settings.dashboard.RestrictedDashboardFragment;
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
|
import com.android.settingslib.search.SearchIndexable;
|
||||||
|
|
||||||
/** Settings fragment containing bluetooth audio routing. */
|
/** Settings fragment containing bluetooth audio routing. */
|
||||||
|
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
|
||||||
public class AccessibilityAudioRoutingFragment extends RestrictedDashboardFragment {
|
public class AccessibilityAudioRoutingFragment extends RestrictedDashboardFragment {
|
||||||
private static final String TAG = "AccessibilityAudioRoutingFragment";
|
private static final String TAG = "AccessibilityAudioRoutingFragment";
|
||||||
|
|
||||||
@@ -47,6 +53,25 @@ public class AccessibilityAudioRoutingFragment extends RestrictedDashboardFragme
|
|||||||
return TAG;
|
return TAG;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
@VisibleForTesting
|
||||||
new BaseSearchIndexProvider(R.xml.accessibility_audio_routing_fragment);
|
static boolean isPageSearchEnabled(Context context) {
|
||||||
|
if (!FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final HearingAidHelper mHelper = new HearingAidHelper(context);
|
||||||
|
return mHelper.isHearingAidSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||||
|
new BaseSearchIndexProvider(R.xml.accessibility_audio_routing_fragment) {
|
||||||
|
@Override
|
||||||
|
protected boolean isPageSearchEnabled(Context context) {
|
||||||
|
if (Flags.fixA11ySettingsSearch()) {
|
||||||
|
return AccessibilityAudioRoutingFragment.isPageSearchEnabled(context);
|
||||||
|
} else {
|
||||||
|
return super.isPageSearchEnabled(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,6 @@ import android.bluetooth.BluetoothAdapter;
|
|||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.Spatializer;
|
import android.media.Spatializer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
@@ -28,7 +27,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import com.android.settings.SettingsPreferenceFragment;
|
import com.android.settings.SettingsPreferenceFragment;
|
||||||
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor;
|
|
||||||
import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
|
import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter;
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||||
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository;
|
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository;
|
||||||
@@ -98,13 +96,6 @@ public interface BluetoothFeatureProvider {
|
|||||||
@NonNull BluetoothAdapter bluetoothAdapter,
|
@NonNull BluetoothAdapter bluetoothAdapter,
|
||||||
@NonNull CoroutineScope scope);
|
@NonNull CoroutineScope scope);
|
||||||
|
|
||||||
/** Gets spatial audio interactor. */
|
|
||||||
@NonNull
|
|
||||||
SpatialAudioInteractor getSpatialAudioInteractor(
|
|
||||||
@NonNull Context context,
|
|
||||||
@NonNull AudioManager audioManager,
|
|
||||||
@NonNull CoroutineScope scope);
|
|
||||||
|
|
||||||
/** Gets device details fragment layout formatter. */
|
/** Gets device details fragment layout formatter. */
|
||||||
@NonNull
|
@NonNull
|
||||||
DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter(
|
DeviceDetailsFragmentFormatter getDeviceDetailsFragmentFormatter(
|
||||||
|
@@ -22,20 +22,14 @@ import android.content.Context
|
|||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.Spatializer
|
import android.media.Spatializer
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import com.android.settings.SettingsPreferenceFragment
|
import com.android.settings.SettingsPreferenceFragment
|
||||||
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
|
|
||||||
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractorImpl
|
|
||||||
import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter
|
import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatter
|
||||||
import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl
|
import com.android.settings.bluetooth.ui.view.DeviceDetailsFragmentFormatterImpl
|
||||||
import com.android.settingslib.bluetooth.BluetoothUtils
|
import com.android.settingslib.bluetooth.BluetoothUtils
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
||||||
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
|
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository
|
||||||
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepositoryImpl
|
import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepositoryImpl
|
||||||
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
|
|
||||||
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -82,21 +76,6 @@ open class BluetoothFeatureProviderImpl : BluetoothFeatureProvider {
|
|||||||
): DeviceSettingRepository =
|
): DeviceSettingRepository =
|
||||||
DeviceSettingRepositoryImpl(context, bluetoothAdapter, scope, Dispatchers.IO)
|
DeviceSettingRepositoryImpl(context, bluetoothAdapter, scope, Dispatchers.IO)
|
||||||
|
|
||||||
override fun getSpatialAudioInteractor(
|
|
||||||
context: Context,
|
|
||||||
audioManager: AudioManager,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
): SpatialAudioInteractor {
|
|
||||||
return SpatialAudioInteractorImpl(
|
|
||||||
context, audioManager,
|
|
||||||
SpatializerInteractor(
|
|
||||||
SpatializerRepositoryImpl(
|
|
||||||
getSpatializer(context),
|
|
||||||
Dispatchers.IO
|
|
||||||
)
|
|
||||||
), scope, Dispatchers.IO)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDeviceDetailsFragmentFormatter(
|
override fun getDeviceDetailsFragmentFormatter(
|
||||||
context: Context,
|
context: Context,
|
||||||
fragment: SettingsPreferenceFragment,
|
fragment: SettingsPreferenceFragment,
|
||||||
|
@@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.settings.bluetooth.domain.interactor
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.AudioManager
|
|
||||||
import android.util.Log
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settingslib.bluetooth.BluetoothUtils
|
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
|
||||||
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
|
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
|
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
|
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
|
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
|
|
||||||
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
|
||||||
import kotlinx.coroutines.flow.onStart
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/** Provides device setting for spatial audio. */
|
|
||||||
interface SpatialAudioInteractor {
|
|
||||||
/** Gets device setting for spatial audio */
|
|
||||||
fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?>
|
|
||||||
}
|
|
||||||
|
|
||||||
class SpatialAudioInteractorImpl(
|
|
||||||
private val context: Context,
|
|
||||||
private val audioManager: AudioManager,
|
|
||||||
private val spatializerInteractor: SpatializerInteractor,
|
|
||||||
private val coroutineScope: CoroutineScope,
|
|
||||||
private val backgroundCoroutineContext: CoroutineContext,
|
|
||||||
) : SpatialAudioInteractor {
|
|
||||||
private val spatialAudioOffToggle =
|
|
||||||
ToggleModel(
|
|
||||||
context.getString(R.string.spatial_audio_multi_toggle_off),
|
|
||||||
DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio_off),
|
|
||||||
)
|
|
||||||
private val spatialAudioOnToggle =
|
|
||||||
ToggleModel(
|
|
||||||
context.getString(R.string.spatial_audio_multi_toggle_on),
|
|
||||||
DeviceSettingIcon.ResourceIcon(R.drawable.ic_spatial_audio),
|
|
||||||
)
|
|
||||||
private val headTrackingOnToggle =
|
|
||||||
ToggleModel(
|
|
||||||
context.getString(R.string.spatial_audio_multi_toggle_head_tracking_on),
|
|
||||||
DeviceSettingIcon.ResourceIcon(R.drawable.ic_head_tracking),
|
|
||||||
)
|
|
||||||
private val changes = MutableSharedFlow<Unit>()
|
|
||||||
|
|
||||||
override fun getDeviceSetting(cachedDevice: CachedBluetoothDevice): Flow<DeviceSettingModel?> =
|
|
||||||
changes
|
|
||||||
.onStart { emit(Unit) }
|
|
||||||
.combine(
|
|
||||||
isDeviceConnected(cachedDevice),
|
|
||||||
) { _, connected ->
|
|
||||||
if (connected) {
|
|
||||||
getSpatialAudioDeviceSettingModel(cachedDevice)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flowOn(backgroundCoroutineContext)
|
|
||||||
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = null)
|
|
||||||
|
|
||||||
private fun isDeviceConnected(cachedDevice: CachedBluetoothDevice): Flow<Boolean> =
|
|
||||||
callbackFlow {
|
|
||||||
val listener =
|
|
||||||
CachedBluetoothDevice.Callback { launch { send(cachedDevice.isConnected) } }
|
|
||||||
cachedDevice.registerCallback(context.mainExecutor, listener)
|
|
||||||
awaitClose { cachedDevice.unregisterCallback(listener) }
|
|
||||||
}
|
|
||||||
.onStart { emit(cachedDevice.isConnected) }
|
|
||||||
.flowOn(backgroundCoroutineContext)
|
|
||||||
|
|
||||||
private suspend fun getSpatialAudioDeviceSettingModel(
|
|
||||||
cachedDevice: CachedBluetoothDevice
|
|
||||||
): DeviceSettingModel? {
|
|
||||||
// TODO(b/343317785): use audio repository instead of calling AudioManager directly.
|
|
||||||
Log.i(TAG, "CachedDevice: $cachedDevice profiles: ${cachedDevice.profiles}")
|
|
||||||
val attributes =
|
|
||||||
BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
|
|
||||||
cachedDevice,
|
|
||||||
audioManager.getBluetoothAudioDeviceCategory(cachedDevice.address),
|
|
||||||
)
|
|
||||||
?: run {
|
|
||||||
Log.i(TAG, "No audio profiles in cachedDevice: ${cachedDevice.address}.")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, "Audio device attributes for ${cachedDevice.address}: $attributes.")
|
|
||||||
val spatialAudioAvailable = spatializerInteractor.isSpatialAudioAvailable(attributes)
|
|
||||||
if (!spatialAudioAvailable) {
|
|
||||||
Log.i(TAG, "Spatial audio is not available for ${cachedDevice.address}")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val headTrackingAvailable =
|
|
||||||
spatialAudioAvailable && spatializerInteractor.isHeadTrackingAvailable(attributes)
|
|
||||||
val toggles =
|
|
||||||
if (headTrackingAvailable) {
|
|
||||||
listOf(spatialAudioOffToggle, spatialAudioOnToggle, headTrackingOnToggle)
|
|
||||||
} else {
|
|
||||||
listOf(spatialAudioOffToggle, spatialAudioOnToggle)
|
|
||||||
}
|
|
||||||
val spatialAudioEnabled = spatializerInteractor.isSpatialAudioEnabled(attributes)
|
|
||||||
val headTrackingEnabled =
|
|
||||||
spatialAudioEnabled && spatializerInteractor.isHeadTrackingEnabled(attributes)
|
|
||||||
|
|
||||||
val activeIndex =
|
|
||||||
when {
|
|
||||||
headTrackingEnabled -> INDEX_HEAD_TRACKING_ENABLED
|
|
||||||
spatialAudioEnabled -> INDEX_SPATIAL_AUDIO_ON
|
|
||||||
else -> INDEX_SPATIAL_AUDIO_OFF
|
|
||||||
}
|
|
||||||
Log.i(
|
|
||||||
TAG,
|
|
||||||
"Head tracking available: $headTrackingAvailable, " +
|
|
||||||
"spatial audio enabled: $spatialAudioEnabled, " +
|
|
||||||
"head tracking enabled: $headTrackingEnabled",
|
|
||||||
)
|
|
||||||
return DeviceSettingModel.MultiTogglePreference(
|
|
||||||
cachedDevice = cachedDevice,
|
|
||||||
id = DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE,
|
|
||||||
title = context.getString(R.string.spatial_audio_multi_toggle_title),
|
|
||||||
toggles = toggles,
|
|
||||||
isActive = spatialAudioEnabled,
|
|
||||||
state = DeviceSettingStateModel.MultiTogglePreferenceState(activeIndex),
|
|
||||||
isAllowedChangingState = true,
|
|
||||||
updateState = { newState ->
|
|
||||||
coroutineScope.launch(backgroundCoroutineContext) {
|
|
||||||
Log.i(TAG, "Update spatial audio state: $newState")
|
|
||||||
when (newState.selectedIndex) {
|
|
||||||
INDEX_SPATIAL_AUDIO_OFF -> {
|
|
||||||
spatializerInteractor.setSpatialAudioEnabled(attributes, false)
|
|
||||||
}
|
|
||||||
INDEX_SPATIAL_AUDIO_ON -> {
|
|
||||||
spatializerInteractor.setSpatialAudioEnabled(attributes, true)
|
|
||||||
spatializerInteractor.setHeadTrackingEnabled(attributes, false)
|
|
||||||
}
|
|
||||||
INDEX_HEAD_TRACKING_ENABLED -> {
|
|
||||||
spatializerInteractor.setSpatialAudioEnabled(attributes, true)
|
|
||||||
spatializerInteractor.setHeadTrackingEnabled(attributes, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changes.emit(Unit)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "SpatialAudioInteractor"
|
|
||||||
private const val INDEX_SPATIAL_AUDIO_OFF = 0
|
|
||||||
private const val INDEX_SPATIAL_AUDIO_ON = 1
|
|
||||||
private const val INDEX_HEAD_TRACKING_ENABLED = 2
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.bluetooth.ui.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon
|
||||||
|
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiTogglePreference(pref: DeviceSettingPreferenceModel.MultiTogglePreference) {
|
||||||
|
Column(modifier = Modifier.padding(24.dp)) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
Row {
|
||||||
|
for ((idx, toggle) in pref.toggles.withIndex()) {
|
||||||
|
val selected = idx == pref.selectedIndex
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
.padding(start = if (idx == 0) 0.dp else 1.dp)
|
||||||
|
.height(56.dp)
|
||||||
|
.background(
|
||||||
|
Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
val startCornerRadius = if (idx == 0) 12.dp else 0.dp
|
||||||
|
val endCornerRadius = if (idx == pref.toggles.size - 1) 12.dp else 0.dp
|
||||||
|
Button(
|
||||||
|
onClick = { pref.onSelectedChange(idx) },
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
enabled = pref.isAllowedChangingState,
|
||||||
|
colors = getButtonColors(selected),
|
||||||
|
shape = RoundedCornerShape(
|
||||||
|
startCornerRadius,
|
||||||
|
endCornerRadius,
|
||||||
|
endCornerRadius,
|
||||||
|
startCornerRadius,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
DeviceSettingComposeIcon(
|
||||||
|
toggle.icon,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().defaultMinSize(32.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
for (toggle in pref.toggles) {
|
||||||
|
Text(
|
||||||
|
text = toggle.label,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
overflow = TextOverflow.Visible,
|
||||||
|
modifier = Modifier.weight(1f).padding(horizontal = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getButtonColors(isActive: Boolean) = if (isActive) {
|
||||||
|
ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ButtonDefaults.buttonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
)
|
||||||
|
}
|
@@ -1,280 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.settings.bluetooth.ui.composable
|
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.BasicAlertDialog
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.LocalContentColor
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.geometry.Rect
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.boundsInParent
|
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import androidx.compose.ui.semantics.contentDescription
|
|
||||||
import androidx.compose.ui.semantics.role
|
|
||||||
import androidx.compose.ui.semantics.semantics
|
|
||||||
import androidx.compose.ui.semantics.toggleableState
|
|
||||||
import androidx.compose.ui.state.ToggleableState
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import com.android.settings.R
|
|
||||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
|
||||||
import com.android.settings.bluetooth.ui.composable.Icon as DeviceSettingComposeIcon
|
|
||||||
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
|
||||||
import com.android.settingslib.spa.widget.dialog.getDialogWidth
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MultiTogglePreferenceGroup(
|
|
||||||
preferenceModels: List<DeviceSettingPreferenceModel.MultiTogglePreference>,
|
|
||||||
) {
|
|
||||||
var settingIdForPopUp by remember { mutableStateOf<Int?>(null) }
|
|
||||||
|
|
||||||
settingIdForPopUp?.let { id ->
|
|
||||||
preferenceModels.find { it.id == id && it.isAllowedChangingState }?.let {
|
|
||||||
dialog(it) { settingIdForPopUp = null }
|
|
||||||
} ?: run {
|
|
||||||
settingIdForPopUp = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(SettingsDimension.itemPadding),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
|
||||||
) {
|
|
||||||
preferenceModels.forEach { preferenceModel ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
verticalArrangement = Arrangement.Top,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Row {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.height(64.dp),
|
|
||||||
shape = RoundedCornerShape(28.dp),
|
|
||||||
color = MaterialTheme.colorScheme.surface) {
|
|
||||||
Button(
|
|
||||||
modifier =
|
|
||||||
Modifier.fillMaxSize().padding(8.dp).semantics {
|
|
||||||
role = Role.Switch
|
|
||||||
toggleableState =
|
|
||||||
if (!preferenceModel.isAllowedChangingState) {
|
|
||||||
ToggleableState.Indeterminate
|
|
||||||
} else if (preferenceModel.isActive) {
|
|
||||||
ToggleableState.On
|
|
||||||
} else {
|
|
||||||
ToggleableState.Off
|
|
||||||
}
|
|
||||||
contentDescription = preferenceModel.title
|
|
||||||
},
|
|
||||||
onClick = { settingIdForPopUp = preferenceModel.id },
|
|
||||||
enabled = preferenceModel.isAllowedChangingState,
|
|
||||||
shape = RoundedCornerShape(20.dp),
|
|
||||||
colors = getButtonColors(preferenceModel.isActive),
|
|
||||||
contentPadding = PaddingValues(0.dp)) {
|
|
||||||
DeviceSettingComposeIcon(
|
|
||||||
preferenceModel.toggles[preferenceModel.selectedIndex]
|
|
||||||
.icon,
|
|
||||||
modifier = Modifier.size(24.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row { Text(text = preferenceModel.title, fontSize = 12.sp) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun getButtonColors(isActive: Boolean) =
|
|
||||||
if (isActive) {
|
|
||||||
ButtonDefaults.buttonColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun dialog(
|
|
||||||
multiTogglePreference: DeviceSettingPreferenceModel.MultiTogglePreference,
|
|
||||||
onDismiss: () -> Unit
|
|
||||||
) {
|
|
||||||
BasicAlertDialog(
|
|
||||||
onDismissRequest = { onDismiss() },
|
|
||||||
modifier = Modifier.width(getDialogWidth()),
|
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
|
||||||
content = {
|
|
||||||
Card(
|
|
||||||
shape = RoundedCornerShape(28.dp),
|
|
||||||
modifier = Modifier.fillMaxWidth().height(192.dp),
|
|
||||||
content = {
|
|
||||||
Box {
|
|
||||||
Button(
|
|
||||||
onClick = { onDismiss() },
|
|
||||||
modifier = Modifier.padding(8.dp).align(Alignment.TopEnd).size(48.dp),
|
|
||||||
contentPadding = PaddingValues(12.dp),
|
|
||||||
colors =
|
|
||||||
ButtonDefaults.buttonColors(containerColor = Color.Transparent),
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painterResource(id = R.drawable.ic_close),
|
|
||||||
null,
|
|
||||||
tint = MaterialTheme.colorScheme.inverseSurface)
|
|
||||||
}
|
|
||||||
Box(modifier = Modifier.padding(horizontal = 8.dp, vertical = 20.dp)) {
|
|
||||||
dialogContent(multiTogglePreference)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun dialogContent(multiTogglePreference: DeviceSettingPreferenceModel.MultiTogglePreference) {
|
|
||||||
Column {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth().height(24.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
) {
|
|
||||||
Text(text = multiTogglePreference.title, fontSize = 16.sp)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
|
||||||
var selectedRect by remember { mutableStateOf<Rect?>(null) }
|
|
||||||
val offset =
|
|
||||||
selectedRect?.let { rect ->
|
|
||||||
animateFloatAsState(targetValue = rect.left, finishedListener = {}).value
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier =
|
|
||||||
Modifier.fillMaxWidth()
|
|
||||||
.height(64.dp)
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(28.dp)),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
offset?.let { offset ->
|
|
||||||
with(LocalDensity.current) {
|
|
||||||
Box(
|
|
||||||
modifier =
|
|
||||||
Modifier.offset(offset.toDp(), 0.dp)
|
|
||||||
.height(selectedRect!!.height.toDp())
|
|
||||||
.width(selectedRect!!.width.toDp())
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.tertiaryContainer,
|
|
||||||
shape = RoundedCornerShape(20.dp)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
for ((idx, toggle) in multiTogglePreference.toggles.withIndex()) {
|
|
||||||
val selected = idx == multiTogglePreference.selectedIndex
|
|
||||||
Column(
|
|
||||||
modifier =
|
|
||||||
Modifier.weight(1f)
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.height(48.dp)
|
|
||||||
.background(
|
|
||||||
Color.Transparent, shape = RoundedCornerShape(28.dp))
|
|
||||||
.onGloballyPositioned { layoutCoordinates ->
|
|
||||||
if (selected) {
|
|
||||||
selectedRect = layoutCoordinates.boundsInParent()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
multiTogglePreference.onSelectedChange(idx)
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
colors =
|
|
||||||
ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
contentColor = LocalContentColor.current),
|
|
||||||
) {
|
|
||||||
DeviceSettingComposeIcon(
|
|
||||||
toggle.icon, modifier = Modifier.size(24.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth().defaultMinSize(32.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
) {
|
|
||||||
for (toggle in multiTogglePreference.toggles) {
|
|
||||||
Text(
|
|
||||||
text = toggle.label,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
overflow = TextOverflow.Visible,
|
|
||||||
modifier = Modifier.weight(1f).padding(horizontal = 8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -43,7 +43,7 @@ import androidx.preference.Preference
|
|||||||
import com.android.settings.R
|
import com.android.settings.R
|
||||||
import com.android.settings.SettingsPreferenceFragment
|
import com.android.settings.SettingsPreferenceFragment
|
||||||
import com.android.settings.bluetooth.ui.composable.Icon
|
import com.android.settings.bluetooth.ui.composable.Icon
|
||||||
import com.android.settings.bluetooth.ui.composable.MultiTogglePreferenceGroup
|
import com.android.settings.bluetooth.ui.composable.MultiTogglePreference
|
||||||
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
|
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
|
||||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||||
@@ -56,11 +56,14 @@ import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSetti
|
|||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
|
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
|
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
|
||||||
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
import com.android.settingslib.spa.framework.theme.SettingsDimension
|
||||||
|
import com.android.settingslib.spa.widget.button.ActionButton
|
||||||
|
import com.android.settingslib.spa.widget.button.ActionButtons
|
||||||
import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
|
import com.android.settingslib.spa.widget.preference.Preference as SpaPreference
|
||||||
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
import com.android.settingslib.spa.widget.preference.PreferenceModel
|
||||||
import com.android.settingslib.spa.widget.preference.SwitchPreference
|
import com.android.settingslib.spa.widget.preference.SwitchPreference
|
||||||
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
|
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
|
||||||
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
|
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
|
||||||
|
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
|
||||||
import com.android.settingslib.spa.widget.ui.Footer
|
import com.android.settingslib.spa.widget.ui.Footer
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@@ -241,7 +244,7 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
buildSwitchPreference(setting)
|
buildSwitchPreference(setting)
|
||||||
}
|
}
|
||||||
is DeviceSettingPreferenceModel.MultiTogglePreference -> {
|
is DeviceSettingPreferenceModel.MultiTogglePreference -> {
|
||||||
buildMultiTogglePreference(listOf(setting))
|
buildMultiTogglePreference(setting)
|
||||||
}
|
}
|
||||||
is DeviceSettingPreferenceModel.FooterPreference -> {
|
is DeviceSettingPreferenceModel.FooterPreference -> {
|
||||||
buildFooterPreference(setting)
|
buildFooterPreference(setting)
|
||||||
@@ -253,22 +256,15 @@ class DeviceDetailsFragmentFormatterImpl(
|
|||||||
null -> {}
|
null -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {}
|
||||||
if (!settings.all { it is DeviceSettingPreferenceModel.MultiTogglePreference }) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buildMultiTogglePreference(
|
|
||||||
settings.filterIsInstance<DeviceSettingPreferenceModel.MultiTogglePreference>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun buildMultiTogglePreference(
|
private fun buildMultiTogglePreference(
|
||||||
prefs: List<DeviceSettingPreferenceModel.MultiTogglePreference>
|
pref: DeviceSettingPreferenceModel.MultiTogglePreference
|
||||||
) {
|
) {
|
||||||
MultiTogglePreferenceGroup(prefs)
|
MultiTogglePreference(pref)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@@ -18,8 +18,6 @@ package com.android.settings.bluetooth.ui.viewmodel
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.media.AudioManager
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
@@ -60,20 +58,12 @@ class BluetoothDeviceDetailsViewModel(
|
|||||||
bluetoothAdapter,
|
bluetoothAdapter,
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
)
|
)
|
||||||
private val spatialAudioInteractor =
|
|
||||||
featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
|
|
||||||
application,
|
|
||||||
application.getSystemService(AudioManager::class.java),
|
|
||||||
viewModelScope,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val items =
|
private val items =
|
||||||
viewModelScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) {
|
viewModelScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) {
|
||||||
deviceSettingRepository.getDeviceSettingsConfig(cachedDevice)
|
deviceSettingRepository.getDeviceSettingsConfig(cachedDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val spatialAudioModel by lazy { spatialAudioInteractor.getDeviceSetting(cachedDevice) }
|
|
||||||
|
|
||||||
suspend fun getItems(fragment: FragmentTypeModel): List<DeviceSettingConfigItemModel>? =
|
suspend fun getItems(fragment: FragmentTypeModel): List<DeviceSettingConfigItemModel>? =
|
||||||
when (fragment) {
|
when (fragment) {
|
||||||
is FragmentTypeModel.DeviceDetailsMainFragment -> items.await()?.mainItems
|
is FragmentTypeModel.DeviceDetailsMainFragment -> items.await()?.mainItems
|
||||||
@@ -95,11 +85,8 @@ class BluetoothDeviceDetailsViewModel(
|
|||||||
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_MORE_SETTINGS) {
|
if (settingId == DeviceSettingId.DEVICE_SETTING_ID_MORE_SETTINGS) {
|
||||||
return flowOf(DeviceSettingPreferenceModel.MoreSettingsPreference(settingId))
|
return flowOf(DeviceSettingPreferenceModel.MoreSettingsPreference(settingId))
|
||||||
}
|
}
|
||||||
return when (settingId) {
|
return deviceSettingRepository.getDeviceSetting(cachedDevice, settingId)
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE ->
|
.map { it?.toPreferenceModel() }
|
||||||
spatialAudioModel
|
|
||||||
else -> deviceSettingRepository.getDeviceSetting(cachedDevice, settingId)
|
|
||||||
}.map { it?.toPreferenceModel() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun DeviceSettingModel.toPreferenceModel(): DeviceSettingPreferenceModel? {
|
private fun DeviceSettingModel.toPreferenceModel(): DeviceSettingPreferenceModel? {
|
||||||
@@ -166,7 +153,6 @@ class BluetoothDeviceDetailsViewModel(
|
|||||||
val positionToSettingIds =
|
val positionToSettingIds =
|
||||||
combine(configDeviceSetting) { settings ->
|
combine(configDeviceSetting) { settings ->
|
||||||
val positionMapping = mutableMapOf<Int, List<DeviceSettingLayoutColumn>>()
|
val positionMapping = mutableMapOf<Int, List<DeviceSettingLayoutColumn>>()
|
||||||
var multiToggleSettingIds: MutableList<DeviceSettingLayoutColumn>? = null
|
|
||||||
for (i in settings.indices) {
|
for (i in settings.indices) {
|
||||||
val configItem = configItems[i]
|
val configItem = configItems[i]
|
||||||
val setting = settings[i]
|
val setting = settings[i]
|
||||||
@@ -174,8 +160,6 @@ class BluetoothDeviceDetailsViewModel(
|
|||||||
if (!isXmlPreference && setting == null) {
|
if (!isXmlPreference && setting == null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (setting !is DeviceSettingPreferenceModel.MultiTogglePreference) {
|
|
||||||
multiToggleSettingIds = null
|
|
||||||
positionMapping[i] =
|
positionMapping[i] =
|
||||||
listOf(
|
listOf(
|
||||||
DeviceSettingLayoutColumn(
|
DeviceSettingLayoutColumn(
|
||||||
@@ -183,26 +167,6 @@ class BluetoothDeviceDetailsViewModel(
|
|||||||
configItem.highlighted,
|
configItem.highlighted,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (multiToggleSettingIds != null) {
|
|
||||||
multiToggleSettingIds.add(
|
|
||||||
DeviceSettingLayoutColumn(
|
|
||||||
configItem.settingId,
|
|
||||||
configItem.highlighted,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
multiToggleSettingIds =
|
|
||||||
mutableListOf(
|
|
||||||
DeviceSettingLayoutColumn(
|
|
||||||
configItem.settingId,
|
|
||||||
configItem.highlighted,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
positionMapping[i] = multiToggleSettingIds
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
positionMapping
|
positionMapping
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import android.util.ArrayMap;
|
|||||||
|
|
||||||
import com.android.settings.accounts.AccountDashboardFragment;
|
import com.android.settings.accounts.AccountDashboardFragment;
|
||||||
import com.android.settings.applications.manageapplications.ManageApplications;
|
import com.android.settings.applications.manageapplications.ManageApplications;
|
||||||
|
import com.android.settings.development.linuxterminal.LinuxTerminalDashboardFragment;
|
||||||
import com.android.settings.deviceinfo.StorageDashboardFragment;
|
import com.android.settings.deviceinfo.StorageDashboardFragment;
|
||||||
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
|
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
|
||||||
import com.android.settings.inputmethod.NewKeyboardLayoutEnabledLocalesFragment;
|
import com.android.settings.inputmethod.NewKeyboardLayoutEnabledLocalesFragment;
|
||||||
@@ -52,5 +53,8 @@ public class ProfileFragmentBridge {
|
|||||||
ProfileSelectKeyboardFragment.class.getName());
|
ProfileSelectKeyboardFragment.class.getName());
|
||||||
FRAGMENT_MAP.put(NewKeyboardLayoutEnabledLocalesFragment.class.getName(),
|
FRAGMENT_MAP.put(NewKeyboardLayoutEnabledLocalesFragment.class.getName(),
|
||||||
ProfileSelectPhysicalKeyboardFragment.class.getName());
|
ProfileSelectPhysicalKeyboardFragment.class.getName());
|
||||||
|
FRAGMENT_MAP.put(
|
||||||
|
LinuxTerminalDashboardFragment.class.getName(),
|
||||||
|
ProfileSelectLinuxTerminalFragment.class.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -331,21 +331,27 @@ public abstract class ProfileSelectFragment extends DashboardFragment {
|
|||||||
|
|
||||||
for (UserInfo userInfo : userInfos) {
|
for (UserInfo userInfo : userInfos) {
|
||||||
if (userInfo.isMain()) {
|
if (userInfo.isMain()) {
|
||||||
fragments.add(createAndGetFragment(
|
fragments.add(
|
||||||
|
createAndGetFragment(
|
||||||
ProfileType.PERSONAL,
|
ProfileType.PERSONAL,
|
||||||
|
userInfo.id,
|
||||||
bundle != null ? bundle : new Bundle(),
|
bundle != null ? bundle : new Bundle(),
|
||||||
personalFragmentConstructor));
|
personalFragmentConstructor));
|
||||||
} else if (userInfo.isManagedProfile()) {
|
} else if (userInfo.isManagedProfile()) {
|
||||||
fragments.add(createAndGetFragment(
|
fragments.add(
|
||||||
|
createAndGetFragment(
|
||||||
ProfileType.WORK,
|
ProfileType.WORK,
|
||||||
|
userInfo.id,
|
||||||
bundle != null ? bundle.deepCopy() : new Bundle(),
|
bundle != null ? bundle.deepCopy() : new Bundle(),
|
||||||
workFragmentConstructor));
|
workFragmentConstructor));
|
||||||
} else if (Flags.allowPrivateProfile()
|
} else if (Flags.allowPrivateProfile()
|
||||||
&& android.multiuser.Flags.enablePrivateSpaceFeatures()
|
&& android.multiuser.Flags.enablePrivateSpaceFeatures()
|
||||||
&& userInfo.isPrivateProfile()) {
|
&& userInfo.isPrivateProfile()) {
|
||||||
if (!privateSpaceInfoProvider.isPrivateSpaceLocked(context)) {
|
if (!privateSpaceInfoProvider.isPrivateSpaceLocked(context)) {
|
||||||
fragments.add(createAndGetFragment(
|
fragments.add(
|
||||||
|
createAndGetFragment(
|
||||||
ProfileType.PRIVATE,
|
ProfileType.PRIVATE,
|
||||||
|
userInfo.id,
|
||||||
bundle != null ? bundle.deepCopy() : new Bundle(),
|
bundle != null ? bundle.deepCopy() : new Bundle(),
|
||||||
privateFragmentConstructor));
|
privateFragmentConstructor));
|
||||||
}
|
}
|
||||||
@@ -364,8 +370,12 @@ public abstract class ProfileSelectFragment extends DashboardFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Fragment createAndGetFragment(
|
private static Fragment createAndGetFragment(
|
||||||
@ProfileType int profileType, Bundle bundle, FragmentConstructor fragmentConstructor) {
|
@ProfileType int profileType,
|
||||||
|
int userId,
|
||||||
|
Bundle bundle,
|
||||||
|
FragmentConstructor fragmentConstructor) {
|
||||||
bundle.putInt(EXTRA_PROFILE, profileType);
|
bundle.putInt(EXTRA_PROFILE, profileType);
|
||||||
|
bundle.putInt(EXTRA_USER_ID, userId);
|
||||||
final Fragment fragment = fragmentConstructor.constructAndGetFragment();
|
final Fragment fragment = fragmentConstructor.constructAndGetFragment();
|
||||||
fragment.setArguments(bundle);
|
fragment.setArguments(bundle);
|
||||||
return fragment;
|
return fragment;
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.settings.dashboard.profileselector;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import com.android.settings.development.DeveloperOptionAwareMixin;
|
||||||
|
import com.android.settings.development.linuxterminal.LinuxTerminalDashboardFragment;
|
||||||
|
|
||||||
|
/** Linux terminal preferences at developers option for personal/managed profile. */
|
||||||
|
public class ProfileSelectLinuxTerminalFragment extends ProfileSelectFragment
|
||||||
|
implements DeveloperOptionAwareMixin {
|
||||||
|
|
||||||
|
private static final String TAG = "ProfileSelLinuxTerminalFrag";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getLogTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Fragment[] getFragments() {
|
||||||
|
return getFragments(
|
||||||
|
getContext(),
|
||||||
|
getArguments(),
|
||||||
|
LinuxTerminalDashboardFragment::new,
|
||||||
|
LinuxTerminalDashboardFragment::new,
|
||||||
|
LinuxTerminalDashboardFragment::new);
|
||||||
|
}
|
||||||
|
}
|
@@ -74,6 +74,7 @@ import com.android.settings.development.bluetooth.BluetoothQualityDialogPreferen
|
|||||||
import com.android.settings.development.bluetooth.BluetoothSampleRateDialogPreferenceController;
|
import com.android.settings.development.bluetooth.BluetoothSampleRateDialogPreferenceController;
|
||||||
import com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController;
|
import com.android.settings.development.bluetooth.BluetoothStackLogPreferenceController;
|
||||||
import com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController;
|
import com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController;
|
||||||
|
import com.android.settings.development.linuxterminal.LinuxTerminalPreferenceController;
|
||||||
import com.android.settings.development.qstile.DevelopmentTiles;
|
import com.android.settings.development.qstile.DevelopmentTiles;
|
||||||
import com.android.settings.development.storage.SharedDataPreferenceController;
|
import com.android.settings.development.storage.SharedDataPreferenceController;
|
||||||
import com.android.settings.overlay.FeatureFactory;
|
import com.android.settings.overlay.FeatureFactory;
|
||||||
|
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 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.settings.development;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
import androidx.preference.PreferenceScreen;
|
|
||||||
import androidx.preference.TwoStatePreference;
|
|
||||||
|
|
||||||
import com.android.settings.R;
|
|
||||||
import com.android.settings.core.PreferenceControllerMixin;
|
|
||||||
import com.android.settingslib.development.DeveloperOptionsPreferenceController;
|
|
||||||
|
|
||||||
public class LinuxTerminalPreferenceController extends DeveloperOptionsPreferenceController
|
|
||||||
implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
|
|
||||||
private static final String TAG = "LinuxTerminalPrefCtrl";
|
|
||||||
|
|
||||||
private static final String ENABLE_TERMINAL_KEY = "enable_linux_terminal";
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final PackageManager mPackageManager;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final String mTerminalPackageName;
|
|
||||||
|
|
||||||
public LinuxTerminalPreferenceController(@NonNull Context context) {
|
|
||||||
super(context);
|
|
||||||
mPackageManager = mContext.getPackageManager();
|
|
||||||
|
|
||||||
String packageName = mContext.getString(R.string.config_linux_terminal_app_package_name);
|
|
||||||
mTerminalPackageName =
|
|
||||||
isPackageInstalled(mPackageManager, packageName) ? packageName : null;
|
|
||||||
|
|
||||||
Log.d(TAG, "Terminal app package name=" + packageName + ", isAvailable=" + isAvailable());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid lazy initialization because this may be called before displayPreference().
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
// Returns true only if the terminal app is installed which only happens when the build flag
|
|
||||||
// RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES is true.
|
|
||||||
// TODO(b/343795511): Add explicitly check for the flag when it's accessible from Java code.
|
|
||||||
return getTerminalPackageName() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public String getPreferenceKey() {
|
|
||||||
return ENABLE_TERMINAL_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void displayPreference(@NonNull PreferenceScreen screen) {
|
|
||||||
super.displayPreference(screen);
|
|
||||||
mPreference.setEnabled(isAvailable());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(
|
|
||||||
@NonNull Preference preference, @NonNull Object newValue) {
|
|
||||||
String packageName = getTerminalPackageName();
|
|
||||||
if (packageName == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean terminalEnabled = (Boolean) newValue;
|
|
||||||
int state = terminalEnabled
|
|
||||||
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
|
||||||
: PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
|
|
||||||
mPackageManager.setApplicationEnabledSetting(packageName, state, /* flags=*/ 0);
|
|
||||||
((TwoStatePreference) mPreference).setChecked(terminalEnabled);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateState(@NonNull Preference preference) {
|
|
||||||
String packageName = getTerminalPackageName();
|
|
||||||
if (packageName == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isTerminalEnabled = mPackageManager.getApplicationEnabledSetting(packageName)
|
|
||||||
== PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
|
||||||
((TwoStatePreference) mPreference).setChecked(isTerminalEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can be mocked for testing
|
|
||||||
@VisibleForTesting
|
|
||||||
@Nullable
|
|
||||||
String getTerminalPackageName() {
|
|
||||||
return mTerminalPackageName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isPackageInstalled(PackageManager manager, String packageName) {
|
|
||||||
if (TextUtils.isEmpty(packageName)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return manager.getPackageInfo(
|
|
||||||
packageName,
|
|
||||||
PackageManager.MATCH_ALL | PackageManager.MATCH_DISABLED_COMPONENTS) != null;
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.settings.development.linuxterminal;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.core.BasePreferenceController;
|
||||||
|
import com.android.settings.core.PreferenceControllerMixin;
|
||||||
|
import com.android.settings.widget.SettingsMainSwitchPreference;
|
||||||
|
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||||
|
|
||||||
|
/** Preference controller for enable/disable toggle of the linux terminal */
|
||||||
|
public class EnableLinuxTerminalPreferenceController extends BasePreferenceController
|
||||||
|
implements CompoundButton.OnCheckedChangeListener, PreferenceControllerMixin {
|
||||||
|
@VisibleForTesting
|
||||||
|
static final int TERMINAL_PACKAGE_NAME_RESID = R.string.config_linux_terminal_app_package_name;
|
||||||
|
|
||||||
|
private static final String TAG = "LinuxTerminalPrefCtrl";
|
||||||
|
|
||||||
|
private static final String ENABLE_TERMINAL_KEY = "enable_linux_terminal";
|
||||||
|
|
||||||
|
@NonNull private final PackageManager mPackageManager;
|
||||||
|
private final boolean mIsPrimaryUser;
|
||||||
|
@Nullable private final String mTerminalPackageName;
|
||||||
|
|
||||||
|
@Nullable private SettingsMainSwitchPreference mPreference;
|
||||||
|
|
||||||
|
public EnableLinuxTerminalPreferenceController(
|
||||||
|
@NonNull Context context, @NonNull Context userAwareContext, int userId) {
|
||||||
|
this(context, userAwareContext, userId == UserHandle.myUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
EnableLinuxTerminalPreferenceController(
|
||||||
|
@NonNull Context context, @NonNull Context userAwareContext, boolean isPrimaryUser) {
|
||||||
|
super(context, ENABLE_TERMINAL_KEY);
|
||||||
|
|
||||||
|
mPackageManager = userAwareContext.getPackageManager();
|
||||||
|
mIsPrimaryUser = isPrimaryUser;
|
||||||
|
|
||||||
|
String packageName =
|
||||||
|
userAwareContext.getString(R.string.config_linux_terminal_app_package_name);
|
||||||
|
mTerminalPackageName =
|
||||||
|
isPackageInstalled(mPackageManager, packageName) ? packageName : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAvailabilityStatus() {
|
||||||
|
return AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void displayPreference(@NonNull PreferenceScreen screen) {
|
||||||
|
super.displayPreference(screen);
|
||||||
|
mPreference = screen.findPreference(getPreferenceKey());
|
||||||
|
if (mPreference != null) {
|
||||||
|
mPreference.addOnSwitchChangeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isChecked) {
|
||||||
|
if (mTerminalPackageName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int state =
|
||||||
|
isChecked
|
||||||
|
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||||
|
: PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
|
||||||
|
mPackageManager.setApplicationEnabledSetting(mTerminalPackageName, state, /* flags= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("NullAway") // setDisabledByAdmin(EnforcedAdmin) doesn't have @Nullable
|
||||||
|
public void updateState(@NonNull Preference preference) {
|
||||||
|
if (mPreference != preference) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isInstalled = (mTerminalPackageName != null);
|
||||||
|
if (isInstalled) {
|
||||||
|
mPreference.setDisabledByAdmin(/* admin= */ null);
|
||||||
|
mPreference.setEnabled(/* enabled= */ true);
|
||||||
|
boolean terminalEnabled =
|
||||||
|
mPackageManager.getApplicationEnabledSetting(mTerminalPackageName)
|
||||||
|
== PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||||
|
mPreference.setChecked(terminalEnabled);
|
||||||
|
} else {
|
||||||
|
if (mIsPrimaryUser) {
|
||||||
|
Log.e(TAG, "Terminal app doesn't exist for primary user but UI was shown");
|
||||||
|
mPreference.setDisabledByAdmin(/* admin= */ null);
|
||||||
|
mPreference.setEnabled(/* enabled= */ false);
|
||||||
|
} else {
|
||||||
|
// If admin hasn't enabled the system app, mark it as disabled by admin.
|
||||||
|
mPreference.setDisabledByAdmin(new EnforcedAdmin());
|
||||||
|
// Make it enabled, so clicking it would show error dialog.
|
||||||
|
mPreference.setEnabled(/* enabled= */ true);
|
||||||
|
}
|
||||||
|
mPreference.setChecked(/* checked= */ false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPackageInstalled(PackageManager manager, String packageName) {
|
||||||
|
if (TextUtils.isEmpty(packageName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return manager.getPackageInfo(
|
||||||
|
packageName,
|
||||||
|
PackageManager.MATCH_ALL | PackageManager.MATCH_DISABLED_COMPONENTS)
|
||||||
|
!= null;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.settings.development.linuxterminal;
|
||||||
|
|
||||||
|
import static android.content.Intent.EXTRA_USER_ID;
|
||||||
|
|
||||||
|
import android.app.settings.SettingsEnums;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.dashboard.DashboardFragment;
|
||||||
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
|
import com.android.settingslib.core.AbstractPreferenceController;
|
||||||
|
import com.android.settingslib.development.DevelopmentSettingsEnabler;
|
||||||
|
import com.android.settingslib.search.SearchIndexable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Fragment shown for 'Linux terminal development' preference in developer option. */
|
||||||
|
@SearchIndexable
|
||||||
|
public class LinuxTerminalDashboardFragment extends DashboardFragment {
|
||||||
|
private static final String TAG = "LinuxTerminalFrag";
|
||||||
|
|
||||||
|
private Context mUserAwareContext;
|
||||||
|
|
||||||
|
private int mUserId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMetricsCategory() {
|
||||||
|
return SettingsEnums.LINUX_TERMINAL_DASHBOARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String getLogTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPreferenceScreenResId() {
|
||||||
|
return R.xml.linux_terminal_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
// Initialize mUserId and mUserAwareContext before super.onAttach(),
|
||||||
|
// so createPreferenceControllers() can be called with proper values from super.onAttach().
|
||||||
|
int currentUserId = UserHandle.myUserId();
|
||||||
|
mUserId = getArguments().getInt(EXTRA_USER_ID, currentUserId);
|
||||||
|
mUserAwareContext =
|
||||||
|
(currentUserId == mUserId)
|
||||||
|
? context
|
||||||
|
: context.createContextAsUser(UserHandle.of(mUserId), /* flags= */ 0);
|
||||||
|
|
||||||
|
// Note: This calls createPreferenceControllers() inside.
|
||||||
|
super.onAttach(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<AbstractPreferenceController> createPreferenceControllers(
|
||||||
|
@NonNull Context context) {
|
||||||
|
List<AbstractPreferenceController> list = new ArrayList<>();
|
||||||
|
list.add(new EnableLinuxTerminalPreferenceController(context, mUserAwareContext, mUserId));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
|
||||||
|
new BaseSearchIndexProvider(R.xml.linux_terminal_settings) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isPageSearchEnabled(Context context) {
|
||||||
|
return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.settings.development.linuxterminal;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.android.settings.R;
|
||||||
|
import com.android.settings.core.PreferenceControllerMixin;
|
||||||
|
import com.android.settingslib.development.DeveloperOptionsPreferenceController;
|
||||||
|
|
||||||
|
/** Preference controller for Linux terminal option in developers option */
|
||||||
|
public class LinuxTerminalPreferenceController extends DeveloperOptionsPreferenceController
|
||||||
|
implements PreferenceControllerMixin {
|
||||||
|
@VisibleForTesting
|
||||||
|
static final int TERMINAL_PACKAGE_NAME_RESID = R.string.config_linux_terminal_app_package_name;
|
||||||
|
|
||||||
|
private static final String LINUX_TERMINAL_KEY = "linux_terminal";
|
||||||
|
|
||||||
|
@Nullable private final String mTerminalPackageName;
|
||||||
|
|
||||||
|
public LinuxTerminalPreferenceController(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
String packageName = context.getString(TERMINAL_PACKAGE_NAME_RESID);
|
||||||
|
mTerminalPackageName =
|
||||||
|
isPackageInstalled(context.getPackageManager(), packageName) ? packageName : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid lazy initialization because this may be called before displayPreference().
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
// Returns true only if the terminal app is installed which only happens when the build flag
|
||||||
|
// RELEASE_AVF_SUPPORT_CUSTOM_VM_WITH_PARAVIRTUALIZED_DEVICES is true.
|
||||||
|
// TODO(b/343795511): Add explicitly check for the flag when it's accessible from Java code.
|
||||||
|
return mTerminalPackageName != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public String getPreferenceKey() {
|
||||||
|
return LINUX_TERMINAL_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPackageInstalled(PackageManager manager, String packageName) {
|
||||||
|
if (TextUtils.isEmpty(packageName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return manager.getPackageInfo(
|
||||||
|
packageName,
|
||||||
|
PackageManager.MATCH_ALL | PackageManager.MATCH_DISABLED_COMPONENTS)
|
||||||
|
!= null;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -46,7 +46,7 @@ import com.android.settingslib.transition.SettingsTransitionHelper
|
|||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
|
|
||||||
// LINT.IfChange
|
// LINT.IfChange
|
||||||
class BrightnessLevelRestrictedPreference :
|
class BrightnessLevelPreference :
|
||||||
PreferenceMetadata,
|
PreferenceMetadata,
|
||||||
PreferenceBinding,
|
PreferenceBinding,
|
||||||
PreferenceRestrictionMixin,
|
PreferenceRestrictionMixin,
|
||||||
@@ -87,7 +87,7 @@ class BrightnessLevelRestrictedPreference :
|
|||||||
override fun onStart(context: PreferenceLifecycleContext) {
|
override fun onStart(context: PreferenceLifecycleContext) {
|
||||||
val observer =
|
val observer =
|
||||||
KeyedObserver<String> { _, _ ->
|
KeyedObserver<String> { _, _ ->
|
||||||
context.notifyPreferenceChange(this@BrightnessLevelRestrictedPreference)
|
context.notifyPreferenceChange(this@BrightnessLevelPreference)
|
||||||
}
|
}
|
||||||
brightnessObserver = observer
|
brightnessObserver = observer
|
||||||
SettingsSystemStore.get(context)
|
SettingsSystemStore.get(context)
|
||||||
@@ -100,7 +100,7 @@ class BrightnessLevelRestrictedPreference :
|
|||||||
override fun onDisplayRemoved(displayId: Int) {}
|
override fun onDisplayRemoved(displayId: Int) {}
|
||||||
|
|
||||||
override fun onDisplayChanged(displayId: Int) {
|
override fun onDisplayChanged(displayId: Int) {
|
||||||
context.notifyPreferenceChange(this@BrightnessLevelRestrictedPreference)
|
context.notifyPreferenceChange(this@BrightnessLevelPreference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
displayListener = listener
|
displayListener = listener
|
@@ -188,4 +188,4 @@ public class BrightnessLevelPreferenceController extends BasePreferenceControlle
|
|||||||
return (value - min) / (max - min);
|
return (value - min) / (max - min);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// LINT.ThenChange(BrightnessLevelRestrictedPreference.kt)
|
// LINT.ThenChange(BrightnessLevelPreference.kt)
|
||||||
|
@@ -51,7 +51,7 @@ open class DisplayScreen :
|
|||||||
override fun fragmentClass() = DisplaySettings::class.java
|
override fun fragmentClass() = DisplaySettings::class.java
|
||||||
|
|
||||||
override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {
|
override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {
|
||||||
+BrightnessLevelRestrictedPreference()
|
+BrightnessLevelPreference()
|
||||||
+AutoBrightnessScreen.KEY
|
+AutoBrightnessScreen.KEY
|
||||||
+DarkModeScreen.KEY
|
+DarkModeScreen.KEY
|
||||||
+PeakRefreshRateSwitchPreference()
|
+PeakRefreshRateSwitchPreference()
|
||||||
|
@@ -38,6 +38,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils;
|
|||||||
import com.android.settingslib.widget.MainSwitchPreference;
|
import com.android.settingslib.widget.MainSwitchPreference;
|
||||||
|
|
||||||
/** Controller to update the battery saver button */
|
/** Controller to update the battery saver button */
|
||||||
|
// LINT.IfChange
|
||||||
public class BatterySaverButtonPreferenceController extends TogglePreferenceController
|
public class BatterySaverButtonPreferenceController extends TogglePreferenceController
|
||||||
implements LifecycleObserver, OnStart, OnStop, BatterySaverReceiver.BatterySaverListener {
|
implements LifecycleObserver, OnStart, OnStop, BatterySaverReceiver.BatterySaverListener {
|
||||||
private static final long SWITCH_ANIMATION_DURATION = 350L;
|
private static final long SWITCH_ANIMATION_DURATION = 350L;
|
||||||
@@ -129,3 +130,4 @@ public class BatterySaverButtonPreferenceController extends TogglePreferenceCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LINT.ThenChange(BatterySaverPreference.kt)
|
||||||
|
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.fuelgauge.batterysaver
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.PowerManager
|
||||||
|
import com.android.settings.R
|
||||||
|
import com.android.settings.fuelgauge.BatterySaverReceiver
|
||||||
|
import com.android.settings.fuelgauge.BatterySaverReceiver.BatterySaverListener
|
||||||
|
import com.android.settingslib.datastore.KeyValueStore
|
||||||
|
import com.android.settingslib.datastore.NoOpKeyedObservable
|
||||||
|
import com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_SETTINGS
|
||||||
|
import com.android.settingslib.fuelgauge.BatterySaverUtils
|
||||||
|
import com.android.settingslib.fuelgauge.BatteryStatus
|
||||||
|
import com.android.settingslib.fuelgauge.BatteryUtils
|
||||||
|
import com.android.settingslib.metadata.MainSwitchPreference
|
||||||
|
import com.android.settingslib.metadata.PreferenceLifecycleContext
|
||||||
|
import com.android.settingslib.metadata.PreferenceLifecycleProvider
|
||||||
|
|
||||||
|
// LINT.IfChange
|
||||||
|
class BatterySaverPreference :
|
||||||
|
MainSwitchPreference(KEY, R.string.battery_saver_master_switch_title),
|
||||||
|
PreferenceLifecycleProvider {
|
||||||
|
|
||||||
|
private var batterySaverReceiver: BatterySaverReceiver? = null
|
||||||
|
private val handler by lazy { Handler(Looper.getMainLooper()) }
|
||||||
|
|
||||||
|
override fun storage(context: Context) = BatterySaverStore(context)
|
||||||
|
|
||||||
|
override fun isEnabled(context: Context) =
|
||||||
|
!BatteryStatus(BatteryUtils.getBatteryIntent(context)).isPluggedIn
|
||||||
|
|
||||||
|
override fun onStart(context: PreferenceLifecycleContext) {
|
||||||
|
BatterySaverReceiver(context).apply {
|
||||||
|
batterySaverReceiver = this
|
||||||
|
setBatterySaverListener(
|
||||||
|
object : BatterySaverListener {
|
||||||
|
override fun onPowerSaveModeChanged() {
|
||||||
|
handler.postDelayed(
|
||||||
|
{ context.notifyPreferenceChange(this@BatterySaverPreference) },
|
||||||
|
SWITCH_ANIMATION_DURATION,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBatteryChanged(pluggedIn: Boolean) =
|
||||||
|
context.notifyPreferenceChange(this@BatterySaverPreference)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
setListening(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop(context: PreferenceLifecycleContext) {
|
||||||
|
batterySaverReceiver?.setListening(false)
|
||||||
|
batterySaverReceiver = null
|
||||||
|
handler.removeCallbacksAndMessages(null /* token */)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class BatterySaverStore(private val context: Context) :
|
||||||
|
NoOpKeyedObservable<String>(), KeyValueStore {
|
||||||
|
override fun contains(key: String) = key == KEY
|
||||||
|
|
||||||
|
override fun <T : Any> getValue(key: String, valueType: Class<T>) =
|
||||||
|
context.isPowerSaveMode() as T
|
||||||
|
|
||||||
|
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
|
||||||
|
BatterySaverUtils.setPowerSaveMode(
|
||||||
|
context,
|
||||||
|
value as Boolean,
|
||||||
|
/* needFirstTimeWarning= */ false,
|
||||||
|
SAVER_ENABLED_SETTINGS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.isPowerSaveMode() =
|
||||||
|
getSystemService(PowerManager::class.java)?.isPowerSaveMode == true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY = "battery_saver"
|
||||||
|
private const val SWITCH_ANIMATION_DURATION: Long = 350L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// LINT.ThenChange(BatterySaverButtonPreferenceController.java)
|
@@ -23,7 +23,7 @@ import com.android.settingslib.metadata.preferenceHierarchy
|
|||||||
import com.android.settingslib.preference.PreferenceScreenCreator
|
import com.android.settingslib.preference.PreferenceScreenCreator
|
||||||
|
|
||||||
@ProvidePreferenceScreen
|
@ProvidePreferenceScreen
|
||||||
class BatterySaverScreen : PreferenceScreenCreator {
|
open class BatterySaverScreen : PreferenceScreenCreator {
|
||||||
override val key: String
|
override val key: String
|
||||||
get() = KEY
|
get() = KEY
|
||||||
|
|
||||||
@@ -39,7 +39,8 @@ class BatterySaverScreen : PreferenceScreenCreator {
|
|||||||
|
|
||||||
override fun hasCompleteHierarchy() = false
|
override fun hasCompleteHierarchy() = false
|
||||||
|
|
||||||
override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {}
|
override fun getPreferenceHierarchy(context: Context) =
|
||||||
|
preferenceHierarchy(this) { +BatterySaverPreference() order -100 }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KEY = "battery_saver_screen"
|
const val KEY = "battery_saver_screen"
|
||||||
|
@@ -29,15 +29,19 @@ import android.view.ViewGroup;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.android.internal.accessibility.AccessibilityShortcutController;
|
import com.android.internal.accessibility.AccessibilityShortcutController;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.accessibility.AccessibilityFragmentUtils;
|
import com.android.settings.accessibility.AccessibilityFragmentUtils;
|
||||||
import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
|
import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
|
||||||
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
|
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
|
||||||
import com.android.settings.search.BaseSearchIndexProvider;
|
import com.android.settings.search.BaseSearchIndexProvider;
|
||||||
import com.android.settingslib.search.SearchIndexable;
|
import com.android.settingslib.search.SearchIndexable;
|
||||||
|
import com.android.settingslib.search.SearchIndexableRaw;
|
||||||
import com.android.settingslib.widget.IllustrationPreference;
|
import com.android.settingslib.widget.IllustrationPreference;
|
||||||
import com.android.settingslib.widget.MainSwitchPreference;
|
import com.android.settingslib.widget.MainSwitchPreference;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment for One-handed mode settings
|
* Fragment for One-handed mode settings
|
||||||
*
|
*
|
||||||
@@ -48,7 +52,8 @@ import com.android.settingslib.widget.MainSwitchPreference;
|
|||||||
public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment {
|
public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment {
|
||||||
|
|
||||||
private static final String TAG = "OneHandedSettings";
|
private static final String TAG = "OneHandedSettings";
|
||||||
private static final String ONE_HANDED_SHORTCUT_KEY = "one_handed_shortcuts_preference";
|
@VisibleForTesting
|
||||||
|
static final String ONE_HANDED_SHORTCUT_KEY = "one_handed_shortcuts_preference";
|
||||||
private static final String ONE_HANDED_ILLUSTRATION_KEY = "one_handed_header";
|
private static final String ONE_HANDED_ILLUSTRATION_KEY = "one_handed_header";
|
||||||
protected static final String ONE_HANDED_MAIN_SWITCH_KEY =
|
protected static final String ONE_HANDED_MAIN_SWITCH_KEY =
|
||||||
"gesture_one_handed_mode_enabled_main_switch";
|
"gesture_one_handed_mode_enabled_main_switch";
|
||||||
@@ -180,6 +185,25 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment {
|
|||||||
protected boolean isPageSearchEnabled(Context context) {
|
protected boolean isPageSearchEnabled(Context context) {
|
||||||
return OneHandedSettingsUtils.isSupportOneHandedMode();
|
return OneHandedSettingsUtils.isSupportOneHandedMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SearchIndexableRaw> getRawDataToIndex(Context context,
|
||||||
|
boolean enabled) {
|
||||||
|
final List<SearchIndexableRaw> rawData =
|
||||||
|
super.getRawDataToIndex(context, enabled);
|
||||||
|
if (!com.android.settings.accessibility.Flags.fixA11ySettingsSearch()) {
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
rawData.add(createShortcutPreferenceSearchData(context));
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchIndexableRaw createShortcutPreferenceSearchData(Context context) {
|
||||||
|
final SearchIndexableRaw raw = new SearchIndexableRaw(context);
|
||||||
|
raw.key = ONE_HANDED_SHORTCUT_KEY;
|
||||||
|
raw.title = context.getString(R.string.one_handed_mode_shortcut_title);
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -207,7 +207,9 @@ public class AddAppNetworksFragment extends InstrumentedFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
mWorkerThread.quit();
|
mWorkerThread.quit();
|
||||||
|
if (mHandler.hasMessagesOrCallbacks()) {
|
||||||
|
mHandler.removeCallbacksAndMessages(null);
|
||||||
|
}
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.accessibility;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.platform.test.annotations.EnableFlags;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
|
import android.util.FeatureFlagUtils;
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
|
import com.android.settings.bluetooth.Utils;
|
||||||
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
|
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||||
|
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Spy;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
import org.robolectric.shadow.api.Shadow;
|
||||||
|
|
||||||
|
/** Tests for {@link AccessibilityAudioRoutingFragment}. */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class})
|
||||||
|
public class AccessibilityAudioRoutingFragmentTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||||
|
@Rule
|
||||||
|
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LocalBluetoothManager mLocalBluetoothManager;
|
||||||
|
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
|
||||||
|
private BluetoothAdapter mBluetoothAdapter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
|
||||||
|
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
|
||||||
|
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
|
||||||
|
public void deviceSupportsHearingAidAndPageEnabled_isPageSearchEnabled_returnTrue() {
|
||||||
|
FeatureFlagUtils.setEnabled(mContext,
|
||||||
|
FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, true);
|
||||||
|
mShadowBluetoothAdapter.clearSupportedProfiles();
|
||||||
|
mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
|
||||||
|
|
||||||
|
assertThat(AccessibilityAudioRoutingFragment.isPageSearchEnabled(mContext)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
|
||||||
|
public void deviceDoesNotSupportHearingAidAndPageEnabled_isPageSearchEnabled_returnFalse() {
|
||||||
|
FeatureFlagUtils.setEnabled(mContext,
|
||||||
|
FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, true);
|
||||||
|
mShadowBluetoothAdapter.clearSupportedProfiles();
|
||||||
|
mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEADSET);
|
||||||
|
|
||||||
|
assertThat(AccessibilityAudioRoutingFragment.isPageSearchEnabled(mContext)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnableFlags(Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
|
||||||
|
public void deviceSupportsHearingAidAndPageDisabled_isPageSearchEnabled_returnFalse() {
|
||||||
|
FeatureFlagUtils.setEnabled(mContext,
|
||||||
|
FeatureFlagUtils.SETTINGS_AUDIO_ROUTING, false);
|
||||||
|
mShadowBluetoothAdapter.clearSupportedProfiles();
|
||||||
|
mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
|
||||||
|
|
||||||
|
assertThat(AccessibilityAudioRoutingFragment.isPageSearchEnabled(mContext)).isFalse();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,275 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.settings.bluetooth.domain.interactor
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import android.bluetooth.BluetoothProfile
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.AudioDeviceAttributes
|
|
||||||
import android.media.AudioDeviceInfo
|
|
||||||
import android.media.AudioManager
|
|
||||||
import androidx.test.core.app.ApplicationProvider
|
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice
|
|
||||||
import com.android.settingslib.bluetooth.LeAudioProfile
|
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
|
|
||||||
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
|
|
||||||
import com.android.settingslib.media.data.repository.SpatializerRepository
|
|
||||||
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
|
||||||
import kotlinx.coroutines.test.runCurrent
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mockito.Mock
|
|
||||||
import org.mockito.Mockito.spy
|
|
||||||
import org.mockito.Mockito.times
|
|
||||||
import org.mockito.Mockito.verify
|
|
||||||
import org.mockito.Mockito.verifyNoInteractions
|
|
||||||
import org.mockito.Mockito.`when`
|
|
||||||
import org.mockito.junit.MockitoJUnit
|
|
||||||
import org.mockito.junit.MockitoRule
|
|
||||||
import org.robolectric.RobolectricTestRunner
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
|
||||||
class SpatialAudioInteractorTest {
|
|
||||||
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
|
|
||||||
|
|
||||||
@Mock private lateinit var audioManager: AudioManager
|
|
||||||
@Mock private lateinit var cachedDevice: CachedBluetoothDevice
|
|
||||||
@Mock private lateinit var bluetoothDevice: BluetoothDevice
|
|
||||||
@Mock private lateinit var spatializerRepository: SpatializerRepository
|
|
||||||
@Mock private lateinit var leAudioProfile: LeAudioProfile
|
|
||||||
|
|
||||||
private lateinit var underTest: SpatialAudioInteractor
|
|
||||||
private val testScope = TestScope()
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
val context = spy(ApplicationProvider.getApplicationContext<Context>())
|
|
||||||
`when`(cachedDevice.device).thenReturn(bluetoothDevice)
|
|
||||||
`when`(cachedDevice.address).thenReturn(BLUETOOTH_ADDRESS)
|
|
||||||
`when`(leAudioProfile.profileId).thenReturn(BluetoothProfile.LE_AUDIO)
|
|
||||||
underTest =
|
|
||||||
SpatialAudioInteractorImpl(
|
|
||||||
context,
|
|
||||||
audioManager,
|
|
||||||
SpatializerInteractor(spatializerRepository),
|
|
||||||
testScope.backgroundScope,
|
|
||||||
testScope.testScheduler)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_noAudioProfile_returnNull() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(true)
|
|
||||||
val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
|
|
||||||
assertThat(setting).isNull()
|
|
||||||
verifyNoInteractions(spatializerRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_audioProfileNotEnabled_returnNull() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(true)
|
|
||||||
`when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
|
|
||||||
`when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false)
|
|
||||||
|
|
||||||
val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
|
|
||||||
assertThat(setting).isNull()
|
|
||||||
verifyNoInteractions(spatializerRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_deviceNotConnected_returnNull() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(false)
|
|
||||||
`when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
|
|
||||||
`when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
|
|
||||||
|
|
||||||
val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
|
|
||||||
assertThat(setting).isNull()
|
|
||||||
verifyNoInteractions(spatializerRepository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_spatialAudioNotSupported_returnNull() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(true)
|
|
||||||
`when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
|
|
||||||
`when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isSpatialAudioAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(false)
|
|
||||||
|
|
||||||
val setting = getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
|
|
||||||
assertThat(setting).isNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_spatialAudioSupported_returnTwoToggles() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(true)
|
|
||||||
`when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
|
|
||||||
`when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isSpatialAudioAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isHeadTrackingAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(false)
|
|
||||||
`when`(spatializerRepository.getSpatialAudioCompatibleDevices())
|
|
||||||
.thenReturn(listOf(BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
`when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(false)
|
|
||||||
|
|
||||||
val setting =
|
|
||||||
getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
as DeviceSettingModel.MultiTogglePreference
|
|
||||||
|
|
||||||
assertThat(setting).isNotNull()
|
|
||||||
assertThat(setting.toggles.size).isEqualTo(2)
|
|
||||||
assertThat(setting.state.selectedIndex).isEqualTo(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_headTrackingSupported_returnThreeToggles() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(true)
|
|
||||||
`when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
|
|
||||||
`when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isSpatialAudioAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isHeadTrackingAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
`when`(spatializerRepository.getSpatialAudioCompatibleDevices())
|
|
||||||
.thenReturn(listOf(BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
`when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
|
|
||||||
val setting =
|
|
||||||
getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
as DeviceSettingModel.MultiTogglePreference
|
|
||||||
|
|
||||||
assertThat(setting).isNotNull()
|
|
||||||
assertThat(setting.toggles.size).isEqualTo(3)
|
|
||||||
assertThat(setting.state.selectedIndex).isEqualTo(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_updateState_enableSpatialAudio() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(true)
|
|
||||||
`when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
|
|
||||||
`when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isSpatialAudioAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isHeadTrackingAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
`when`(spatializerRepository.getSpatialAudioCompatibleDevices()).thenReturn(listOf())
|
|
||||||
`when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(false)
|
|
||||||
|
|
||||||
val setting =
|
|
||||||
getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
as DeviceSettingModel.MultiTogglePreference
|
|
||||||
setting.updateState(DeviceSettingStateModel.MultiTogglePreferenceState(2))
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(setting).isNotNull()
|
|
||||||
verify(spatializerRepository, times(1))
|
|
||||||
.addSpatialAudioCompatibleDevice(BLE_AUDIO_DEVICE_ATTRIBUTES)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_updateState_enableHeadTracking() {
|
|
||||||
testScope.runTest {
|
|
||||||
`when`(cachedDevice.isConnected).thenReturn(true)
|
|
||||||
`when`(cachedDevice.profiles).thenReturn(listOf(leAudioProfile))
|
|
||||||
`when`(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isSpatialAudioAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
`when`(
|
|
||||||
spatializerRepository.isHeadTrackingAvailableForDevice(
|
|
||||||
BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(true)
|
|
||||||
`when`(spatializerRepository.getSpatialAudioCompatibleDevices()).thenReturn(listOf())
|
|
||||||
`when`(spatializerRepository.isHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES))
|
|
||||||
.thenReturn(false)
|
|
||||||
|
|
||||||
val setting =
|
|
||||||
getLatestValue(underTest.getDeviceSetting(cachedDevice))
|
|
||||||
as DeviceSettingModel.MultiTogglePreference
|
|
||||||
setting.updateState(DeviceSettingStateModel.MultiTogglePreferenceState(2))
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(setting).isNotNull()
|
|
||||||
verify(spatializerRepository, times(1))
|
|
||||||
.addSpatialAudioCompatibleDevice(BLE_AUDIO_DEVICE_ATTRIBUTES)
|
|
||||||
verify(spatializerRepository, times(1))
|
|
||||||
.setHeadTrackingEnabled(BLE_AUDIO_DEVICE_ATTRIBUTES, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLatestValue(deviceSettingFlow: Flow<DeviceSettingModel?>): DeviceSettingModel? {
|
|
||||||
var latestValue: DeviceSettingModel? = null
|
|
||||||
deviceSettingFlow.onEach { latestValue = it }.launchIn(testScope.backgroundScope)
|
|
||||||
testScope.runCurrent()
|
|
||||||
return latestValue
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val BLUETOOTH_ADDRESS = "12:34:56:78:12:34"
|
|
||||||
val BLE_AUDIO_DEVICE_ATTRIBUTES =
|
|
||||||
AudioDeviceAttributes(
|
|
||||||
AudioDeviceAttributes.ROLE_OUTPUT,
|
|
||||||
AudioDeviceInfo.TYPE_BLE_HEADSET,
|
|
||||||
BLUETOOTH_ADDRESS,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -20,14 +20,11 @@ import android.bluetooth.BluetoothAdapter
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.media.AudioManager
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
|
|
||||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||||
import com.android.settings.dashboard.DashboardFragment
|
import com.android.settings.dashboard.DashboardFragment
|
||||||
@@ -56,13 +53,11 @@ import org.junit.runner.RunWith
|
|||||||
import org.mockito.ArgumentMatchers.eq
|
import org.mockito.ArgumentMatchers.eq
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.any
|
import org.mockito.Mockito.any
|
||||||
import org.mockito.Mockito.verify
|
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
import org.mockito.junit.MockitoRule
|
import org.mockito.junit.MockitoRule
|
||||||
import org.robolectric.Robolectric
|
import org.robolectric.Robolectric
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.Shadows
|
|
||||||
import org.robolectric.shadows.ShadowLooper
|
import org.robolectric.shadows.ShadowLooper
|
||||||
import org.robolectric.shadows.ShadowLooper.shadowMainLooper
|
import org.robolectric.shadows.ShadowLooper.shadowMainLooper
|
||||||
|
|
||||||
@@ -74,7 +69,6 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
@Mock private lateinit var cachedDevice: CachedBluetoothDevice
|
@Mock private lateinit var cachedDevice: CachedBluetoothDevice
|
||||||
@Mock private lateinit var bluetoothAdapter: BluetoothAdapter
|
@Mock private lateinit var bluetoothAdapter: BluetoothAdapter
|
||||||
@Mock private lateinit var repository: DeviceSettingRepository
|
@Mock private lateinit var repository: DeviceSettingRepository
|
||||||
@Mock private lateinit var spatialAudioInteractor: SpatialAudioInteractor
|
|
||||||
|
|
||||||
private lateinit var fragment: TestFragment
|
private lateinit var fragment: TestFragment
|
||||||
private lateinit var underTest: DeviceDetailsFragmentFormatter
|
private lateinit var underTest: DeviceDetailsFragmentFormatter
|
||||||
@@ -90,10 +84,6 @@ class DeviceDetailsFragmentFormatterTest {
|
|||||||
featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
|
featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository(
|
||||||
eq(context), eq(bluetoothAdapter), any()))
|
eq(context), eq(bluetoothAdapter), any()))
|
||||||
.thenReturn(repository)
|
.thenReturn(repository)
|
||||||
`when`(
|
|
||||||
featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
|
|
||||||
eq(context), any(AudioManager::class.java), any()))
|
|
||||||
.thenReturn(spatialAudioInteractor)
|
|
||||||
fragmentActivity = Robolectric.setupActivity(FragmentActivity::class.java)
|
fragmentActivity = Robolectric.setupActivity(FragmentActivity::class.java)
|
||||||
assertThat(fragmentActivity.applicationContext).isNotNull()
|
assertThat(fragmentActivity.applicationContext).isNotNull()
|
||||||
fragment = TestFragment(context)
|
fragment = TestFragment(context)
|
||||||
|
@@ -19,9 +19,7 @@ package com.android.settings.bluetooth.ui.viewmodel
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.media.AudioManager
|
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import com.android.settings.bluetooth.domain.interactor.SpatialAudioInteractor
|
|
||||||
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
|
import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout
|
||||||
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel
|
||||||
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
import com.android.settings.bluetooth.ui.model.FragmentTypeModel
|
||||||
@@ -68,8 +66,6 @@ class BluetoothDeviceDetailsViewModelTest {
|
|||||||
|
|
||||||
@Mock private lateinit var repository: DeviceSettingRepository
|
@Mock private lateinit var repository: DeviceSettingRepository
|
||||||
|
|
||||||
@Mock private lateinit var spatialAudioInteractor: SpatialAudioInteractor
|
|
||||||
|
|
||||||
private lateinit var underTest: BluetoothDeviceDetailsViewModel
|
private lateinit var underTest: BluetoothDeviceDetailsViewModel
|
||||||
private lateinit var featureFactory: FakeFeatureFactory
|
private lateinit var featureFactory: FakeFeatureFactory
|
||||||
private val testScope = TestScope()
|
private val testScope = TestScope()
|
||||||
@@ -84,11 +80,6 @@ class BluetoothDeviceDetailsViewModelTest {
|
|||||||
eq(application), eq(bluetoothAdapter), any()
|
eq(application), eq(bluetoothAdapter), any()
|
||||||
))
|
))
|
||||||
.thenReturn(repository)
|
.thenReturn(repository)
|
||||||
`when`(
|
|
||||||
featureFactory.bluetoothFeatureProvider.getSpatialAudioInteractor(
|
|
||||||
eq(application), any(AudioManager::class.java), any()
|
|
||||||
))
|
|
||||||
.thenReturn(spatialAudioInteractor)
|
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
BluetoothDeviceDetailsViewModel(
|
BluetoothDeviceDetailsViewModel(
|
||||||
@@ -173,37 +164,6 @@ class BluetoothDeviceDetailsViewModelTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun getDeviceSetting_spatialAudio_returnSpatialAudioInteractorResponse() {
|
|
||||||
testScope.runTest {
|
|
||||||
val pref =
|
|
||||||
buildMultiTogglePreference(
|
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE)
|
|
||||||
`when`(repository.getDeviceSettingsConfig(cachedDevice))
|
|
||||||
.thenReturn(
|
|
||||||
DeviceSettingConfigModel(
|
|
||||||
listOf(
|
|
||||||
BUILTIN_SETTING_ITEM_1,
|
|
||||||
buildRemoteSettingItem(
|
|
||||||
DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE),
|
|
||||||
),
|
|
||||||
listOf(),
|
|
||||||
null))
|
|
||||||
`when`(spatialAudioInteractor.getDeviceSetting(cachedDevice)).thenReturn(flowOf(pref))
|
|
||||||
|
|
||||||
var deviceSettingPreference: DeviceSettingPreferenceModel? = null
|
|
||||||
underTest
|
|
||||||
.getDeviceSetting(
|
|
||||||
cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE)
|
|
||||||
.onEach { deviceSettingPreference = it }
|
|
||||||
.launchIn(testScope.backgroundScope)
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertThat(deviceSettingPreference?.id).isEqualTo(pref.id)
|
|
||||||
verify(spatialAudioInteractor, times(1)).getDeviceSetting(cachedDevice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getLayout_builtinDeviceSettings() {
|
fun getLayout_builtinDeviceSettings() {
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
@@ -252,7 +212,8 @@ class BluetoothDeviceDetailsViewModelTest {
|
|||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
listOf(
|
listOf(
|
||||||
listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER),
|
listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER),
|
||||||
listOf(remoteSettingId1, remoteSettingId2),
|
listOf(remoteSettingId1),
|
||||||
|
listOf(remoteSettingId2),
|
||||||
listOf(remoteSettingId3),
|
listOf(remoteSettingId3),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 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.settings.development;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.doThrow;
|
|
||||||
import static org.mockito.Mockito.eq;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
|
|
||||||
import androidx.preference.PreferenceScreen;
|
|
||||||
import androidx.preference.SwitchPreference;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.robolectric.RobolectricTestRunner;
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
|
||||||
public class LinuxTerminalPreferenceControllerTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Context mContext;
|
|
||||||
@Mock
|
|
||||||
private SwitchPreference mPreference;
|
|
||||||
@Mock
|
|
||||||
private PreferenceScreen mPreferenceScreen;
|
|
||||||
@Mock
|
|
||||||
private PackageManager mPackageManager;
|
|
||||||
@Mock
|
|
||||||
private ApplicationInfo mApplicationInfo;
|
|
||||||
|
|
||||||
private String mTerminalPackageName = "com.android.virtualization.terminal";
|
|
||||||
private LinuxTerminalPreferenceController mController;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
doReturn(mPackageManager).when(mContext).getPackageManager();
|
|
||||||
doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(
|
|
||||||
eq(mTerminalPackageName), any());
|
|
||||||
|
|
||||||
mController = spy(new LinuxTerminalPreferenceController(mContext));
|
|
||||||
doReturn(true).when(mController).isAvailable();
|
|
||||||
doReturn(mTerminalPackageName).when(mController).getTerminalPackageName();
|
|
||||||
when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
|
|
||||||
.thenReturn(mPreference);
|
|
||||||
mController.displayPreference(mPreferenceScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isAvailable_whenPackageNameIsNull_returnsFalse() throws Exception {
|
|
||||||
mController = spy(new LinuxTerminalPreferenceController(mContext));
|
|
||||||
doReturn(null).when(mController).getTerminalPackageName();
|
|
||||||
|
|
||||||
assertThat(mController.isAvailable()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isAvailable_whenAppDoesNotExist_returnsFalse() throws Exception {
|
|
||||||
doThrow(new NameNotFoundException()).when(mPackageManager).getApplicationInfo(
|
|
||||||
eq(mTerminalPackageName), any());
|
|
||||||
|
|
||||||
mController = spy(new LinuxTerminalPreferenceController(mContext));
|
|
||||||
|
|
||||||
assertThat(mController.isAvailable()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onPreferenceChanged_turnOnTerminal() {
|
|
||||||
mController.onPreferenceChange(null, true);
|
|
||||||
|
|
||||||
verify(mPackageManager).setApplicationEnabledSetting(
|
|
||||||
mTerminalPackageName,
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
|
||||||
/* flags= */ 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void onPreferenceChanged_turnOffTerminal() {
|
|
||||||
mController.onPreferenceChange(null, false);
|
|
||||||
|
|
||||||
verify(mPackageManager).setApplicationEnabledSetting(
|
|
||||||
mTerminalPackageName,
|
|
||||||
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
|
|
||||||
/* flags= */ 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void updateState_preferenceShouldBeChecked() {
|
|
||||||
when(mPackageManager.getApplicationEnabledSetting(mTerminalPackageName))
|
|
||||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
|
|
||||||
mController.updateState(mPreference);
|
|
||||||
|
|
||||||
verify(mPreference).setChecked(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void updateState_preferenceShouldNotBeChecked() {
|
|
||||||
when(mPackageManager.getApplicationEnabledSetting(mTerminalPackageName))
|
|
||||||
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
|
|
||||||
mController.updateState(mPreference);
|
|
||||||
|
|
||||||
verify(mPreference).setChecked(false);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.settings.development.linuxterminal;
|
||||||
|
|
||||||
|
import static com.android.settings.development.linuxterminal.EnableLinuxTerminalPreferenceController.TERMINAL_PACKAGE_NAME_RESID;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.junit.Assume.assumeFalse;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import com.android.settings.widget.SettingsMainSwitchPreference;
|
||||||
|
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Tests {@link EnableLinuxTerminalPreferenceController} */
|
||||||
|
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||||
|
public class EnableLinuxTerminalPreferenceControllerTest {
|
||||||
|
|
||||||
|
/** Defines parameters for parameterized test */
|
||||||
|
@ParameterizedRobolectricTestRunner.Parameters(
|
||||||
|
name = "isPrimaryUser={0}, installed={1}, enabled={2}")
|
||||||
|
public static List<Object[]> params() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new Object[] {true, true, false},
|
||||||
|
new Object[] {true, true, true},
|
||||||
|
new Object[] {false, false, false},
|
||||||
|
new Object[] {false, true, false},
|
||||||
|
new Object[] {false, true, true});
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedRobolectricTestRunner.Parameter(0)
|
||||||
|
public boolean mIsPrimaryUser;
|
||||||
|
|
||||||
|
@ParameterizedRobolectricTestRunner.Parameter(1)
|
||||||
|
public boolean mInstalled;
|
||||||
|
|
||||||
|
@ParameterizedRobolectricTestRunner.Parameter(2)
|
||||||
|
public boolean mEnabled;
|
||||||
|
|
||||||
|
@Mock private Context mContext;
|
||||||
|
@Mock private Context mUserContext;
|
||||||
|
@Mock private SettingsMainSwitchPreference mPreference;
|
||||||
|
@Mock private PreferenceScreen mPreferenceScreen;
|
||||||
|
@Mock private PackageManager mPackageManager;
|
||||||
|
@Mock private PackageInfo mPackageInfo;
|
||||||
|
|
||||||
|
private String mTerminalPackageName = "com.android.virtualization.terminal";
|
||||||
|
private EnableLinuxTerminalPreferenceController mController;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
doReturn(mTerminalPackageName)
|
||||||
|
.when(mUserContext)
|
||||||
|
.getString(eq(TERMINAL_PACKAGE_NAME_RESID));
|
||||||
|
|
||||||
|
doReturn(mPackageManager).when(mUserContext).getPackageManager();
|
||||||
|
doReturn(mInstalled ? mPackageInfo : null)
|
||||||
|
.when(mPackageManager)
|
||||||
|
.getPackageInfo(eq(mTerminalPackageName), anyInt());
|
||||||
|
doReturn(
|
||||||
|
mEnabled
|
||||||
|
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||||
|
: PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
|
||||||
|
.when(mPackageManager)
|
||||||
|
.getApplicationEnabledSetting(eq(mTerminalPackageName));
|
||||||
|
|
||||||
|
mController =
|
||||||
|
new EnableLinuxTerminalPreferenceController(mContext, mUserContext, mIsPrimaryUser);
|
||||||
|
|
||||||
|
doReturn(mPreference)
|
||||||
|
.when(mPreferenceScreen)
|
||||||
|
.findPreference(eq(mController.getPreferenceKey()));
|
||||||
|
mController.displayPreference(mPreferenceScreen);
|
||||||
|
mController.updateState(mPreference);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_returnTrue() {
|
||||||
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onCheckedChanged_whenChecked_turnOnTerminal() {
|
||||||
|
assumeTrue(mInstalled);
|
||||||
|
|
||||||
|
mController.onCheckedChanged(/* buttonView= */ null, /* isChecked= */ true);
|
||||||
|
|
||||||
|
verify(mPackageManager)
|
||||||
|
.setApplicationEnabledSetting(
|
||||||
|
mTerminalPackageName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||||
|
/* flags= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onCheckedChanged_whenUnchecked_turnOffTerminal() {
|
||||||
|
assumeTrue(mInstalled);
|
||||||
|
|
||||||
|
mController.onCheckedChanged(/* buttonView= */ null, /* isChecked= */ false);
|
||||||
|
|
||||||
|
verify(mPackageManager)
|
||||||
|
.setApplicationEnabledSetting(
|
||||||
|
mTerminalPackageName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
|
||||||
|
/* flags= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateState_enabled() {
|
||||||
|
verify(mPreference).setEnabled(/* enabled= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateState_whenEnabled_checked() {
|
||||||
|
assumeTrue(mEnabled);
|
||||||
|
|
||||||
|
verify(mPreference).setChecked(/* checked= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateState_whenDisabled_unchecked() {
|
||||||
|
assumeFalse(mEnabled);
|
||||||
|
|
||||||
|
verify(mPreference).setChecked(/* checked= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateState_withProfileWhenAllowed_enabledByAdmin() {
|
||||||
|
assumeFalse(mIsPrimaryUser);
|
||||||
|
assumeTrue(mInstalled);
|
||||||
|
|
||||||
|
verify(mPreference).setDisabledByAdmin(eq(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateState_withProfileWhenNotAllowed_disabledByAdmin() {
|
||||||
|
assumeFalse(mIsPrimaryUser);
|
||||||
|
assumeFalse(mInstalled);
|
||||||
|
|
||||||
|
verify(mPreference).setDisabledByAdmin(any(EnforcedAdmin.class));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 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.settings.development.linuxterminal;
|
||||||
|
|
||||||
|
import static com.android.settings.development.linuxterminal.LinuxTerminalPreferenceController.TERMINAL_PACKAGE_NAME_RESID;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.eq;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
|
/** Tests {@link LinuxTerminalPreferenceController} */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class LinuxTerminalPreferenceControllerTest {
|
||||||
|
|
||||||
|
@Mock private Context mContext;
|
||||||
|
@Mock private PackageManager mPackageManager;
|
||||||
|
@Mock private PackageInfo mPackageInfo;
|
||||||
|
|
||||||
|
private String mTerminalPackageName = "com.android.virtualization.terminal";
|
||||||
|
private LinuxTerminalPreferenceController mController;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
doReturn(mTerminalPackageName).when(mContext).getString(TERMINAL_PACKAGE_NAME_RESID);
|
||||||
|
|
||||||
|
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||||
|
doReturn(mPackageInfo)
|
||||||
|
.when(mPackageManager)
|
||||||
|
.getPackageInfo(eq(mTerminalPackageName), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_whenPackageExists_returnsTrue() throws NameNotFoundException {
|
||||||
|
mController = new LinuxTerminalPreferenceController(mContext);
|
||||||
|
|
||||||
|
assertThat(mController.isAvailable()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_whenPackageNameIsNull_returnsFalse() {
|
||||||
|
doReturn(null).when(mContext).getString(TERMINAL_PACKAGE_NAME_RESID);
|
||||||
|
|
||||||
|
mController = new LinuxTerminalPreferenceController(mContext);
|
||||||
|
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAvailable_whenAppDoesNotExist_returnsFalse() throws Exception {
|
||||||
|
doThrow(new NameNotFoundException())
|
||||||
|
.when(mPackageManager)
|
||||||
|
.getPackageInfo(eq(mTerminalPackageName), anyInt());
|
||||||
|
|
||||||
|
mController = new LinuxTerminalPreferenceController(mContext);
|
||||||
|
|
||||||
|
assertThat(mController.isAvailable()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
@@ -40,6 +40,7 @@ import org.mockito.MockitoAnnotations;
|
|||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.RuntimeEnvironment;
|
import org.robolectric.RuntimeEnvironment;
|
||||||
|
|
||||||
|
// LINT.IfChange
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class BatterySaverButtonPreferenceControllerTest {
|
public class BatterySaverButtonPreferenceControllerTest {
|
||||||
|
|
||||||
@@ -120,3 +121,4 @@ public class BatterySaverButtonPreferenceControllerTest {
|
|||||||
assertThat(mController.isPublicSlice()).isTrue();
|
assertThat(mController.isPublicSlice()).isTrue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LINT.ThenChange(BatterySaverPreferenceTest.kt)
|
||||||
|
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings.fuelgauge.batterysaver
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.BatteryManager.EXTRA_PLUGGED
|
||||||
|
import android.os.PowerManager
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.android.settingslib.preference.createAndBindWidget
|
||||||
|
import com.android.settingslib.widget.MainSwitchPreference
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.stub
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
|
||||||
|
// LINT.IfChange
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class BatterySaverPreferenceTest {
|
||||||
|
private val powerManager = mock<PowerManager>()
|
||||||
|
|
||||||
|
private val context: Context =
|
||||||
|
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
|
||||||
|
override fun getSystemService(name: String): Any? =
|
||||||
|
when {
|
||||||
|
name == getSystemServiceName(PowerManager::class.java) -> powerManager
|
||||||
|
else -> super.getSystemService(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registerReceiver(receiver: BroadcastReceiver?, filter: IntentFilter?) =
|
||||||
|
Intent().putExtra(EXTRA_PLUGGED, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val contextPlugIn: Context =
|
||||||
|
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
|
||||||
|
override fun registerReceiver(receiver: BroadcastReceiver?, filter: IntentFilter?) =
|
||||||
|
Intent().putExtra(EXTRA_PLUGGED, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val batterySaverPreference = BatterySaverPreference()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun lowPowerOn_preferenceIsChecked() {
|
||||||
|
powerManager.stub { on { isPowerSaveMode } doReturn true }
|
||||||
|
|
||||||
|
assertThat(getMainSwitchPreference().isChecked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun lowPowerOff_preferenceIsUnChecked() {
|
||||||
|
powerManager.stub { on { isPowerSaveMode } doReturn false }
|
||||||
|
|
||||||
|
assertThat(getMainSwitchPreference().isChecked).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun storeSetOn_setPowerSaveMode() {
|
||||||
|
batterySaverPreference
|
||||||
|
.storage(context)
|
||||||
|
.setValue(batterySaverPreference.key, Boolean::class.javaObjectType, true)
|
||||||
|
|
||||||
|
verify(powerManager).setPowerSaveModeEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun storeSetOff_unsetPowerSaveMode() {
|
||||||
|
batterySaverPreference
|
||||||
|
.storage(context)
|
||||||
|
.setValue(batterySaverPreference.key, Boolean::class.javaObjectType, false)
|
||||||
|
|
||||||
|
verify(powerManager).setPowerSaveModeEnabled(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isUnPlugIn_preferenceEnabled() {
|
||||||
|
assertThat(getMainSwitchPreference().isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isPlugIn_preferenceDisabled() {
|
||||||
|
assertThat(getMainSwitchPreference(contextPlugIn).isEnabled).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMainSwitchPreference(ctx: Context = context) =
|
||||||
|
batterySaverPreference.createAndBindWidget<MainSwitchPreference>(ctx)
|
||||||
|
}
|
||||||
|
// LINT.ThenChange(BatterySaverButtonPreferenceControllerTest.java)
|
@@ -15,21 +15,37 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.settings.fuelgauge.batterysaver
|
package com.android.settings.fuelgauge.batterysaver
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.BatteryManager
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.android.settings.flags.Flags
|
import com.android.settings.flags.Flags
|
||||||
import com.android.settingslib.preference.CatalystScreenTestCase
|
import com.android.settingslib.preference.CatalystScreenTestCase
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class BatterySaverScreenTest : CatalystScreenTestCase() {
|
class BatterySaverScreenTest : CatalystScreenTestCase() {
|
||||||
|
private val intent =
|
||||||
|
Intent(Intent.ACTION_BATTERY_CHANGED).putExtra(BatteryManager.EXTRA_PLUGGED, 0)
|
||||||
|
|
||||||
override val preferenceScreenCreator = BatterySaverScreen()
|
override val preferenceScreenCreator = BatterySaverScreen()
|
||||||
|
|
||||||
override val flagName: String
|
override val flagName: String
|
||||||
get() = Flags.FLAG_CATALYST_BATTERY_SAVER_SCREEN
|
get() = Flags.FLAG_CATALYST_BATTERY_SAVER_SCREEN
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
appContext.sendStickyBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
appContext.removeStickyBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun key() {
|
fun key() {
|
||||||
assertThat(preferenceScreenCreator.key).isEqualTo(BatterySaverScreen.KEY)
|
assertThat(preferenceScreenCreator.key).isEqualTo(BatterySaverScreen.KEY)
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.settings.gestures;
|
package com.android.settings.gestures;
|
||||||
|
|
||||||
|
import static com.android.settings.gestures.OneHandedSettings.ONE_HANDED_SHORTCUT_KEY;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
@@ -23,14 +25,19 @@ import static org.mockito.Mockito.when;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.SystemProperties;
|
import android.os.SystemProperties;
|
||||||
|
import android.platform.test.annotations.DisableFlags;
|
||||||
|
import android.platform.test.annotations.EnableFlags;
|
||||||
|
import android.platform.test.flag.junit.SetFlagsRule;
|
||||||
import android.provider.SearchIndexableResource;
|
import android.provider.SearchIndexableResource;
|
||||||
|
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
|
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
|
||||||
|
import com.android.settingslib.search.SearchIndexableRaw;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
@@ -43,12 +50,16 @@ import java.util.List;
|
|||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class OneHandedSettingsTest {
|
public class OneHandedSettingsTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||||
|
|
||||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||||
private OneHandedSettings mSettings;
|
private OneHandedSettings mSettings;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
mSettings = spy(new OneHandedSettings());
|
mSettings = spy(new OneHandedSettings());
|
||||||
|
SystemProperties.set(OneHandedSettingsUtils.SUPPORT_ONE_HANDED_MODE, "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -102,4 +113,35 @@ public class OneHandedSettingsTest {
|
|||||||
final boolean isEnabled = (Boolean) obj;
|
final boolean isEnabled = (Boolean) obj;
|
||||||
assertThat(isEnabled).isFalse();
|
assertThat(isEnabled).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisableFlags(com.android.settings.accessibility.Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
|
||||||
|
public void getRawDataToIndex_flagDisabled_isEmpty() {
|
||||||
|
final List<SearchIndexableRaw> rawData = OneHandedSettings
|
||||||
|
.SEARCH_INDEX_DATA_PROVIDER.getRawDataToIndex(mContext, true);
|
||||||
|
final List<String> actualSearchKeys = rawData.stream().map(raw -> raw.key).toList();
|
||||||
|
|
||||||
|
assertThat(actualSearchKeys).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnableFlags(com.android.settings.accessibility.Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH)
|
||||||
|
public void getRawDataToIndex_returnsOnlyShortcutKey() {
|
||||||
|
final List<SearchIndexableRaw> rawData = OneHandedSettings
|
||||||
|
.SEARCH_INDEX_DATA_PROVIDER.getRawDataToIndex(mContext, true);
|
||||||
|
final List<String> actualSearchKeys = rawData.stream().map(raw -> raw.key).toList();
|
||||||
|
|
||||||
|
assertThat(actualSearchKeys).containsExactly(ONE_HANDED_SHORTCUT_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getNonIndexableKeys_containsNonSearchableElements() {
|
||||||
|
final List<String> niks = OneHandedSettings.SEARCH_INDEX_DATA_PROVIDER
|
||||||
|
.getNonIndexableKeys(mContext);
|
||||||
|
|
||||||
|
assertThat(niks).containsExactly(
|
||||||
|
"gesture_one_handed_mode_intro",
|
||||||
|
"one_handed_header",
|
||||||
|
"one_handed_mode_footer");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user