Snap for 12296955 from 4f8e95fb57 to 24Q4-release

Change-Id: If544eda285f37211f6c0d9cb78b35c4ae09f2594
This commit is contained in:
Android Build Coastguard Worker
2024-08-29 23:18:52 +00:00
51 changed files with 1353 additions and 1020 deletions

View File

@@ -1325,7 +1325,7 @@
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.notification.modes.ZenModesListFragment"/>
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
android:value="@string/menu_key_notifications"/>
android:value="@string/menu_key_priority_modes" />
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>

View File

@@ -1,13 +1,6 @@
package: "com.android.settings.flags"
container: "system_ext"
flag {
name: "enable_subsequent_pair_settings_integration"
namespace: "pixel_cross_device_control"
description: "Gates whether to enable subsequent pair Settings integration."
bug: "299405720"
}
flag {
name: "rotation_connected_display_setting"
namespace: "display_manager"
@@ -47,4 +40,4 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
}
}

View File

@@ -56,3 +56,10 @@ flag {
description: "This flag controls the About phone > Legal information page migration"
bug: "323791114"
}
flag {
name: "updated_suggestion_card_aosp"
namespace: "android_settings"
description: "Use updated suggestion card(s) in AOSP Settings"
bug: "323258154"
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="40dp"
android:height="40dp" />
<solid android:color="@color/settingslib_materialColorSecondaryContainer" />
</shape>

View File

@@ -35,7 +35,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="2"
android:maxLines="3"
android:ellipsize="end"
android:paddingTop="14dp"
android:textAlignment="center"
android:textSize="24sp" />

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:apps="http://schemas.android.com/apk/res-auto"
android:id="@+id/suggestion_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
style="@style/SuggestionCardStyle">
<LinearLayout
android:id="@+id/card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:minHeight="72dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="?android:attr/selectableItemBackground">
<FrameLayout
android:id="@android:id/icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="ContentDescription">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/suggestion_icon_background"/>
<ImageView
android:id="@android:id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"/>
</FrameLayout>
<LinearLayout
android:id="@+id/text_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingVertical="@dimen/suggestion_card_text_padding_vertical"
android:orientation="vertical">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.SuggestionCardTitle"/>
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.SuggestionCardSummary"/>
</LinearLayout>
<ImageView
android:id="@android:id/closeButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:visibility="gone"
android:layout_gravity="center"
android:contentDescription="@string/suggestion_button_close"
android:src="@drawable/ic_suggestion_close_button"
android:tint="@color/settingslib_materialColorPrimaryContainer"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -297,6 +297,7 @@
<dimen name="contextual_card_side_margin">4dp</dimen>
<dimen name="contextual_card_icon_padding_start">14dp</dimen>
<dimen name="contextual_card_text_padding_start">16dp</dimen>
<dimen name="suggestion_card_text_padding_vertical">18dp</dimen>
<dimen name="contextual_card_padding_end">16dp</dimen>
<dimen name="contextual_card_corner_radius">@*android:dimen/config_dialogCornerRadius</dimen>
<dimen name="contextual_full_card_padding_end">12dp</dimen>

View File

@@ -24,6 +24,7 @@
<string name="menu_key_battery" translatable="false">top_level_battery</string>
<string name="menu_key_storage" translatable="false">top_level_storage</string>
<string name="menu_key_sound" translatable="false">top_level_sound</string>
<string name="menu_key_priority_modes" translatable="false">top_level_priority_modes</string>
<string name="menu_key_display" translatable="false">top_level_display</string>
<string name="menu_key_wallpaper" translatable="false">top_level_wallpaper</string>
<string name="menu_key_accessibility" translatable="false">top_level_accessibility</string>

View File

@@ -937,7 +937,7 @@
<!-- Message showing that multiple fingerprints, face, and the current watch is set up. Shown for a menu item that launches fingerprint, face, and active unlock settings or enrollment. [CHAR LIMIT=80]-->
<string name="security_settings_fingerprint_multiple_face_watch_preference_summary">Face, fingerprints, and <xliff:g id="watch" example="Dani's Watch">%s</xliff:g> added</string>
<!-- Description for mandatory biometrics prompt-->
<string name="mandatory_biometrics_prompt_description">Identity Check is on</string>
<string name="mandatory_biometrics_prompt_description">Identity Check is on and requires a biometric</string>
<!-- RemoteAuth unlock enrollment and settings --><skip />
<!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
<string name ="security_settings_remoteauth_preference_title">Remote Authenticator Unlock</string>

View File

@@ -1017,4 +1017,22 @@
<item name="android:background">@null</item>
</style>
<style name="SuggestionCardStyle">
<item name="cardBackgroundColor">@color/settingslib_materialColorPrimary</item>
<item name="cardCornerRadius">40dp</item>
<item name="cardElevation">0dp</item>
</style>
<style name="TextAppearance.SuggestionCardTitle">
<item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
<item name="android:textSize">20sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
</style>
<style name="TextAppearance.SuggestionCardSummary">
<item name="android:textColor">@color/settingslib_materialColorOnSecondary</item>
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
</style>
</resources>

View File

@@ -135,15 +135,6 @@
settings:controller="com.android.settings.notification.zen.ZenModePreferenceController"
/>
<com.android.settingslib.RestrictedPreference
android:key="modes_notifications"
android:order="18"
android:title="@string/zen_modes_list_title"
settings:useAdminDisabledSummary="true"
android:fragment="com.android.settings.notification.modes.ZenModesListFragment"
settings:controller="com.android.settings.notification.modes.ZenModesLinkPreferenceController"
/>
<SwitchPreferenceCompat
android:key="lock_screen_show_only_unseen_notifs"
android:order="19"

View File

@@ -221,10 +221,11 @@
</intent>
</Preference>
<!-- Settings search is handled by WifiCallingSearchItem. -->
<SwitchPreferenceCompat
android:key="video_calling_key"
android:title="@string/video_calling_settings_title"
android:persistent="true"
settings:searchable="false"
settings:controller="com.android.settings.network.telephony.VideoCallingPreferenceController"/>
</PreferenceCategory>

View File

@@ -18,7 +18,8 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/zen_modes_list_title">
android:title="@string/zen_modes_list_title"
android:key="modes_list_settings">
<com.android.settingslib.widget.TopIntroPreference
android:title="@string/zen_modes_list_intro" />

View File

@@ -105,14 +105,6 @@
settings:keywords="@string/keywords_sounds_and_notifications_interruptions"
settings:controller="com.android.settings.notification.zen.ZenModePreferenceController"/>
<com.android.settingslib.RestrictedPreference
android:key="modes_notifications"
android:order="-130"
android:title="@string/zen_modes_list_title"
settings:useAdminDisabledSummary="true"
android:fragment="com.android.settings.notification.modes.ZenModesListFragment"
settings:controller="com.android.settings.notification.modes.ZenModesLinkPreferenceController"/>
<!-- Phone ringtone -->
<com.android.settings.DefaultRingtonePreference
android:key="phone_ringtone"

View File

@@ -68,6 +68,16 @@
android:summary="@string/notification_dashboard_summary"
settings:highlightableMenuKey="@string/menu_key_notifications"/>
<com.android.settings.widget.RestrictedHomepagePreference
android:fragment="com.android.settings.notification.modes.ZenModesListFragment"
android:icon="@*android:drawable/ic_zen_priority_modes"
android:key="top_level_priority_modes"
android:order="-115"
android:title="@string/zen_modes_list_title"
settings:useAdminDisabledSummary="true"
settings:highlightableMenuKey="@string/menu_key_priority_modes"
settings:controller="com.android.settings.notification.modes.ZenModesLinkPreferenceController"/>
<com.android.settings.widget.HomepagePreference
android:fragment="com.android.settings.fuelgauge.batteryusage.PowerUsageSummary"
android:icon="@drawable/ic_settings_battery_white"

View File

@@ -82,6 +82,16 @@
android:summary="@string/sound_dashboard_summary"
settings:highlightableMenuKey="@string/menu_key_sound"/>
<com.android.settings.widget.RestrictedHomepagePreference
android:fragment="com.android.settings.notification.modes.ZenModesListFragment"
android:icon="@*android:drawable/ic_zen_priority_modes"
android:key="top_level_priority_modes"
android:order="-35"
android:title="@string/zen_modes_list_title"
settings:useAdminDisabledSummary="true"
settings:highlightableMenuKey="@string/menu_key_priority_modes"
settings:controller="com.android.settings.notification.modes.ZenModesLinkPreferenceController"/>
<com.android.settings.widget.HomepagePreference
android:fragment="com.android.settings.communal.CommunalDashboardFragment"
android:icon="@drawable/ia_settings_communal"

View File

@@ -1543,19 +1543,42 @@ public final class Utils extends com.android.settingslib.Utils {
*/
public static void launchBiometricPromptForMandatoryBiometrics(@NonNull Fragment fragment,
int requestCode, int userId, boolean hideBackground) {
fragment.startActivityForResult(getIntentForBiometricAuthentication(fragment.getResources(),
userId, hideBackground), requestCode);
}
/**
* Launch biometric prompt for mandatory biometrics. Call
* {@link #requestBiometricAuthenticationForMandatoryBiometrics(Context, boolean, int)}
* to check if all requirements for mandatory biometrics is satisfied
* before launching biometric prompt.
*
* @param activity corresponding activity of the surface
* @param requestCode for starting the new activity
* @param userId user id for the authentication request
* @param hideBackground if the background activity screen needs to be hidden
*/
public static void launchBiometricPromptForMandatoryBiometrics(@NonNull Activity activity,
int requestCode, int userId, boolean hideBackground) {
activity.startActivityForResult(getIntentForBiometricAuthentication(
activity.getResources(), userId, hideBackground), requestCode);
}
private static Intent getIntentForBiometricAuthentication(Resources resources, int userId,
boolean hideBackground) {
final Intent intent = new Intent();
intent.putExtra(BIOMETRIC_PROMPT_AUTHENTICATORS,
BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
intent.putExtra(BIOMETRIC_PROMPT_NEGATIVE_BUTTON_TEXT,
fragment.getString(R.string.cancel));
resources.getString(R.string.cancel));
intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION,
fragment.getString(R.string.mandatory_biometrics_prompt_description));
resources.getString(R.string.mandatory_biometrics_prompt_description));
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, true);
intent.putExtra(EXTRA_USER_ID, userId);
intent.putExtra(BIOMETRIC_PROMPT_HIDE_BACKGROUND, hideBackground);
intent.setClassName(SETTINGS_PACKAGE_NAME,
ConfirmDeviceCredentialActivity.InternalActivity.class.getName());
fragment.startActivityForResult(intent, requestCode);
return intent;
}
private static void disableComponent(PackageManager pm, ComponentName componentName) {

View File

@@ -19,6 +19,7 @@ package com.android.settings.biometrics;
import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL;
import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED;
import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED;
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED;
@@ -51,6 +52,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.password.ChooseLockGeneric;
@@ -442,6 +444,16 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
if (!mParentalConsentHelper.launchNext(this, REQUEST_CHOOSE_OPTIONS)) {
Log.e(TAG, "Nothing to prompt for consent (no modalities enabled)!");
finish();
} else {
final Utils.BiometricStatus biometricStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(this,
false /* biometricsAuthenticationRequested */, mUserId);
if (biometricStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this,
BIOMETRIC_AUTH_REQUEST, mUserId, true /* hideBackground */);
} else if (biometricStatus != Utils.BiometricStatus.NOT_ACTIVE) {
finish();
}
}
} else {
Log.d(TAG, "Unknown result for set/choose lock: " + resultCode);
@@ -473,6 +485,10 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
finish();
}
break;
case BIOMETRIC_AUTH_REQUEST:
if (resultCode != RESULT_OK) {
finish();
}
default:
Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing");
finish();

View File

@@ -117,7 +117,6 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
public static final int LEARN_MORE_REQUEST = 3;
public static final int CONFIRM_REQUEST = 4;
public static final int ENROLL_REQUEST = 5;
public static final int BIOMETRIC_AUTH_REQUEST = 6;
/**
* Request code when starting another biometric enrollment from within a biometric flow. For
@@ -125,6 +124,7 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
*/
public static final int ENROLL_NEXT_BIOMETRIC_REQUEST = 6;
public static final int REQUEST_POSTURE_GUIDANCE = 7;
public static final int BIOMETRIC_AUTH_REQUEST = 8;
protected boolean mLaunchedConfirmLock;
protected boolean mLaunchedPostureGuidance;

View File

@@ -36,6 +36,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.Utils;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.SetupSkipDialog;
@@ -417,6 +418,15 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
getNextButton().setEnabled(true);
}));
}
final Utils.BiometricStatus biometricStatus =
Utils.requestBiometricAuthenticationForMandatoryBiometrics(this,
false /* biometricsAuthenticationRequested */, mUserId);
if (biometricStatus == Utils.BiometricStatus.OK) {
Utils.launchBiometricPromptForMandatoryBiometrics(this,
BIOMETRIC_AUTH_REQUEST, mUserId, true /* hideBackground */);
} else if (biometricStatus != Utils.BiometricStatus.NOT_ACTIVE) {
finish();
}
} else {
setResult(resultCode, data);
finish();
@@ -445,6 +455,10 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
setResult(resultCode, data);
finish();
}
} else if (requestCode == BIOMETRIC_AUTH_REQUEST) {
if (resultCode != RESULT_OK) {
finish();
}
}
super.onActivityResult(requestCode, resultCode, data);
}

View File

@@ -35,7 +35,6 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
/**
@@ -66,15 +65,11 @@ public class FastPairDeviceGroupController extends BasePreferenceController
public FastPairDeviceGroupController(Context context) {
super(context, KEY);
if (Flags.enableSubsequentPairSettingsIntegration()) {
FastPairFeatureProvider fastPairFeatureProvider =
FeatureFactory.getFeatureFactory().getFastPairFeatureProvider();
mFastPairDeviceUpdater =
fastPairFeatureProvider.getFastPairDeviceUpdater(context, this);
} else {
Log.d(TAG, "Flag disabled. Ignored.");
mFastPairDeviceUpdater = null;
}
FastPairFeatureProvider fastPairFeatureProvider =
FeatureFactory.getFeatureFactory().getFastPairFeatureProvider();
mFastPairDeviceUpdater =
fastPairFeatureProvider.getFastPairDeviceUpdater(context, this);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
}

View File

@@ -34,8 +34,8 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -75,43 +75,22 @@ public class FastPairDevicePreferenceController extends BasePreferenceController
public FastPairDevicePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
if (Flags.enableSubsequentPairSettingsIntegration()) {
FastPairFeatureProvider fastPairFeatureProvider =
FeatureFactory.getFeatureFactory().getFastPairFeatureProvider();
mFastPairDeviceUpdater =
fastPairFeatureProvider.getFastPairDeviceUpdater(context, this);
} else {
Log.d(TAG, "Flag disabled. Ignore.");
mFastPairDeviceUpdater = null;
}
FastPairFeatureProvider fastPairFeatureProvider =
FeatureFactory.getFeatureFactory().getFastPairFeatureProvider();
mFastPairDeviceUpdater =
fastPairFeatureProvider.getFastPairDeviceUpdater(context, this);
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.setPreferenceContext(mContext);
mFastPairDeviceUpdater.registerCallback();
} else {
if (DEBUG) {
Log.d(TAG, "Callback register: Fast Pair device updater is null. Ignore.");
}
}
mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
var unused = ThreadUtils.postOnBackgroundThread(() -> registerCallbacks());
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.setPreferenceContext(null);
mFastPairDeviceUpdater.unregisterCallback();
} else {
if (DEBUG) {
Log.d(TAG, "Callback unregister: Fast Pair device updater is null. Ignore.");
}
}
mContext.unregisterReceiver(mReceiver);
var unused = ThreadUtils.postOnBackgroundThread(() -> unregisterCallbacks());
}
@Override
@@ -208,4 +187,28 @@ public class FastPairDevicePreferenceController extends BasePreferenceController
mSeeAllPreference.setVisible(false);
}
}
private void registerCallbacks() {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.setPreferenceContext(mContext);
mFastPairDeviceUpdater.registerCallback();
} else {
if (DEBUG) {
Log.d(TAG, "Callback register: Fast Pair device updater is null. Ignore.");
}
}
mContext.registerReceiver(mReceiver, mIntentFilter, Context.RECEIVER_EXPORTED_UNAUDITED);
}
private void unregisterCallbacks() {
if (mFastPairDeviceUpdater != null) {
mFastPairDeviceUpdater.setPreferenceContext(null);
mFastPairDeviceUpdater.unregisterCallback();
} else {
if (DEBUG) {
Log.d(TAG, "Callback unregister: Fast Pair device updater is null. Ignore.");
}
}
mContext.unregisterReceiver(mReceiver);
}
}

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
/** Interface should be implemented if you have added new suggestions */
@@ -46,6 +47,20 @@ public interface SuggestionFeatureProvider {
/**
* Returns the class of {@link Fragment} that supports contextual suggestion.
*
* @deprecated - use {@link SuggestionFeatureProvider#getSuggestionFragment()} instead.
*/
Class<? extends Fragment> getContextualSuggestionFragment();
@Deprecated
@Nullable
default Class<? extends Fragment> getContextualSuggestionFragment() {
return null;
}
/**
* Returns the class of {@link Fragment} that provides the UI for Suggestions.
*/
@Nullable
default Class<? extends Fragment> getSuggestionFragment() {
return getContextualSuggestionFragment();
}
}

View File

@@ -22,12 +22,14 @@ import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.android.settings.Settings.NightDisplaySuggestionActivity;
import com.android.settings.biometrics.fingerprint.FingerprintEnrollSuggestionActivity;
import com.android.settings.biometrics.fingerprint.FingerprintSuggestionActivity;
import com.android.settings.display.NightDisplayPreferenceController;
import com.android.settings.flags.Flags;
import com.android.settings.notification.zen.ZenOnboardingActivity;
import com.android.settings.notification.zen.ZenSuggestionActivity;
import com.android.settings.password.ScreenLockSuggestionActivity;
@@ -81,8 +83,13 @@ public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider
return context.getSharedPreferences(SHARED_PREF_FILENAME, Context.MODE_PRIVATE);
}
@Nullable
@Override
public Class<? extends Fragment> getContextualSuggestionFragment() {
return null;
public Class<? extends Fragment> getSuggestionFragment() {
if (Flags.updatedSuggestionCardAosp()) {
return SuggestionFragment.class;
} else {
return null;
}
}
}

View File

@@ -0,0 +1,238 @@
/*
* 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.dashboard.suggestions
import android.app.ActivityOptions
import android.app.PendingIntent
import android.app.settings.SettingsEnums
import android.content.Context
import android.os.Bundle
import android.os.SystemClock
import android.service.settings.suggestions.Suggestion
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.android.settings.core.InstrumentedFragment
import com.android.settings.homepage.SettingsHomepageActivity
import com.android.settings.homepage.SplitLayoutListener
import com.android.settings.overlay.FeatureFactory
import com.android.settings.R
import com.android.settingslib.suggestions.SuggestionController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private const val SUGGESTIONS = "suggestions"
private const val TAG = "ContextualSuggestFrag"
private const val FLAG_IS_DISMISSIBLE = 1 shl 2
/**
* Fragment to control display and interaction logic for [Suggestion]s
*/
class SuggestionFragment : InstrumentedFragment(),
SplitLayoutListener, SuggestionController.ServiceConnectionListener {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
private lateinit var suggestionController: SuggestionController
private lateinit var suggestionTile: View
private var icon: ImageView? = null
private var iconFrame: View? = null
private var title: TextView? = null
private var summary: TextView? = null
private var dismiss: ImageView? = null
private var iconVisible = true
private var startTime: Long = 0
private var suggestionsRestored = false
private var splitLayoutSupported = false
override fun onAttach(context: Context) {
super.onAttach(context)
val component = FeatureFactory.featureFactory
.suggestionFeatureProvider
.suggestionServiceComponent
suggestionController = SuggestionController(context, component, this)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
suggestionTile = inflater.inflate(R.layout.suggestion_tile, container, true)
icon = suggestionTile.findViewById(android.R.id.icon)
iconFrame = suggestionTile.findViewById(android.R.id.icon_frame)
title = suggestionTile.findViewById(android.R.id.title)
summary = suggestionTile.findViewById(android.R.id.summary)
dismiss = suggestionTile.findViewById(android.R.id.closeButton)
if (!iconVisible) {
onSplitLayoutChanged(false)
}
// Restore the suggestion and skip reloading
if (savedInstanceState != null) {
Log.d(TAG, "Restoring suggestions")
savedInstanceState.getParcelableArrayList(
SUGGESTIONS,
Suggestion::class.java
)?.let { suggestions ->
suggestionsRestored = true
startTime = SystemClock.uptimeMillis()
updateState(suggestions)
}
}
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putParcelableArrayList(SUGGESTIONS, currentSuggestions)
super.onSaveInstanceState(outState)
}
override fun onStart() {
super.onStart()
suggestionController.start()
}
override fun onStop() {
suggestionController.stop()
super.onStop()
}
override fun getMetricsCategory(): Int {
return SettingsEnums.SETTINGS_HOMEPAGE
}
override fun setSplitLayoutSupported(supported: Boolean) {
splitLayoutSupported = supported
}
override fun onSplitLayoutChanged(isRegularLayout: Boolean) {
iconVisible = isRegularLayout
if (splitLayoutSupported) {
iconFrame?.visibility = if (iconVisible) View.VISIBLE else View.GONE
}
}
override fun onServiceConnected() {
loadSuggestions()
}
override fun onServiceDisconnected() {
// no-op
}
private fun loadSuggestions() {
if (suggestionsRestored) {
// Skip first suggestion loading when restored
suggestionsRestored = false
return
}
startTime = SystemClock.uptimeMillis()
scope.launch(Dispatchers.IO) {
Log.d(TAG, "Start loading suggestions")
val suggestions = suggestionController.suggestions
Log.d(TAG, "Loaded suggestions: ${suggestions?.size}")
withContext(Dispatchers.Main) {
updateState(suggestions)
}
}
}
private fun updateState(suggestions: List<Suggestion>?) {
currentSuggestions.clear()
if (suggestions.isNullOrEmpty()) {
Log.d(TAG, "Remove suggestions")
showSuggestionTile(false)
return
}
currentSuggestions.addAll(suggestions)
// Only take top suggestion; we assume this is the highest rank.
val suggestion = suggestions.first()
icon?.setImageIcon(suggestion.icon)
suggestion.title?.let {
title?.text = it
} ?: run {
Log.d(TAG, "No suggestion title, removing")
showSuggestionTile(false)
return
}
val suggestionSummary = suggestion.summary
if (suggestionSummary.isNullOrEmpty()) {
summary?.visibility = View.GONE
} else {
summary?.visibility = View.VISIBLE
summary?.text = suggestionSummary
}
if (suggestion.flags and FLAG_IS_DISMISSIBLE != 0) {
dismiss?.let { dismissView ->
dismissView.visibility = View.VISIBLE
dismissView.setOnClickListener {
scope.launch(Dispatchers.IO) {
suggestionController.dismissSuggestions(suggestion)
}
if (suggestions.size > 1) {
dismissView.visibility = View.GONE
updateState(suggestions.subList(1, suggestions.size))
} else {
currentSuggestions.clear()
suggestionTile.visibility = View.GONE
}
}
}
}
suggestionTile.setOnClickListener {
// Notify service that suggestion is being launched. Note that the service does not
// actually start the suggestion on our behalf, instead simply logging metrics.
scope.launch(Dispatchers.IO) {
suggestionController.launchSuggestion(suggestion)
}
currentSuggestions.clear()
try {
val options = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
suggestion.pendingIntent.send(options.toBundle())
} catch (e: PendingIntent.CanceledException) {
Log.e(TAG, "Failed to start suggestion ${suggestion.title}", e)
}
}
showSuggestionTile(true)
}
private fun showSuggestionTile(show: Boolean) {
val totalTime = SystemClock.uptimeMillis() - startTime
Log.d(TAG, "Total loading time: $totalTime ms")
mMetricsFeatureProvider.action(
context,
SettingsEnums.ACTION_CONTEXTUAL_HOME_SHOW,
totalTime.toInt()
)
(activity as? SettingsHomepageActivity)?.showHomepageWithSuggestion(show)
}
private companion object {
val currentSuggestions = arrayListOf<Suggestion>()
}
}

View File

@@ -276,7 +276,8 @@ public class SettingsHomepageActivity extends FragmentActivity implements
final boolean scrollNeeded = mIsEmbeddingActivityEnabled
&& !TextUtils.equals(getString(DEFAULT_HIGHLIGHT_MENU_KEY), highlightMenuKey);
showSuggestionFragment(scrollNeeded);
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) {
if (!Flags.updatedSuggestionCardAosp()
&& FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) {
showFragment(() -> new ContextualCardsFragment(), R.id.contextual_cards_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
@@ -477,7 +478,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements
private void showSuggestionFragment(boolean scrollNeeded) {
final Class<? extends Fragment> fragmentClass = FeatureFactory.getFeatureFactory()
.getSuggestionFeatureProvider().getContextualSuggestionFragment();
.getSuggestionFeatureProvider().getSuggestionFragment();
if (fragmentClass == null) {
return;
}

View File

@@ -27,7 +27,7 @@ import com.android.settings.R
import com.android.settings.SettingsPreferenceFragment
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.flags.Flags
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.euicc.EuiccRepository
import com.android.settings.search.BaseSearchIndexProvider
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.network.NetworkCellularGroupProvider
@@ -58,7 +58,7 @@ class MobileNetworkListFragment : DashboardFragment() {
listView.itemAnimator = null
findPreference<Preference>(KEY_ADD_SIM)!!.isVisible =
MobileNetworkUtils.showEuiccSettings(context)
EuiccRepository(requireContext()).showEuiccSettings()
}
override fun getPreferenceScreenResId() = R.xml.network_provider_sims_list

View File

@@ -35,7 +35,7 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settings.network.telephony.euicc.EuiccRepository;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.Utils;
@@ -118,7 +118,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController
if ((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) || (
mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) || (
mMobileNetworkInfoEntityList == null || mMobileNetworkInfoEntityList.isEmpty())) {
if (MobileNetworkUtils.showEuiccSettingsDetecting(mContext)) {
if (new EuiccRepository(mContext).showEuiccSettings()) {
return mContext.getResources().getString(
R.string.mobile_network_summary_add_a_network);
}
@@ -168,7 +168,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController
|| (mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty())
|| (mMobileNetworkInfoEntityList == null
|| mMobileNetworkInfoEntityList.isEmpty()))) {
if (MobileNetworkUtils.showEuiccSettingsDetecting(mContext)) {
if (new EuiccRepository(mContext).showEuiccSettings()) {
mPreference.setOnPreferenceClickListener((Preference pref) -> {
logPreferenceClick(pref);
startAddSimFlow();

View File

@@ -18,24 +18,17 @@ package com.android.settings.network.ims;
import android.content.Context;
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants;
import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
/**
* Controller class for querying VT status
*/
public class VtQueryImsState extends ImsQueryController {
public class VtQueryImsState {
private static final String LOG_TAG = "VtQueryImsState";
private Context mContext;
private int mSubId;
private final Context mContext;
private final int mSubId;
/**
* Constructor
@@ -44,9 +37,6 @@ public class VtQueryImsState extends ImsQueryController {
* @param subId subscription's id
*/
public VtQueryImsState(Context context, int subId) {
super(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
mContext = context;
mSubId = subId;
}
@@ -62,24 +52,6 @@ public class VtQueryImsState extends ImsQueryController {
return (new ImsQueryVtUserSetting(subId)).query();
}
/**
* Check whether Video Call can be perform or not on this subscription
*
* @return true when Video Call can be performed, otherwise false
*/
public boolean isReadyToVideoCall() {
if (!isProvisionedOnDevice(mSubId)) {
return false;
}
try {
return isEnabledByPlatform(mSubId) && isServiceStateReady(mSubId);
} catch (InterruptedException | IllegalArgumentException | ImsException exception) {
Log.w(LOG_TAG, "fail to get Vt ready. subId=" + mSubId, exception);
}
return false;
}
/**
* Get allowance status for user to alter configuration
*
@@ -89,8 +61,7 @@ public class VtQueryImsState extends ImsQueryController {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
return false;
}
return ((!isTtyEnabled(mContext))
|| (isTtyOnVolteEnabled(mSubId)));
return !isTtyEnabled(mContext) || new ImsQueryTtyOnVolteStat(mSubId).query();
}
@VisibleForTesting

View File

@@ -50,7 +50,7 @@ class CarrierConfigRepository(private val context: Context) {
private val keysToRetrieve = mutableMapOf<String, KeyType>()
override fun getBoolean(key: String): Boolean {
check(key.endsWith("_bool")) { "Boolean key should ends with _bool" }
checkBooleanKey(key)
val value = cache[key]
return if (value == null) {
keysToRetrieve += key to KeyType.BOOLEAN
@@ -186,9 +186,18 @@ class CarrierConfigRepository(private val context: Context) {
ListenerRegistered.getAndSet(false)
}
private val BooleanKeysWhichNotFollowingsNamingConventions =
listOf(CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS)
private fun checkBooleanKey(key: String) {
check(key.endsWith("_bool") || key in BooleanKeysWhichNotFollowingsNamingConventions) {
"Boolean key should ends with _bool"
}
}
@VisibleForTesting
fun setBooleanForTest(subId: Int, key: String, value: Boolean) {
check(key.endsWith("_bool")) { "Boolean key should ends with _bool" }
checkBooleanKey(key)
getPerSubCache(subId)[key] = BooleanConfigValue(value)
}

View File

@@ -26,6 +26,7 @@ import com.android.settings.network.telephony.DataUsagePreferenceController.Comp
import com.android.settings.network.telephony.MmsMessagePreferenceController.Companion.MmsMessageSearchItem
import com.android.settings.network.telephony.NrAdvancedCallingPreferenceController.Companion.NrAdvancedCallingSearchItem
import com.android.settings.network.telephony.RoamingPreferenceController.Companion.RoamingSearchItem
import com.android.settings.network.telephony.VideoCallingPreferenceController.Companion.VideoCallingSearchItem
import com.android.settings.network.telephony.WifiCallingPreferenceController.Companion.WifiCallingSearchItem
import com.android.settings.spa.SpaSearchLanding.BundleValue
import com.android.settings.spa.SpaSearchLanding.SpaSearchLandingFragment
@@ -122,6 +123,7 @@ class MobileNetworkSettingsSearchIndex(
NrAdvancedCallingSearchItem(context),
PreferredNetworkModeSearchItem(context),
RoamingSearchItem(context),
VideoCallingSearchItem(context),
WifiCallingSearchItem(context),
)
}

View File

@@ -32,7 +32,6 @@ import static com.android.settings.network.telephony.TelephonyConstants.Telephon
import static com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA;
import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -51,8 +50,6 @@ import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -89,32 +86,17 @@ import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settings.network.telephony.TelephonyConstants.TelephonyManagerConstants;
import com.android.settings.network.telephony.wificalling.WifiCallingRepository;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import com.android.settingslib.utils.ThreadUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class MobileNetworkUtils {
private static final String TAG = "MobileNetworkUtils";
// CID of the device.
private static final String KEY_CID = "ro.boot.cid";
// CIDs of devices which should not show anything related to eSIM.
private static final String KEY_ESIM_CID_IGNORE = "ro.setupwizard.esim_cid_ignore";
// System Property which is used to decide whether the default eSIM UI will be shown,
// the default value is false.
private static final String KEY_ENABLE_ESIM_UI_BY_DEFAULT =
"esim.enable_esim_system_ui_by_default";
private static final String LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT =
"android.telecom.action.CONNECTION_SERVICE_CONFIGURE";
private static final String RTL_MARK = "\u200F";
@@ -282,64 +264,6 @@ public class MobileNetworkUtils {
return intent;
}
/**
* Whether to show the entry point to eUICC settings.
*
* <p>We show the entry point on any device which supports eUICC as long as either the eUICC
* was ever provisioned (that is, at least one profile was ever downloaded onto it), or if
* the user has enabled development mode.
*/
public static boolean showEuiccSettings(Context context) {
if (!SubscriptionUtil.isSimHardwareVisible(context)) {
return false;
}
long timeForAccess = SystemClock.elapsedRealtime();
try {
Boolean isShow = ((Future<Boolean>) ThreadUtils.postOnBackgroundThread(() -> {
try {
return showEuiccSettingsDetecting(context);
} catch (Exception threadException) {
Log.w(TAG, "Accessing Euicc failure", threadException);
}
return Boolean.FALSE;
})).get(3, TimeUnit.SECONDS);
return ((isShow != null) && isShow.booleanValue());
} catch (ExecutionException | InterruptedException | TimeoutException exception) {
timeForAccess = SystemClock.elapsedRealtime() - timeForAccess;
Log.w(TAG, "Accessing Euicc takes too long: +" + timeForAccess + "ms");
}
return false;
}
// The same as #showEuiccSettings(Context context)
public static Boolean showEuiccSettingsDetecting(Context context) {
final EuiccManager euiccManager =
(EuiccManager) context.getSystemService(EuiccManager.class);
if (euiccManager == null || !euiccManager.isEnabled()) {
Log.w(TAG, "EuiccManager is not enabled.");
return false;
}
final ContentResolver cr = context.getContentResolver();
final boolean esimIgnoredDevice =
Arrays.asList(TextUtils.split(SystemProperties.get(KEY_ESIM_CID_IGNORE, ""), ","))
.contains(SystemProperties.get(KEY_CID));
final boolean enabledEsimUiByDefault =
SystemProperties.getBoolean(KEY_ENABLE_ESIM_UI_BY_DEFAULT, true);
final boolean euiccProvisioned =
Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) != 0;
final boolean inDeveloperMode =
DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
Log.i(TAG,
String.format("showEuiccSettings: esimIgnoredDevice: %b, enabledEsimUiByDefault: "
+ "%b, euiccProvisioned: %b, inDeveloperMode: %b.",
esimIgnoredDevice, enabledEsimUiByDefault, euiccProvisioned, inDeveloperMode));
return (euiccProvisioned
|| (!esimIgnoredDevice && inDeveloperMode)
|| (!esimIgnoredDevice && enabledEsimUiByDefault
&& isCurrentCountrySupported(context)));
}
/**
* Return {@code true} if mobile data is enabled
*/

View File

@@ -1,234 +0,0 @@
/*
* Copyright (C) 2018 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.network.telephony;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsMmTelManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.internal.telephony.flags.Flags;
import com.android.settings.network.CarrierConfigCache;
import com.android.settings.network.MobileDataEnabledListener;
import com.android.settings.network.ims.VolteQueryImsState;
import com.android.settings.network.ims.VtQueryImsState;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
/**
* Preference controller for "Video Calling"
*/
public class VideoCallingPreferenceController extends TelephonyTogglePreferenceController implements
LifecycleObserver, OnStart, OnStop,
MobileDataEnabledListener.Client,
Enhanced4gBasePreferenceController.On4gLteUpdateListener {
private static final String TAG = "VideoCallingPreference";
private Preference mPreference;
private PhoneTelephonyCallback mTelephonyCallback;
@VisibleForTesting
Integer mCallState;
private MobileDataEnabledListener mDataContentObserver;
private CallingPreferenceCategoryController mCallingPreferenceCategoryController;
public VideoCallingPreferenceController(Context context, String key) {
super(context, key);
mDataContentObserver = new MobileDataEnabledListener(context, this);
mTelephonyCallback = new PhoneTelephonyCallback();
}
@Override
public int getAvailabilityStatus(int subId) {
return SubscriptionManager.isValidSubscriptionId(subId)
&& isVideoCallEnabled(subId)
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public void onStart() {
mTelephonyCallback.register(mContext, mSubId);
mDataContentObserver.start(mSubId);
}
@Override
public void onStop() {
mTelephonyCallback.unregister();
mDataContentObserver.stop();
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if ((mCallState == null) || (preference == null)) {
Log.d(TAG, "Skip update under mCallState=" + mCallState);
return;
}
final TwoStatePreference switchPreference = (TwoStatePreference) preference;
final boolean videoCallEnabled = isVideoCallEnabled(mSubId);
switchPreference.setVisible(videoCallEnabled);
mCallingPreferenceCategoryController
.updateChildVisible(getPreferenceKey(), videoCallEnabled);
if (videoCallEnabled) {
final boolean videoCallEditable = queryVoLteState(mSubId).isEnabledByUser()
&& queryImsState(mSubId).isAllowUserControl();
preference.setEnabled(videoCallEditable
&& mCallState == TelephonyManager.CALL_STATE_IDLE);
switchPreference.setChecked(videoCallEditable && isChecked());
}
}
@Override
public boolean setChecked(boolean isChecked) {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
return false;
}
final ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(mSubId);
if (imsMmTelManager == null) {
return false;
}
try {
imsMmTelManager.setVtSettingEnabled(isChecked);
return true;
} catch (IllegalArgumentException exception) {
Log.w(TAG, "Unable to set VT status " + isChecked + ". subId=" + mSubId,
exception);
}
return false;
}
@Override
public boolean isChecked() {
return queryImsState(mSubId).isEnabledByUser();
}
@VisibleForTesting
protected boolean isImsSupported() {
return mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_TELEPHONY_IMS);
}
/**
* Init instance of VideoCallingPreferenceController.
*/
public VideoCallingPreferenceController init(
int subId, CallingPreferenceCategoryController callingPreferenceCategoryController) {
mSubId = subId;
mCallingPreferenceCategoryController = callingPreferenceCategoryController;
return this;
}
@VisibleForTesting
boolean isVideoCallEnabled(int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return false;
}
final PersistableBundle carrierConfig =
CarrierConfigCache.getInstance(mContext).getConfigForSubId(subId);
if (carrierConfig == null) {
return false;
}
if (!carrierConfig.getBoolean(
CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS)
&& (!mContext.getSystemService(TelephonyManager.class)
.createForSubscriptionId(subId).isDataEnabled())) {
return false;
}
return isImsSupported() && queryImsState(subId).isReadyToVideoCall();
}
@Override
public void on4gLteUpdated() {
updateState(mPreference);
}
private class PhoneTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.CallStateListener {
private TelephonyManager mTelephonyManager;
@Override
public void onCallStateChanged(int state) {
mCallState = state;
updateState(mPreference);
}
public void register(Context context, int subId) {
mTelephonyManager = context.getSystemService(TelephonyManager.class);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
mTelephonyManager = mTelephonyManager.createForSubscriptionId(subId);
}
// assign current call state so that it helps to show correct preference state even
// before first onCallStateChanged() by initial registration.
if (Flags.enforceTelephonyFeatureMappingForPublicApis()) {
try {
mCallState = mTelephonyManager.getCallState(subId);
} catch (UnsupportedOperationException e) {
// Device doesn't support FEATURE_TELEPHONY_CALLING
mCallState = TelephonyManager.CALL_STATE_IDLE;
}
} else {
mCallState = mTelephonyManager.getCallState(subId);
}
mTelephonyManager.registerTelephonyCallback(context.getMainExecutor(), this);
}
public void unregister() {
mCallState = null;
mTelephonyManager.unregisterTelephonyCallback(this);
}
}
/**
* Implementation of MobileDataEnabledListener.Client
*/
public void onMobileDataEnabledChange() {
updateState(mPreference);
}
@VisibleForTesting
VtQueryImsState queryImsState(int subId) {
return new VtQueryImsState(mContext, subId);
}
@VisibleForTesting
VolteQueryImsState queryVoLteState(int subId) {
return new VolteQueryImsState(mContext, subId);
}
}

View File

@@ -0,0 +1,147 @@
/*
* 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.network.telephony
import android.content.Context
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.telephony.ims.ImsManager
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleOwner
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import androidx.preference.TwoStatePreference
import com.android.settings.R
import com.android.settings.core.TogglePreferenceController
import com.android.settings.network.ims.VolteQueryImsState
import com.android.settings.network.ims.VtQueryImsState
import com.android.settings.network.telephony.Enhanced4gBasePreferenceController.On4gLteUpdateListener
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchItem
import com.android.settings.network.telephony.MobileNetworkSettingsSearchIndex.MobileNetworkSettingsSearchResult
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
/** Preference controller for "Video Calling" */
class VideoCallingPreferenceController
@JvmOverloads
constructor(
context: Context,
key: String,
private val callStateRepository: CallStateRepository = CallStateRepository(context),
) : TogglePreferenceController(context, key), On4gLteUpdateListener {
private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID
private var preference: TwoStatePreference? = null
private var callingPreferenceCategoryController: CallingPreferenceCategoryController? = null
private val repository = VideoCallingRepository(context)
private var videoCallEditable = false
private var isInCall = false
/** Init instance of VideoCallingPreferenceController. */
fun init(
subId: Int,
callingPreferenceCategoryController: CallingPreferenceCategoryController?,
): VideoCallingPreferenceController {
this.subId = subId
this.callingPreferenceCategoryController = callingPreferenceCategoryController
return this
}
// Availability is controlled in onViewCreated() and VideoCallingSearchItem.
override fun getAvailabilityStatus() = AVAILABLE
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)
}
override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
repository.isVideoCallReadyFlow(subId).collectLatestWithLifecycle(viewLifecycleOwner) {
isReady ->
preference?.isVisible = isReady
callingPreferenceCategoryController?.updateChildVisible(preferenceKey, isReady)
}
callStateRepository.callStateFlow(subId).collectLatestWithLifecycle(viewLifecycleOwner) {
callState ->
isInCall = callState != TelephonyManager.CALL_STATE_IDLE
updatePreference()
}
}
override fun updateState(preference: Preference) {
super.updateState(preference)
videoCallEditable =
queryVoLteState(subId).isEnabledByUser && queryImsState(subId).isAllowUserControl
updatePreference()
}
private fun updatePreference() {
preference?.isEnabled = videoCallEditable && !isInCall
preference?.isChecked = videoCallEditable && isChecked
}
override fun getSliceHighlightMenuRes() = NO_RES
override fun setChecked(isChecked: Boolean): Boolean {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
return false
}
val imsMmTelManager = ImsManager(mContext).getImsMmTelManager(subId)
try {
imsMmTelManager.isVtSettingEnabled = isChecked
return true
} catch (exception: IllegalArgumentException) {
Log.w(TAG, "[$subId] Unable to set VT status $isChecked", exception)
}
return false
}
override fun isChecked(): Boolean = queryImsState(subId).isEnabledByUser
override fun on4gLteUpdated() {
preference?.let { updateState(it) }
}
@VisibleForTesting fun queryImsState(subId: Int) = VtQueryImsState(mContext, subId)
@VisibleForTesting fun queryVoLteState(subId: Int) = VolteQueryImsState(mContext, subId)
companion object {
private const val TAG = "VideoCallingPreferenceController"
class VideoCallingSearchItem(private val context: Context) :
MobileNetworkSettingsSearchItem {
private val repository = VideoCallingRepository(context)
private fun isAvailable(subId: Int): Boolean = runBlocking {
repository.isVideoCallReadyFlow(subId).first()
}
override fun getSearchResult(subId: Int): MobileNetworkSettingsSearchResult? {
if (!isAvailable(subId)) return null
return MobileNetworkSettingsSearchResult(
key = "video_calling_key",
title = context.getString(R.string.video_calling_settings_title),
)
}
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.network.telephony
import android.content.Context
import android.telephony.AccessNetworkConstants
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.ims.feature.MmTelFeature
import android.telephony.ims.stub.ImsRegistrationImplBase
import com.android.settings.network.telephony.ims.ImsFeatureRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@OptIn(ExperimentalCoroutinesApi::class)
class VideoCallingRepository(
context: Context,
private val mobileDataRepository: MobileDataRepository = MobileDataRepository(context),
private val imsFeatureRepositoryFactory: (Int) -> ImsFeatureRepository = { subId ->
ImsFeatureRepository(context, subId)
},
) {
private val carrierConfigRepository = CarrierConfigRepository(context)
fun isVideoCallReadyFlow(subId: Int): Flow<Boolean> {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
return isPreconditionMeetFlow(subId).flatMapLatest { isPreconditionMeet ->
if (isPreconditionMeet) {
imsFeatureRepositoryFactory(subId)
.isReadyFlow(
capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
)
} else {
flowOf(false)
}
}
}
private fun isPreconditionMeetFlow(subId: Int): Flow<Boolean> =
if (carrierConfigRepository.getBoolean(
subId, CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS)) {
flowOf(true)
} else {
mobileDataRepository.isMobileDataEnabledFlow(subId)
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.network.telephony.euicc
import android.content.Context
import android.os.SystemProperties
import android.provider.Settings
import android.telephony.TelephonyManager
import android.telephony.euicc.EuiccManager
import android.util.Log
import com.android.settings.network.SubscriptionUtil
import com.android.settingslib.development.DevelopmentSettingsEnabler
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
class EuiccRepository
@JvmOverloads
constructor(
private val context: Context,
private val isEuiccProvisioned: () -> Boolean = {
val euiccProvisioned by context.settingsGlobalBoolean(Settings.Global.EUICC_PROVISIONED)
euiccProvisioned
},
private val isDevelopmentSettingsEnabled: () -> Boolean = {
DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context)
},
) {
private val euiccManager = context.getSystemService(EuiccManager::class.java)
private val telephonyManager = context.getSystemService(TelephonyManager::class.java)
fun showEuiccSettingsFlow() =
flow { emit(showEuiccSettings()) }
.distinctUntilChanged()
.conflate()
.flowOn(Dispatchers.Default)
/**
* Whether to show the entry point to eUICC settings.
*
* We show the entry point on any device which supports eUICC as long as either the eUICC was
* ever provisioned (that is, at least one profile was ever downloaded onto it), or if the user
* has enabled development mode.
*/
fun showEuiccSettings(): Boolean {
if (!SubscriptionUtil.isSimHardwareVisible(context)) return false
if (euiccManager == null || !euiccManager.isEnabled) {
Log.w(TAG, "EuiccManager is not enabled.")
return false
}
if (isEuiccProvisioned()) {
Log.i(TAG, "showEuiccSettings: euicc provisioned")
return true
}
val ignoredCids =
SystemProperties.get(KEY_ESIM_CID_IGNORE).split(',').filter { it.isNotEmpty() }
val cid = SystemProperties.get(KEY_CID)
if (cid in ignoredCids) {
Log.i(TAG, "showEuiccSettings: cid ignored")
return false
}
if (isDevelopmentSettingsEnabled()) {
Log.i(TAG, "showEuiccSettings: development settings enabled")
return true
}
val enabledEsimUiByDefault =
SystemProperties.getBoolean(KEY_ENABLE_ESIM_UI_BY_DEFAULT, true)
Log.i(TAG, "showEuiccSettings: enabledEsimUiByDefault=$enabledEsimUiByDefault")
return enabledEsimUiByDefault && isCurrentCountrySupported()
}
/**
* Loop through all the device logical slots to check whether the user's current country
* supports eSIM.
*/
private fun isCurrentCountrySupported(): Boolean {
val euiccManager = euiccManager ?: return false
val telephonyManager = telephonyManager ?: return false
val visitedCountrySet = mutableSetOf<String>()
for (slotIndex in 0 until telephonyManager.getActiveModemCount()) {
val countryCode = telephonyManager.getNetworkCountryIso(slotIndex)
if (
countryCode.isNotEmpty() &&
visitedCountrySet.add(countryCode) &&
euiccManager.isSupportedCountry(countryCode)
) {
Log.i(TAG, "isCurrentCountrySupported: $countryCode is supported")
return true
}
}
Log.i(TAG, "isCurrentCountrySupported: no country is supported")
return false
}
companion object {
private const val TAG = "EuiccRepository"
/** CID of the device. */
private const val KEY_CID: String = "ro.boot.cid"
/** CIDs of devices which should not show anything related to eSIM. */
private const val KEY_ESIM_CID_IGNORE: String = "ro.setupwizard.esim_cid_ignore"
/**
* System Property which is used to decide whether the default eSIM UI will be shown, the
* default value is false.
*/
private const val KEY_ENABLE_ESIM_UI_BY_DEFAULT: String =
"esim.enable_esim_system_ui_by_default"
}
}

View File

@@ -18,6 +18,7 @@ package com.android.settings.notification.modes;
import android.app.Flags;
import android.content.Context;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -30,6 +31,7 @@ import com.android.settingslib.notification.modes.ZenModesBackend;
public class ZenModesLinkPreferenceController extends BasePreferenceController
implements LifecycleObserver, OnStart, OnStop {
private static final String TAG = "ModesLinkPrefController";
private final ZenModesBackend mBackend;
private final ZenSettingsObserver mSettingObserver;
@@ -71,7 +73,13 @@ public class ZenModesLinkPreferenceController extends BasePreferenceController
@Override
public void updateState(Preference preference) {
preference.setSummary(mSummaryBuilder.getModesSummary(mBackend.getModes()));
try {
preference.setSummary(mSummaryBuilder.getModesSummary(mBackend.getModes()));
} catch (SecurityException e) {
// Standard usage should have the correct permissions to read zen state. But if we don't
// for whatever reason, don't crash.
Log.w(TAG, "No permission to read mode state");
}
}
@Override

View File

@@ -40,6 +40,7 @@ import com.android.settings.network.SubscriptionUtil
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.SubscriptionActivationRepository
import com.android.settings.network.telephony.SubscriptionRepository
import com.android.settings.network.telephony.euicc.EuiccRepository
import com.android.settings.network.telephony.phoneNumberFlow
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
@@ -120,13 +121,17 @@ fun phoneNumber(subInfo: SubscriptionInfo): State<String?> {
@Composable
private fun AddSim() {
val context = LocalContext.current
if (remember { MobileNetworkUtils.showEuiccSettings(context) }) {
val isShow by
remember { EuiccRepository(context).showEuiccSettingsFlow() }
.collectAsStateWithLifecycle(initialValue = false)
if (isShow) {
RestrictedPreference(
model = object : PreferenceModel {
override val title = stringResource(id = R.string.mobile_network_list_add_more)
override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) }
override val onClick = { startAddSimFlow(context) }
},
model =
object : PreferenceModel {
override val title = stringResource(id = R.string.mobile_network_list_add_more)
override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) }
override val onClick = { startAddSimFlow(context) }
},
restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
)
}

View File

@@ -556,6 +556,7 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
return mContext.getDrawable(getHotspotIconResource(deviceType));
}
if (mWifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
Log.w(TAG, "WiFi level is WIFI_LEVEL_UNREACHABLE(-1)");
return mContext.getDrawable(R.drawable.empty_icon);
}
return mIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), wifiEntry.getLevel());

View File

@@ -36,7 +36,6 @@ import com.android.settings.R;
import com.android.settings.connecteddevice.fastpair.FastPairDeviceUpdater;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerListHelper;
import com.android.settings.flags.Flags;
import com.android.settings.slices.SlicePreferenceController;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
@@ -94,7 +93,6 @@ public class ConnectedDeviceDashboardFragmentTest {
mContext = spy(RuntimeEnvironment.application);
mFragment = new ConnectedDeviceDashboardFragment();
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION);
mSetFlagsRule.enableFlags(com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
mFeatureFactory = FakeFeatureFactory.setupForTest();
when(mFeatureFactory

View File

@@ -33,8 +33,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Looper;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -45,7 +43,6 @@ import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.widget.GearPreference;
@@ -107,7 +104,6 @@ public class FastPairDeviceGroupControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStart_flagOn_registerCallback() {
// register the callback in onStart()
mFastPairDeviceGroupController.onStart(mLifecycleOwner);
@@ -120,7 +116,6 @@ public class FastPairDeviceGroupControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStop_flagOn_unregisterCallback() {
// register broadcast first
mContext.registerReceiver(
@@ -133,51 +128,6 @@ public class FastPairDeviceGroupControllerTest {
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStart_flagOff_registerCallback() {
// register the callback in onStart()
mFastPairDeviceGroupController.onStart(mLifecycleOwner);
assertThat(mFastPairDeviceUpdater).isNull();
verify(mContext)
.registerReceiver(
mFastPairDeviceGroupController.mReceiver,
mFastPairDeviceGroupController.mIntentFilter,
Context.RECEIVER_EXPORTED);
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStop_flagOff_unregisterCallback() {
// register broadcast first
mContext.registerReceiver(
mFastPairDeviceGroupController.mReceiver, null, Context.RECEIVER_EXPORTED);
// unregister the callback in onStop()
mFastPairDeviceGroupController.onStop(mLifecycleOwner);
assertThat(mFastPairDeviceUpdater).isNull();
verify(mContext).unregisterReceiver(mFastPairDeviceGroupController.mReceiver);
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_noFastPairFeature_returnUnSupported() {
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
assertThat(mFastPairDeviceGroupController.getAvailabilityStatus())
.isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_noBluetoothFastPairFeature_returnUnSupported() {
doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
assertThat(mFastPairDeviceGroupController.getAvailabilityStatus())
.isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_noBluetoothFeature_returnUnSupported() {
doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
@@ -186,7 +136,6 @@ public class FastPairDeviceGroupControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_withBluetoothFastPairFeature_returnSupported() {
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
@@ -231,14 +180,6 @@ public class FastPairDeviceGroupControllerTest {
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void displayPreference_notAvailable_doNothing() {
mFastPairDeviceGroupController.displayPreference(mScreen);
assertThat(mPreferenceGroup.isVisible()).isFalse();
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void displayPreference_isAvailable_fetchFastPairDevices() {
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);

View File

@@ -32,8 +32,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Looper;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -44,7 +42,6 @@ import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.widget.GearPreference;
@@ -108,7 +105,6 @@ public class FastPairDevicePreferenceControllerTest {
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStart_flagOn_registerCallback() {
// register the callback in onStart()
mFastPairDevicePrefController.onStart(mLifecycleOwner);
@@ -121,20 +117,6 @@ public class FastPairDevicePreferenceControllerTest {
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStart_flagOff_registerCallback() {
// register the callback in onStart()
mFastPairDevicePrefController.onStart(mLifecycleOwner);
assertThat(mFastPairDeviceUpdater).isNull();
verify(mContext)
.registerReceiver(
mFastPairDevicePrefController.mReceiver,
mFastPairDevicePrefController.mIntentFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStop_flagOn_unregisterCallback() {
// register broadcast first
mContext.registerReceiver(
@@ -147,20 +129,6 @@ public class FastPairDevicePreferenceControllerTest {
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void onStop_flagOff_unregisterCallback() {
// register broadcast first
mContext.registerReceiver(
mFastPairDevicePrefController.mReceiver, null, Context.RECEIVER_EXPORTED_UNAUDITED);
// unregister the callback in onStop()
mFastPairDevicePrefController.onStop(mLifecycleOwner);
assertThat(mFastPairDeviceUpdater).isNull();
verify(mContext).unregisterReceiver(mFastPairDevicePrefController.mReceiver);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_noBluetoothFeature_returnUnsupported() {
doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
@@ -169,25 +137,6 @@ public class FastPairDevicePreferenceControllerTest {
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_noFastPairFeature_returnUnsupported() {
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
assertThat(mFastPairDevicePrefController.getAvailabilityStatus())
.isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_noBluetoothFastPairFeature_returnUnsupported() {
doReturn(false).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
assertThat(mFastPairDevicePrefController.getAvailabilityStatus())
.isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SUBSEQUENT_PAIR_SETTINGS_INTEGRATION)
public void getAvailabilityStatus_bothBluetoothFastPairFeature_returnSupported() {
doReturn(true).when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);

View File

@@ -25,12 +25,19 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.settings.suggestions.Suggestion;
import androidx.fragment.app.Fragment;
import com.android.settings.flags.Flags;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowSecureSettings;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -43,6 +50,9 @@ import org.robolectric.annotation.Config;
@Config(shadows = ShadowSecureSettings.class)
public class SuggestionFeatureProviderImplTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private Context mContext;
@Mock
@@ -94,4 +104,20 @@ public class SuggestionFeatureProviderImplTest {
when(mActivityManager.isLowRamDevice()).thenReturn(false);
assertThat(mProvider.isSuggestionEnabled(mContext)).isTrue();
}
@DisableFlags(Flags.FLAG_UPDATED_SUGGESTION_CARD_AOSP)
@Test
public void getSuggestionFragment_withFlagDisabled_shouldReturnNull() {
Class<? extends Fragment> fragment = mProvider.getSuggestionFragment();
assertThat(fragment).isNull();
}
@EnableFlags(Flags.FLAG_UPDATED_SUGGESTION_CARD_AOSP)
@Test
public void getSuggestionFragment_withFlagEnabled_shouldReturnFragment() {
Class<? extends Fragment> fragment = mProvider.getSuggestionFragment();
assertThat(fragment).isEqualTo(SuggestionFragment.class);
}
}

View File

@@ -444,7 +444,7 @@ public class SettingsHomepageActivityTest {
public static class ShadowSuggestionFeatureProviderImpl {
@Implementation
public Class<? extends Fragment> getContextualSuggestionFragment() {
public Class<? extends Fragment> getSuggestionFragment() {
return Fragment.class;
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright (C) 2020 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.network.ims;
import android.content.Context;
import android.telephony.ims.ImsException;
/**
* Controller class for mock VoLte status
*/
public class MockVolteQueryImsState extends VolteQueryImsState {
private Boolean mIsTtyOnVolteEnabled;
private Boolean mIsSupported;
private Boolean mIsProvisionedOnDevice;
private Boolean mIsServiceStateReady;
private Boolean mIsEnabledByUser;
/**
* Constructor
*
* @param context {@link Context}
* @param subId subscription's id
*/
public MockVolteQueryImsState(Context context, int subId) {
super(context, subId);
}
public void setIsTtyOnVolteEnabled(boolean enabled) {
mIsTtyOnVolteEnabled = enabled;
}
@Override
boolean isTtyOnVolteEnabled(int subId) {
if (mIsTtyOnVolteEnabled != null) {
return mIsTtyOnVolteEnabled;
}
return super.isTtyOnVolteEnabled(subId);
}
public void setEnabledByPlatform(boolean isSupported) {
mIsSupported = isSupported;
}
@Override
boolean isEnabledByPlatform(int subId) throws InterruptedException, ImsException,
IllegalArgumentException {
if (mIsSupported != null) {
return mIsSupported;
}
return super.isEnabledByPlatform(subId);
}
public void setIsProvisionedOnDevice(boolean isProvisioned) {
mIsProvisionedOnDevice = isProvisioned;
}
@Override
boolean isProvisionedOnDevice(int subId) {
if (mIsProvisionedOnDevice != null) {
return mIsProvisionedOnDevice;
}
return super.isProvisionedOnDevice(subId);
}
public void setServiceStateReady(boolean isReady) {
mIsServiceStateReady = isReady;
}
@Override
boolean isServiceStateReady(int subId) throws InterruptedException, ImsException,
IllegalArgumentException {
if (mIsServiceStateReady != null) {
return mIsServiceStateReady;
}
return super.isServiceStateReady(subId);
}
public void setIsEnabledByUser(boolean enabled) {
mIsEnabledByUser = enabled;
}
@Override
boolean isEnabledByUser(int subId) {
if (mIsEnabledByUser != null) {
return mIsEnabledByUser;
}
return super.isEnabledByUser(subId);
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright (C) 2020 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.network.ims;
import android.content.Context;
import android.telephony.ims.ImsException;
/**
* Controller class for mock VT status
*/
public class MockVtQueryImsState extends VtQueryImsState {
private Boolean mIsTtyOnVolteEnabled;
private Boolean mIsEnabledOnPlatform;
private Boolean mIsProvisionedOnDevice;
private Boolean mIsEnabledByUser;
private Boolean mIsServiceStateReady;
/**
* Constructor
*
* @param context {@link Context}
* @param subId subscription's id
*/
public MockVtQueryImsState(Context context, int subId) {
super(context, subId);
}
public void setIsTtyOnVolteEnabled(boolean enabled) {
mIsTtyOnVolteEnabled = enabled;
}
@Override
boolean isTtyOnVolteEnabled(int subId) {
if (mIsTtyOnVolteEnabled != null) {
return mIsTtyOnVolteEnabled;
}
return super.isTtyOnVolteEnabled(subId);
}
public void setIsEnabledByPlatform(boolean isEnabled) {
mIsEnabledOnPlatform = isEnabled;
}
@Override
boolean isEnabledByPlatform(int subId) throws InterruptedException, ImsException,
IllegalArgumentException {
if (mIsEnabledOnPlatform != null) {
return mIsEnabledOnPlatform;
}
return super.isEnabledByPlatform(subId);
}
public void setIsProvisionedOnDevice(boolean isProvisioned) {
mIsProvisionedOnDevice = isProvisioned;
}
@Override
boolean isProvisionedOnDevice(int subId) {
if (mIsProvisionedOnDevice != null) {
return mIsProvisionedOnDevice;
}
return super.isProvisionedOnDevice(subId);
}
public void setServiceStateReady(boolean isReady) {
mIsServiceStateReady = isReady;
}
@Override
boolean isServiceStateReady(int subId) throws InterruptedException, ImsException,
IllegalArgumentException {
if (mIsServiceStateReady != null) {
return mIsServiceStateReady;
}
return super.isServiceStateReady(subId);
}
public void setIsEnabledByUser(boolean enabled) {
mIsEnabledByUser = enabled;
}
@Override
boolean isEnabledByUser(int subId) {
if (mIsEnabledByUser != null) {
return mIsEnabledByUser;
}
return super.isEnabledByUser(subId);
}
}

View File

@@ -1,160 +0,0 @@
/*
* Copyright (C) 2018 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.network.telephony;
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.spy;
import android.content.Context;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.TelephonyManager;
import android.telephony.ims.ProvisioningManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.network.CarrierConfigCache;
import com.android.settings.network.ims.MockVolteQueryImsState;
import com.android.settings.network.ims.MockVtQueryImsState;
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;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class VideoCallingPreferenceControllerTest {
private static final int SUB_ID = 2;
@Mock
private TelephonyManager mTelephonyManager;
@Mock
private ProvisioningManager mProvisioningManager;
@Mock
private CarrierConfigCache mCarrierConfigCache;
@Mock
private PreferenceScreen mPreferenceScreen;
private MockVtQueryImsState mQueryImsState;
private MockVolteQueryImsState mQueryVoLteState;
private VideoCallingPreferenceController mController;
private PersistableBundle mCarrierConfig;
private SwitchPreference mPreference;
private Context mContext;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class);
CarrierConfigCache.setTestInstance(mContext, mCarrierConfigCache);
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID);
mCarrierConfig = new PersistableBundle();
mCarrierConfig.putBoolean(
CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
doReturn(mCarrierConfig).when(mCarrierConfigCache).getConfigForSubId(SUB_ID);
mQueryImsState = new MockVtQueryImsState(mContext, SUB_ID);
mQueryImsState.setIsEnabledByUser(true);
mQueryVoLteState = new MockVolteQueryImsState(mContext, SUB_ID);
mQueryVoLteState.setIsEnabledByUser(true);
mPreference = new SwitchPreference(mContext);
mController = spy(new VideoCallingPreferenceController(mContext, "wifi_calling"));
mController.init(
SUB_ID, new CallingPreferenceCategoryController(mContext, "calling_category"));
doReturn(mQueryImsState).when(mController).queryImsState(anyInt());
doReturn(mQueryVoLteState).when(mController).queryVoLteState(anyInt());
doReturn(true).when(mController).isImsSupported();
mPreference.setKey(mController.getPreferenceKey());
mQueryImsState.setIsEnabledByPlatform(true);
mQueryImsState.setIsProvisionedOnDevice(true);
mQueryImsState.setServiceStateReady(true);
doReturn(true).when(mTelephonyManager).isDataEnabled();
mController.mCallState = TelephonyManager.CALL_STATE_IDLE;
}
@Test
public void isVideoCallEnabled_allFlagsOn_returnTrue() {
assertThat(mController.isVideoCallEnabled(SUB_ID)).isTrue();
}
@Test
public void isVideoCallEnabled_disabledByPlatform_returnFalse() {
mQueryImsState.setIsProvisionedOnDevice(false);
mQueryImsState.setIsEnabledByPlatform(false);
assertThat(mController.isVideoCallEnabled(SUB_ID)).isFalse();
}
@Test
public void isVideoCallEnabled_dataDisabled_returnFalse() {
mCarrierConfig.putBoolean(
CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, false);
doReturn(false).when(mTelephonyManager).isDataEnabled();
assertThat(mController.isVideoCallEnabled(SUB_ID)).isFalse();
}
@Test
public void updateState_4gLteOff_disabled() {
mQueryImsState.setIsEnabledByUser(false);
mQueryVoLteState.setIsEnabledByUser(false);
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isFalse();
assertThat(mPreference.isChecked()).isFalse();
}
@Test
public void updateState_4gLteOnWithoutCall_checked() {
mQueryImsState.setIsEnabledByUser(true);
mQueryVoLteState.setIsEnabledByUser(true);
mQueryImsState.setIsTtyOnVolteEnabled(true);
mController.mCallState = TelephonyManager.CALL_STATE_IDLE;
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isTrue();
assertThat(mPreference.isChecked()).isTrue();
}
@Test
public void displayPreference_notAvailable_setPreferenceInvisible() {
mQueryImsState.setIsEnabledByPlatform(false);
mController.displayPreference(mPreferenceScreen);
assertThat(mPreferenceScreen.isVisible()).isFalse();
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.network.telephony
import android.content.Context
import android.telephony.TelephonyManager
import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.preference.PreferenceManager
import androidx.preference.SwitchPreferenceCompat
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.network.ims.VolteQueryImsState
import com.android.settings.network.ims.VtQueryImsState
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class VideoCallingPreferenceControllerTest {
private val mockVtQueryImsState = mock<VtQueryImsState> {}
private var mockQueryVoLteState = mock<VolteQueryImsState> {}
private val context: Context = ApplicationProvider.getApplicationContext()
private val mockCallStateRepository = mock<CallStateRepository> {}
private var controller =
spy(
VideoCallingPreferenceController(
context = context,
key = TEST_KEY,
callStateRepository = mockCallStateRepository,
)
) {
on { queryImsState(SUB_ID) } doReturn mockVtQueryImsState
on { queryVoLteState(SUB_ID) } doReturn mockQueryVoLteState
}
private val preference = SwitchPreferenceCompat(context).apply { key = TEST_KEY }
private val preferenceScreen = PreferenceManager(context).createPreferenceScreen(context)
@Before
fun setUp() {
controller.init(SUB_ID, CallingPreferenceCategoryController(context, "calling_category"))
preferenceScreen.addPreference(preference)
controller.displayPreference(preferenceScreen)
}
@Test
fun updateState_4gLteOff_disabledAndUnchecked() {
mockQueryVoLteState.stub { on { isEnabledByUser } doReturn false }
controller.updateState(preference)
assertThat(preference.isEnabled).isFalse()
assertThat(preference.isChecked).isFalse()
}
@Test
fun updateState_4gLteOnWithoutCall_enabledAndChecked() = runBlocking {
mockVtQueryImsState.stub {
on { isEnabledByUser } doReturn true
on { isAllowUserControl } doReturn true
}
mockQueryVoLteState.stub { on { isEnabledByUser } doReturn true }
mockCallStateRepository.stub {
on { callStateFlow(SUB_ID) } doReturn flowOf(TelephonyManager.CALL_STATE_IDLE)
}
controller.onViewCreated(TestLifecycleOwner())
delay(100)
controller.updateState(preference)
assertThat(preference.isEnabled).isTrue()
assertThat(preference.isChecked).isTrue()
}
@Test
fun updateState_4gLteOnWithCall_disabledAndChecked() = runBlocking {
mockVtQueryImsState.stub {
on { isEnabledByUser } doReturn true
on { isAllowUserControl } doReturn true
}
mockQueryVoLteState.stub { on { isEnabledByUser } doReturn true }
mockCallStateRepository.stub {
on { callStateFlow(SUB_ID) } doReturn flowOf(TelephonyManager.CALL_STATE_RINGING)
}
controller.onViewCreated(TestLifecycleOwner())
delay(100)
controller.updateState(preference)
assertThat(preference.isEnabled).isFalse()
assertThat(preference.isChecked).isTrue()
}
private companion object {
const val TEST_KEY = "test_key"
const val SUB_ID = 10
}
}

View File

@@ -0,0 +1,166 @@
/*
* 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.network.telephony
import android.content.Context
import android.telephony.AccessNetworkConstants
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.ims.feature.MmTelFeature
import android.telephony.ims.stub.ImsRegistrationImplBase
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.network.telephony.ims.ImsFeatureRepository
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class VideoCallingRepositoryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val mockMobileDataRepository = mock<MobileDataRepository>()
private val mockImsFeatureRepository = mock<ImsFeatureRepository>()
private val repository =
VideoCallingRepository(
context = context,
mobileDataRepository = mockMobileDataRepository,
imsFeatureRepositoryFactory = { mockImsFeatureRepository },
)
@Before
fun setUp() {
CarrierConfigRepository.resetForTest()
}
@Test
fun isVideoCallReadyFlow_invalidSubId() = runBlocking {
val isVideoCallReady =
repository
.isVideoCallReadyFlow(subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.firstWithTimeoutOrNull()
assertThat(isVideoCallReady).isFalse()
}
@Test
fun isVideoCallReadyFlow_ignoreDataEnabledChangedAndIsReady_returnTrue() = runBlocking {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS,
value = true,
)
mockImsFeatureRepository.stub {
on {
isReadyFlow(
capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
)
} doReturn flowOf(true)
}
val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVideoCallReady).isTrue()
}
@Test
fun isVideoCallReadyFlow_ignoreDataEnabledChangedAndNotReady_returnFalse() = runBlocking {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS,
value = true,
)
mockImsFeatureRepository.stub {
on {
isReadyFlow(
capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
)
} doReturn flowOf(false)
}
val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVideoCallReady).isFalse()
}
@Test
fun isVideoCallReadyFlow_mobileDataEnabledAndIsReady_returnTrue() = runBlocking {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS,
value = false,
)
mockMobileDataRepository.stub {
on { isMobileDataEnabledFlow(SUB_ID) } doReturn flowOf(true)
}
mockImsFeatureRepository.stub {
on {
isReadyFlow(
capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
)
} doReturn flowOf(true)
}
val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVideoCallReady).isTrue()
}
@Test
fun isVideoCallReadyFlow_ignoreDataEnabledChangedAndIsReady_returnFalse() = runBlocking {
CarrierConfigRepository.setBooleanForTest(
subId = SUB_ID,
key = CarrierConfigManager.KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS,
value = false,
)
mockMobileDataRepository.stub {
on { isMobileDataEnabledFlow(SUB_ID) } doReturn flowOf(false)
}
mockImsFeatureRepository.stub {
on {
isReadyFlow(
capability = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO,
tech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
transportType = AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
)
} doReturn flowOf(true)
}
val isVideoCallReady = repository.isVideoCallReadyFlow(SUB_ID).firstWithTimeoutOrNull()
assertThat(isVideoCallReady).isFalse()
}
private companion object {
const val SUB_ID = 10
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.network.telephony.euicc
import android.content.Context
import android.telephony.TelephonyManager
import android.telephony.euicc.EuiccManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class EuiccRepositoryTest {
private val mockEuiccManager = mock<EuiccManager> { on { isEnabled } doReturn true }
private val mockTelephonyManager =
mock<TelephonyManager> {
on { activeModemCount } doReturn 1
on { getNetworkCountryIso(any()) } doReturn COUNTRY_CODE
}
private val context: Context =
spy(ApplicationProvider.getApplicationContext()) {
on { getSystemService(EuiccManager::class.java) } doReturn mockEuiccManager
on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
}
private val resources =
spy(context.resources) { on { getBoolean(R.bool.config_show_sim_info) } doReturn true }
private var euiccProvisioned = false
private val repository =
EuiccRepository(
context,
isEuiccProvisioned = { euiccProvisioned },
isDevelopmentSettingsEnabled = { false },
)
@Before
fun setUp() {
context.stub { on { resources } doReturn resources }
}
@Test
fun showEuiccSettings_noSim_returnFalse() {
resources.stub { on { getBoolean(R.bool.config_show_sim_info) } doReturn false }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isFalse()
}
@Test
fun showEuiccSettings_euiccDisabled_returnFalse() {
mockEuiccManager.stub { on { isEnabled } doReturn false }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isFalse()
}
@Test
fun showEuiccSettings_euiccProvisioned_returnTrue() {
euiccProvisioned = true
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isTrue()
}
@Test
fun showEuiccSettings_countryNotSupported_returnFalse() {
mockEuiccManager.stub { on { isSupportedCountry(COUNTRY_CODE) } doReturn false }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isFalse()
}
@Test
fun showEuiccSettings_countrySupported_returnTrue() {
mockEuiccManager.stub { on { isSupportedCountry(COUNTRY_CODE) } doReturn true }
val showEuiccSettings = repository.showEuiccSettings()
assertThat(showEuiccSettings).isTrue()
}
private companion object {
const val COUNTRY_CODE = "us"
}
}

View File

@@ -1,104 +0,0 @@
/*
* Copyright (C) 2020 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.network.ims;
import android.content.Context;
import android.telephony.ims.ImsException;
/**
* Controller class for mock VT status
*/
public class MockVtQueryImsState extends VtQueryImsState {
private Boolean mIsTtyOnVolteEnabled;
private Boolean mIsEnabledOnPlatform;
private Boolean mIsProvisionedOnDevice;
private Boolean mIsEnabledByUser;
private Boolean mIsServiceStateReady;
/**
* Constructor
*
* @param context {@link Context}
* @param subId subscription's id
*/
public MockVtQueryImsState(Context context, int subId) {
super(context, subId);
}
public void setIsTtyOnVolteEnabled(boolean enabled) {
mIsTtyOnVolteEnabled = enabled;
}
@Override
boolean isTtyOnVolteEnabled(int subId) {
if (mIsTtyOnVolteEnabled != null) {
return mIsTtyOnVolteEnabled;
}
return super.isTtyOnVolteEnabled(subId);
}
public void setIsEnabledByPlatform(boolean isEnabled) {
mIsEnabledOnPlatform = isEnabled;
}
@Override
boolean isEnabledByPlatform(int subId) throws InterruptedException, ImsException,
IllegalArgumentException {
if (mIsEnabledOnPlatform != null) {
return mIsEnabledOnPlatform;
}
return super.isEnabledByPlatform(subId);
}
public void setIsProvisionedOnDevice(boolean isProvisioned) {
mIsProvisionedOnDevice = isProvisioned;
}
@Override
boolean isProvisionedOnDevice(int subId) {
if (mIsProvisionedOnDevice != null) {
return mIsProvisionedOnDevice;
}
return super.isProvisionedOnDevice(subId);
}
public void setServiceStateReady(boolean isReady) {
mIsServiceStateReady = isReady;
}
@Override
boolean isServiceStateReady(int subId) throws InterruptedException, ImsException,
IllegalArgumentException {
if (mIsServiceStateReady != null) {
return mIsServiceStateReady;
}
return super.isServiceStateReady(subId);
}
public void setIsEnabledByUser(boolean enabled) {
mIsEnabledByUser = enabled;
}
@Override
boolean isEnabledByUser(int subId) {
if (mIsEnabledByUser != null) {
return mIsEnabledByUser;
}
return super.isEnabledByUser(subId);
}
}