Snap for 13256841 from 081fab1330 to 25Q2-release

Change-Id: I36755cf3e7b0e6de77eb961c135989c6cca80050
This commit is contained in:
Android Build Coastguard Worker
2025-03-21 20:22:28 -07:00
36 changed files with 827 additions and 81 deletions

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2025 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="960"
android:viewportWidth="960"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM260,600L306,600L306,360L270,360L200,410L224,446L260,420L260,600ZM384,600L540,600L540,560L446,560L444,558Q465,538 478.5,524Q492,510 500,502Q518,484 527,466Q536,448 536,428Q536,399 514,379.5Q492,360 458,360Q432,360 411,375Q390,390 382,414L422,430Q427,417 436.5,409.5Q446,402 458,402Q473,402 482.5,410Q492,418 492,430Q492,441 488,450.5Q484,460 470,474Q459,485 438,506Q417,527 384,560L384,600ZM680,600Q716,600 738,580Q760,560 760,528Q760,510 750,496Q740,482 722,474L722,472Q736,464 744,451.5Q752,439 752,422Q752,395 731,377.5Q710,360 678,360Q653,360 631.5,374.5Q610,389 604,410L644,426Q648,414 657,407Q666,400 678,400Q691,400 699.5,407.5Q708,415 708,426Q708,440 698,448Q688,456 672,456L654,456L654,496L674,496Q694,496 705,504Q716,512 716,526Q716,539 705,548.5Q694,558 680,558Q663,558 654,550.5Q645,543 638,524L598,540Q605,569 626.5,584.5Q648,600 680,600ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,240Q800,240 800,240Q800,240 800,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720ZM160,720Q160,720 160,720Q160,720 160,720L160,240Q160,240 160,240Q160,240 160,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720Z" />
</vector>

View File

@@ -1358,6 +1358,10 @@
<string name="lock_screen_pattern_skip_fingerprint_title">Skip setup for pattern and fingerprint?</string>
<!-- Title of dialog shown when the user tries to skip setting up a pattern, warning them of potential consequences of not doing so [CHAR LIMIT=48]-->
<string name="lock_screen_pattern_skip_biometrics_title">Skip setup for pattern, face, and fingerprint?</string>
<!-- Accessibility action label for resuming animation -->
<string name="resume_animation">Resume animation</string>
<!-- Accessibility action label for pausing animation -->
<string name="pause_animation">Pause animation</string>
<!-- Button text to setup screen lock in onboard dialog [CHAR LIMIT=34] -->
<string name="security_settings_fingerprint_enroll_setup_screen_lock">Set up screen lock</string>
@@ -5934,6 +5938,10 @@
<string name="accessibility_hearing_device_pairing_page_title">Pair hearing device</string>
<!-- Subtitle for the pair hearing device page. [CHAR LIMIT=NONE] -->
<string name="accessibility_hearing_device_pairing_intro">You can pair ASHA and LE Audio hearing devices on this page. Make sure your hearing device is turned on and ready to pair.</string>
<!-- Subtitle for the pair hearing device page. This string is for device that only supports ASHA hearing aids. [CHAR LIMIT=NONE] -->
<string name="accessibility_hearing_device_pairing_asha_only_intro">You can pair ASHA hearing devices on this page. Make sure your hearing device is turned on and ready to pair.</string>
<!-- Subtitle for the pair hearing device page. This string is for device that only supports LE Audio hearing aids. [CHAR LIMIT=NONE] -->
<string name="accessibility_hearing_device_pairing_hap_only_intro">You can pair LE Audio hearing devices on this page. Make sure your hearing device is turned on and ready to pair.</string>
<!-- Title for the preference category containing the list of the available hearing during and after bluetooth scanning devices. [CHAR LIMIT=30] -->
<string name="accessibility_found_hearing_devices">Available hearing devices</string>
<!-- Title for the preference category containing the all bluetooth devices during and after bluetooth scanning devices. Used when people can not find their hearing device in hearing device pairing list. [CHAR LIMIT=45] -->
@@ -12764,6 +12772,10 @@ Data usage charges may apply.</string>
<string name="title_satellite_supported_app_list_entry">see all apps</string>
<!-- Title for a page of apps list page with Satellite service supported. [CHAR LIMIT=60] -->
<string name="title_satellite_supported_app_list_page">Supported apps on your phone</string>
<!-- Title for showing a dialog to notify user sim restriction. [CHAR LIMIT=60] -->
<string name="title_satellite_dialog_for_sim_restriction">Can\u2019t add a SIM</string>
<!-- Description for showing a dialog to notify user sim restriction. [CHAR LIMIT=NONE] -->
<string name="description_satellite_dialog_for_sim_restriction">End the satellite connection before you add a SIM</string>
<!-- Title for Apn settings in mobile network settings [CHAR LIMIT=60] -->
<string name="mobile_network_apn_title">Access Point Names</string>

View File

@@ -20,8 +20,10 @@
android:title="@string/bluetooth_pairing_pref_title">
<com.android.settingslib.widget.TopIntroPreference
android:key="hearing_device_pairing_intro"
settings:searchable="false"
android:title="@string/accessibility_hearing_device_pairing_intro" />
android:title="@string/accessibility_hearing_device_pairing_intro"
settings:controller="com.android.settings.accessibility.HearingDevicePairingIntroPreferenceController"/>
<com.android.settings.bluetooth.BluetoothProgressCategory
android:key="available_hearing_devices"

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.google.common.annotations.VisibleForTesting;
public class HearingDevicePairingIntroPreferenceController extends BasePreferenceController {
private final HearingAidHelper mHelper;
public HearingDevicePairingIntroPreferenceController(
@NonNull Context context,
@NonNull String preferenceKey) {
super(context, preferenceKey);
mHelper = new HearingAidHelper(context);
}
@VisibleForTesting
public HearingDevicePairingIntroPreferenceController(
@NonNull Context context,
@NonNull String preferenceKey,
@NonNull HearingAidHelper hearingAidHelper) {
super(context, preferenceKey);
mHelper = hearingAidHelper;
}
@Override
public int getAvailabilityStatus() {
return mHelper.isHearingAidSupported() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public void displayPreference(@NonNull PreferenceScreen screen) {
super.displayPreference(screen);
final Preference pairingIntroPreference = screen.findPreference(getPreferenceKey());
final boolean isAshaProfileSupported = mHelper.isAshaProfileSupported();
final boolean isHapClientProfileSupported = mHelper.isHapClientProfileSupported();
if (isAshaProfileSupported && isHapClientProfileSupported) {
pairingIntroPreference.setTitle(
mContext.getString(R.string.accessibility_hearing_device_pairing_intro));
} else if (isAshaProfileSupported) {
pairingIntroPreference.setTitle(
mContext.getString(
R.string.accessibility_hearing_device_pairing_asha_only_intro));
} else if (isHapClientProfileSupported) {
pairingIntroPreference.setTitle(
mContext.getString(
R.string.accessibility_hearing_device_pairing_hap_only_intro));
} else {
// Intentionally blank, getAvailabilityStatus() should handle visibility for
// none-supported case.
}
}
}

View File

@@ -73,7 +73,7 @@ public interface FingerprintFeatureProvider {
default FingerprintExtPreferencesProvider getExtPreferenceProvider(
@NonNull Context context
) {
return new FingerprintExtPreferencesProvider();
return new FingerprintExtPreferencesProvider(context);
}
/**

View File

@@ -468,12 +468,10 @@ public class FingerprintSettings extends SubSettings {
* Add new preferences from FingerprintExtPreferencesProvider
*/
public void setupExtFingerprintPreferences() {
final FingerprintExtPreferencesProvider preferencesProvider =
FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
.getExtPreferenceProvider(requireContext());
FingerprintExtPreferencesProvider preferencesProvider = getExtPreferenceProvider();
for (int index = 0; index < preferencesProvider.getSize(); ++index) {
final RestrictedPreference preference = preferencesProvider.newPreference(
index, this::inflateFromResource, requireContext());
index, this::inflateFromResource);
if (preference == null || findPreference(preference.getKey()) != null) {
continue;
}
@@ -485,6 +483,12 @@ public class FingerprintSettings extends SubSettings {
}
}
@NonNull
private FingerprintExtPreferencesProvider getExtPreferenceProvider() {
return FeatureFactory.getFeatureFactory().getFingerprintFeatureProvider()
.getExtPreferenceProvider(requireContext());
}
/**
*
*/
@@ -748,7 +752,8 @@ public class FingerprintSettings extends SubSettings {
// This needs to be after setting ids, otherwise
// |mRequireScreenOnToAuthPreferenceController.isChecked| is always checking the primary
// user instead of the user with |mUserId|.
if (isSfps() || (screenOffUnlockUdfps() && isScreenOffUnlcokSupported())) {
if (isSfps() || (screenOffUnlockUdfps() && isScreenOffUnlcokSupported())
|| getExtPreferenceProvider().getSize() > 0) {
scrollToPreference(fpPrefKey);
addFingerprintUnlockCategory();
}
@@ -1266,6 +1271,16 @@ public class FingerprintSettings extends SubSettings {
}
}
if (mFingerprintUnlockCategoryPreferenceController == null
&& getExtPreferenceProvider().getSize() > 0 && controllers != null) {
for (AbstractPreferenceController controller : controllers) {
if (KEY_FINGERPRINT_UNLOCK_CATEGORY.equals(controller.getPreferenceKey())) {
mFingerprintUnlockCategoryPreferenceController =
(FingerprintUnlockCategoryController) controller;
}
}
}
return controllers;
}
@@ -1654,7 +1669,10 @@ public class FingerprintSettings extends SubSettings {
private static final String KEY_USER_ID = "user_id";
private static final String KEY_SENSOR_PROPERTIES = "sensor_properties";
private static final String EXTRA_FAILURE_COUNT = "failure_count";
private static final int MAX_FAILURE_COUNT = 3;
private int mUserId;
private int mFailureCount;
private @Nullable CancellationSignal mCancellationSignal;
private @Nullable FingerprintSensorPropertiesInternal mSensorPropertiesInternal;
@@ -1663,6 +1681,9 @@ public class FingerprintSettings extends SubSettings {
@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFailureCount = savedInstanceState.getInt(EXTRA_FAILURE_COUNT, 0);
}
return inflater.inflate(
R.layout.fingerprint_check_enrolled_dialog, container, false);
}
@@ -1682,12 +1703,22 @@ public class FingerprintSettings extends SubSettings {
final UdfpsCheckEnrolledView v =
dialog.findViewById(R.id.udfps_check_enrolled_view);
v.setSensorProperties(mSensorPropertiesInternal);
v.setOnTouchListener((view, event) -> {
Log.d(TAG, "CheckEnrollDialog dismissed: touch outside");
dialog.dismiss();
return false;
});
});
}
return dialog;
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(EXTRA_FAILURE_COUNT, mFailureCount);
}
@Override
public void onStart() {
super.onStart();
@@ -1752,6 +1783,11 @@ public class FingerprintSettings extends SubSettings {
message.postDelayed(() -> {
message.setText(R.string.fingerprint_check_enroll_touch_sensor);
}, 2000);
mFailureCount++;
if (mFailureCount >= MAX_FAILURE_COUNT) {
Log.d(TAG, "CheckEnrollDialog dismissed: failed 3 times");
dialog.dismiss();
}
}
},
null /* handler */,

View File

@@ -24,6 +24,7 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.RotationUtils;
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.Surface;
import android.widget.ImageView;
import android.widget.RelativeLayout;
@@ -61,6 +62,13 @@ public class UdfpsCheckEnrolledView extends RelativeLayout {
super.onFinishInflate();
mFingerprintView = findViewById(R.id.udfps_fingerprint_sensor_view);
mFingerprintView.setImageDrawable(mFingerprintDrawable);
mFingerprintView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "Fingerprint view touched!");
return true;
}
return false;
});
}
/**

View File

@@ -27,14 +27,13 @@ import com.android.settingslib.RestrictedPreference
*
* @see com.android.settings.biometrics.fingerprint.FingerprintSettings
*/
open class FingerprintExtPreferencesProvider {
open class FingerprintExtPreferencesProvider(protected val context: Context) {
open val size: Int = 0
open fun newPreference(
index: Int,
inflater: PreferenceInflater,
context: Context
): RestrictedPreference? = null
interface PreferenceInflater {

View File

@@ -60,6 +60,8 @@ import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -417,13 +419,17 @@ public class BluetoothDeviceDetailsFragment extends BluetoothDetailsConfigurable
@Nullable
private List<String> generateDisplayedPreferenceKeys(boolean bondingLoss) {
if (bondingLoss) {
return List.of(
use(BluetoothDetailsBannerController.class).getPreferenceKey(),
use(AdvancedBluetoothDetailsHeaderController.class).getPreferenceKey(),
use(BluetoothDetailsHeaderController.class).getPreferenceKey(),
use(LeAudioBluetoothDetailsHeaderController.class).getPreferenceKey(),
use(BluetoothDetailsButtonsController.class).getPreferenceKey(),
use(BluetoothDetailsMacAddressController.class).getPreferenceKey());
ImmutableList.Builder<String> visibleKeys = new ImmutableList.Builder<>();
visibleKeys
.add(use(BluetoothDetailsBannerController.class).getPreferenceKey())
.add(use(AdvancedBluetoothDetailsHeaderController.class).getPreferenceKey())
.add(use(BluetoothDetailsHeaderController.class).getPreferenceKey())
.add(use(LeAudioBluetoothDetailsHeaderController.class).getPreferenceKey())
.add(use(BluetoothDetailsButtonsController.class).getPreferenceKey());
if (!BluetoothUtils.isHeadset(mCachedDevice.getDevice())) {
visibleKeys.add(use(BluetoothDetailsMacAddressController.class).getPreferenceKey());
}
return visibleKeys.build();
}
return null;
}

View File

@@ -296,6 +296,10 @@ public final class BluetoothDevicePreference extends GearPreference {
void onPreferenceAttributesChanged() {
try {
ThreadUtils.postOnBackgroundThread(() -> {
if (mCachedDevice.getDevice() != null) {
Log.d(TAG, "onPreferenceAttributesChanged, start updating for device "
+ mCachedDevice.getDevice().getAnonymizedAddress());
}
@Nullable String name = mCachedDevice.getName();
// Null check is done at the framework
@Nullable String connectionSummary = getConnectionSummary();
@@ -325,6 +329,7 @@ public final class BluetoothDevicePreference extends GearPreference {
notifyHierarchyChanged();
}
});
Log.d(TAG, "onPreferenceAttributesChanged, complete updating for device " + name);
});
} catch (RejectedExecutionException e) {
Log.w(TAG, "Handler thread unavailable, skipping getConnectionSummary!");

View File

@@ -335,6 +335,7 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
var unused =
ThreadUtils.postOnBackgroundThread(
() -> {
Log.d(TAG, "updateTitle, check current status");
int titleResId;
if (isAudioModeOngoingCall(mContext)) {
// in phone call
@@ -347,6 +348,7 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
// without phone call, not audio sharing
titleResId = R.string.connected_device_media_device_title;
}
Log.d(TAG, "updateTitle, title = " + titleResId);
mContext.getMainExecutor()
.execute(
() -> {

View File

@@ -17,6 +17,7 @@ package com.android.settings.connecteddevice;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
@@ -34,18 +35,25 @@ import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.slices.SlicePreferenceController;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.search.SearchIndexable;
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
public class ConnectedDeviceDashboardFragment extends DashboardFragment {
private static final String TAG = "ConnectedDeviceFrag";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE";
private static final String SETTINGS_SEARCH_ACTION =
"com.android.settings.SEARCH_RESULT_TRAMPOLINE";
@VisibleForTesting static final String KEY_CONNECTED_DEVICES = "connected_device_list";
@VisibleForTesting static final String KEY_AVAILABLE_DEVICES = "available_device_list";
private static final String ENTRYPOINT_SYSUI = "bt_settings_entrypoint_sysui";
private static final String ENTRYPOINT_SETTINGS = "bt_settings_entrypoint_settings_click";
private static final String ENTRYPOINT_SETTINGS_SEARCH =
"bt_settings_entrypoint_settings_search";
private static final String ENTRYPOINT_OTHER = "bt_settings_entrypoint_other";
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
@@ -71,15 +79,16 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
super.onAttach(context);
String callingAppPackageName =
((SettingsActivity) getActivity()).getInitialCallingPackage();
String action = getIntent() != null ? getIntent().getAction() : "";
if (DEBUG) {
Log.d(
TAG,
"onAttach() calling package name is : "
+ callingAppPackageName
+ ", action : "
+ action);
}
Intent intent = getIntent();
String action = intent != null ? intent.getAction() : "";
Log.d(
TAG,
"onAttach() calling package name is : "
+ callingAppPackageName
+ ", action : "
+ action);
if (BluetoothUtils.isAudioSharingUIAvailable(context)) {
use(AudioSharingDevicePreferenceController.class).init(this);
}
@@ -100,16 +109,51 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
provider.sendActivityIfAvailable(category);
}
}
logPageEntrypoint(context, callingAppPackageName, intent);
}
@VisibleForTesting
boolean isAlwaysDiscoverable(String callingAppPackageName, String action) {
return TextUtils.equals(SLICE_ACTION, action)
return TextUtils.equals(SETTINGS_SEARCH_ACTION, action)
? false
: TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, callingAppPackageName)
|| TextUtils.equals(Utils.SYSTEMUI_PACKAGE_NAME, callingAppPackageName);
}
private void logPageEntrypoint(Context context, String callingAppPackageName, Intent intent) {
String action = intent != null ? intent.getAction() : "";
if (TextUtils.equals(Utils.SYSTEMUI_PACKAGE_NAME, callingAppPackageName)) {
mMetricsFeatureProvider.action(
context, SettingsEnums.SETTINGS_CONNECTED_DEVICES_ENTRYPOINT, ENTRYPOINT_SYSUI);
} else if (TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, callingAppPackageName)
&& TextUtils.equals(Intent.ACTION_MAIN, action)) {
String sourceCategory =
intent != null
? Integer.toString(
getIntent()
.getIntExtra(
MetricsFeatureProvider
.EXTRA_SOURCE_METRICS_CATEGORY,
SettingsEnums.PAGE_UNKNOWN))
: "";
mMetricsFeatureProvider.action(
context,
SettingsEnums.SETTINGS_CONNECTED_DEVICES_ENTRYPOINT,
ENTRYPOINT_SETTINGS + "_" + sourceCategory);
} else if (TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, callingAppPackageName)
&& TextUtils.equals(SETTINGS_SEARCH_ACTION, action)) {
mMetricsFeatureProvider.action(
context,
SettingsEnums.SETTINGS_CONNECTED_DEVICES_ENTRYPOINT,
ENTRYPOINT_SETTINGS_SEARCH);
} else {
mMetricsFeatureProvider.action(
context, SettingsEnums.SETTINGS_CONNECTED_DEVICES_ENTRYPOINT, ENTRYPOINT_OTHER);
}
}
/** For Search. */
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.connected_devices);

View File

@@ -18,6 +18,14 @@ package com.android.settings.localepicker;
import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static com.android.settings.regionalpreferences.RegionDialogFragment.ARG_CALLING_PAGE;
import static com.android.settings.regionalpreferences.RegionDialogFragment.CALLING_PAGE_LANGUAGE_CHOOSE_A_REGION;
import static com.android.settings.regionalpreferences.RegionDialogFragment.DIALOG_CHANGE_SYSTEM_LOCALE_REGION;
import static com.android.settings.regionalpreferences.RegionDialogFragment.DIALOG_CHANGE_PREFERRED_LOCALE_REGION;
import static com.android.settings.regionalpreferences.RegionDialogFragment.ARG_DIALOG_TYPE;
import static com.android.settings.regionalpreferences.RegionDialogFragment.ARG_TARGET_LOCALE;
import static com.android.settings.regionalpreferences.RegionDialogFragment.ARG_REPLACED_TARGET_LOCALE;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
@@ -50,11 +58,6 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity
private static final String TAG = LocalePickerWithRegionActivity.class.getSimpleName();
private static final String PARENT_FRAGMENT_NAME = "localeListEditor";
private static final String CHILD_FRAGMENT_NAME = "LocalePickerWithRegion";
private static final int DIALOG_CHANGE_SYSTEM_LOCALE_REGION = 1;
private static final int DIALOG_CHANGE_PREFERRED_LOCALE_REGION = 2;
private static final String ARG_DIALOG_TYPE = "arg_dialog_type";
private static final String ARG_TARGET_LOCALE = "arg_target_locale";
private static final String ARG_REPLACED_TARGET_LOCALE = "arg_replaced_target_locale";
private static final String TAG_DIALOG_CHANGE_REGION = "dialog_change_region";
private static final int DISPOSE = -1;
private static final int SHOW_DIALOG_FOR_SYSTEM_LANGUAGE = 0;
@@ -139,6 +142,7 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity
LocaleStore.LocaleInfo locale, FragmentManager fragmentManager) {
Bundle args = new Bundle();
args.putInt(ARG_DIALOG_TYPE, DIALOG_CHANGE_SYSTEM_LOCALE_REGION);
args.putInt(ARG_CALLING_PAGE, CALLING_PAGE_LANGUAGE_CHOOSE_A_REGION);
args.putSerializable(ARG_TARGET_LOCALE, locale);
RegionDialogFragment regionDialogFragment = RegionDialogFragment.newInstance();
regionDialogFragment.setArguments(args);
@@ -149,6 +153,7 @@ public class LocalePickerWithRegionActivity extends SettingsBaseActivity
LocaleStore.LocaleInfo locale, Locale replacedLocale, FragmentManager fragmentManager) {
Bundle args = new Bundle();
args.putInt(ARG_DIALOG_TYPE, DIALOG_CHANGE_PREFERRED_LOCALE_REGION);
args.putInt(ARG_CALLING_PAGE, CALLING_PAGE_LANGUAGE_CHOOSE_A_REGION);
args.putSerializable(ARG_TARGET_LOCALE, locale);
args.putSerializable(ARG_REPLACED_TARGET_LOCALE, replacedLocale);
RegionDialogFragment regionDialogFragment = RegionDialogFragment.newInstance();

View File

@@ -16,11 +16,11 @@
package com.android.settings.network
import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_CONNECTIVITY
import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_MOBILE_NETWORK
import android.content.Context
import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_MOBILE_NETWORK_ENABLED
import com.android.settings.R
import com.android.settings.contract.KEY_ADAPTIVE_CONNECTIVITY
import com.android.settings.contract.KEY_ADAPTIVE_MOBILE_NETWORK
import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyValueStoreDelegate
@@ -38,12 +38,12 @@ class AdaptiveMobileNetworkTogglePreference() :
PreferenceActionMetricsProvider {
override val preferenceActionMetrics: Int
get() = ACTION_ADAPTIVE_CONNECTIVITY
get() = ACTION_ADAPTIVE_MOBILE_NETWORK
override val key: String
get() = KEY
override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_CONNECTIVITY)
override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_MOBILE_NETWORK)
override fun storage(context: Context): KeyValueStore =
AdaptiveMobileNetworkToggleStorage(context)

View File

@@ -25,6 +25,7 @@ import androidx.preference.Preference.OnPreferenceClickListener
import com.android.settings.R
import com.android.settings.flags.Flags
import com.android.settings.network.AirplaneModePreference.Companion.isAirplaneModeOn
import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn
import com.android.settings.network.SubscriptionUtil.getUniqueSubscriptionDisplayName
import com.android.settings.network.telephony.SimRepository
import com.android.settings.network.telephony.SubscriptionRepository
@@ -32,6 +33,7 @@ import com.android.settings.network.telephony.euicc.EuiccRepository
import com.android.settings.restriction.PreferenceRestrictionMixin
import com.android.settings.spa.network.getAddSimIntent
import com.android.settings.spa.network.startAddSimFlow
import com.android.settings.spa.network.startSatelliteWarningDialogFlow
import com.android.settingslib.RestrictedPreference
import com.android.settingslib.datastore.HandlerExecutor
import com.android.settingslib.datastore.KeyedObserver
@@ -120,7 +122,11 @@ class MobileNetworkListScreen :
val summary = preference.summary ?: return true // no-op
val context = preference.context
if (summary == context.getString(R.string.mobile_network_summary_add_a_network)) {
startAddSimFlow(context) // start intent
if (isSatelliteOn(context, 3000)) {
startSatelliteWarningDialogFlow(context) // start intent
} else {
startAddSimFlow(context) // start intent
}
return true
}
return false // start fragment

View File

@@ -27,6 +27,7 @@ import com.android.settings.dashboard.DashboardFragment
import com.android.settings.network.telephony.SimRepository
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settings.spa.network.startAddSimFlow
import com.android.settings.spa.network.startSatelliteWarningDialogFlow
import com.android.settingslib.RestrictedPreference
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
@@ -51,11 +52,13 @@ constructor(
MobileNetworkSummaryRepository(context),
private val airplaneModeOnFlow: Flow<Boolean> =
context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON),
private val satelliteIsStartedFlow: Flow<Boolean> = SatelliteRepository(context).getIsSessionStartedFlow()
) : BasePreferenceController(context, preferenceKey) {
private val metricsFeatureProvider = featureFactory.metricsFeatureProvider
private var preference: RestrictedPreference? = null
private var isAirplaneModeOn = false
private var isSatelliteOn = false
override fun getAvailabilityStatus() =
if (SimRepository(mContext).showMobileNetworkPageEntrance()) AVAILABLE
@@ -74,6 +77,9 @@ constructor(
isAirplaneModeOn = it
updateEnabled()
}
satelliteIsStartedFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
isSatelliteOn = it
}
}
private fun update(state: MobileNetworkSummaryRepository.SubscriptionsState) {
@@ -87,7 +93,10 @@ constructor(
preference.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
logPreferenceClick()
startAddSimFlow(context)
if (isSatelliteOn)
startSatelliteWarningDialogFlow(context)
else
startAddSimFlow(context)
true
}
}

View File

@@ -32,11 +32,20 @@ import com.android.settingslib.wifi.WifiUtils
/** A dialog to show the warning message when device is under satellite mode. */
class SatelliteWarningDialogActivity : SpaDialogWindowTypeActivity() {
private var warningType = TYPE_IS_UNKNOWN
private var customizedContent: HashMap<Int, String> = HashMap<Int, String>()
private var isCustomizedContent = false
override fun onCreate(savedInstanceState: Bundle?) {
warningType = intent.getIntExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, TYPE_IS_UNKNOWN)
if (warningType == TYPE_IS_UNKNOWN) {
finish()
isCustomizedContent = intent.hasExtra(EXTRA_TYPE_OF_SATELLITE_CUSTOMIZED_CONTENT)
if (isCustomizedContent) {
customizedContent =
intent.getSerializableExtra(EXTRA_TYPE_OF_SATELLITE_CUSTOMIZED_CONTENT)
as HashMap<Int, String>
} else {
warningType =
intent.getIntExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, TYPE_IS_UNKNOWN)
if (warningType == TYPE_IS_UNKNOWN) {
finish()
}
}
super.onCreate(savedInstanceState)
}
@@ -48,23 +57,52 @@ class SatelliteWarningDialogActivity : SpaDialogWindowTypeActivity() {
)
}
fun getBodyString(): String {
if (isCustomizedContent) {
// For customized content
return customizedContent.get(CUSTOM_CONTENT_DESCRIPTION).toString()
} else {
// For wifi, bluetooth, airplane mode
return String.format(
getString(R.string.satellite_warning_dialog_content),
getTypeString(warningType)
)
}
}
fun getButtonString(): String {
if (isCustomizedContent)
// For customized content
return customizedContent.get(CUSTOM_CONTENT_BUTTON_NAME).toString()
else
// For wifi, bluetooth, airplane mode
return getString(com.android.settingslib.R.string.okay)
}
fun getTitleString(): String {
if (isCustomizedContent)
// For customized content
return customizedContent.get(CUSTOM_CONTENT_TITLE).toString()
else
// For wifi, bluetooth, airplane mode
return String.format(
getString(R.string.satellite_warning_dialog_title),
getTypeString(warningType)
)
}
@Composable
override fun Content() {
SettingsAlertDialogContent(
dismissButton = null,
confirmButton = AlertDialogButton(
getString(com.android.settingslib.R.string.okay)
getButtonString()
) { finish() },
title = String.format(
getString(R.string.satellite_warning_dialog_title),
getTypeString(warningType)
),
title = getTitleString(),
text = {
Text(
String.format(
getString(R.string.satellite_warning_dialog_content),
getTypeString(warningType)
),
getBodyString(),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
@@ -83,9 +121,14 @@ class SatelliteWarningDialogActivity : SpaDialogWindowTypeActivity() {
companion object {
const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
"extra_type_of_satellite_warning_dialog"
const val EXTRA_TYPE_OF_SATELLITE_CUSTOMIZED_CONTENT: String =
"extra_type_of_satellite_customized_content"
const val TYPE_IS_UNKNOWN = -1
const val TYPE_IS_WIFI = 0
const val TYPE_IS_BLUETOOTH = 1
const val TYPE_IS_AIRPLANE_MODE = 2
const val CUSTOM_CONTENT_TITLE = 0
const val CUSTOM_CONTENT_DESCRIPTION = 1
const val CUSTOM_CONTENT_BUTTON_NAME = 2
}
}

View File

@@ -17,13 +17,13 @@
package com.android.settings.network
import android.Manifest
import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_CONNECTIVITY
import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_WIFI_SCORER
import android.content.Context
import android.net.wifi.WifiManager
import android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_WIFI_ENABLED
import androidx.annotation.RequiresPermission
import com.android.settings.R
import com.android.settings.contract.KEY_ADAPTIVE_CONNECTIVITY
import com.android.settings.contract.KEY_ADAPTIVE_WIFI_SCORER
import com.android.settings.metrics.PreferenceActionMetricsProvider
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyValueStoreDelegate
@@ -42,12 +42,12 @@ class WifiScorerTogglePreference() :
PreferenceActionMetricsProvider {
override val preferenceActionMetrics: Int
get() = ACTION_ADAPTIVE_CONNECTIVITY
get() = ACTION_ADAPTIVE_WIFI_SCORER
override val key: String
get() = KEY
override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_CONNECTIVITY)
override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_WIFI_SCORER)
override fun storage(context: Context): KeyValueStore =
WifiScorerToggleStorage(context)

View File

@@ -101,6 +101,10 @@ public class SatelliteSettingAccountInfoController extends TelephonyBasePreferen
@Override
public int getAvailabilityStatus(int subId) {
if (mConfigBundle.getInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT)
== CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
return AVAILABLE;
}
return mConfigBundle.getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL)
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2023 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.privatespace;
import static com.android.settingslib.widget.preference.illustration.R.string.settingslib_action_label_pause;
import static com.android.settingslib.widget.preference.illustration.R.string.settingslib_action_label_resume;
import static com.android.settingslib.widget.preference.illustration.R.string.settingslib_illustration_content_description;
import android.content.Context;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.airbnb.lottie.LottieAnimationView;
public class PrivateSpaceAccessibilityUtils {
static void updateAccessibilityActionForAnimation(Context context,
LottieAnimationView animationView, boolean isAnimationPlaying) {
animationView.setContentDescription(
context.getString(settingslib_illustration_content_description));
ViewCompat.setAccessibilityDelegate(animationView, new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(
View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
// Clearing the class name to ensure the animation is not called out as "button"
// inside the TalkBack flows
info.setClassName("");
info.removeAction(
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK);
final AccessibilityNodeInfoCompat.AccessibilityActionCompat clickAction =
new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
AccessibilityNodeInfo.ACTION_CLICK,
getActionLabelForAnimation(context, isAnimationPlaying));
info.addAction(clickAction);
}
});
}
private static String getActionLabelForAnimation(Context context, boolean isAnimationPlaying) {
if (isAnimationPlaying) {
return context.getString(settingslib_action_label_pause);
} else {
return context.getString(settingslib_action_label_resume);
}
}
}

View File

@@ -76,6 +76,8 @@ public class PrivateSpaceEducation extends InstrumentedFragment {
LottieAnimationView lottieAnimationView = rootView.findViewById(R.id.lottie_animation);
LottieColorUtils.applyDynamicColors(getContext(), lottieAnimationView);
lottieAnimationView.setOnClickListener(v -> handleAnimationClick(lottieAnimationView));
PrivateSpaceAccessibilityUtils.updateAccessibilityActionForAnimation(getContext(),
lottieAnimationView, mIsAnimationPlaying);
TextView infoTextView = rootView.findViewById(R.id.learn_more);
Pattern pattern = Pattern.compile(infoTextView.getText().toString());
@@ -121,5 +123,7 @@ public class PrivateSpaceEducation extends InstrumentedFragment {
lottieAnimationView.playAnimation();
}
mIsAnimationPlaying = !mIsAnimationPlaying;
PrivateSpaceAccessibilityUtils.updateAccessibilityActionForAnimation(getContext(),
lottieAnimationView, mIsAnimationPlaying);
}
}

View File

@@ -94,6 +94,8 @@ public class PrivateSpaceSetLockFragment extends InstrumentedFragment {
LottieAnimationView lottieAnimationView = rootView.findViewById(R.id.lottie_animation);
LottieColorUtils.applyDynamicColors(getContext(), lottieAnimationView);
lottieAnimationView.setOnClickListener(v -> handleAnimationClick(lottieAnimationView));
PrivateSpaceAccessibilityUtils.updateAccessibilityActionForAnimation(getContext(),
lottieAnimationView, mIsAnimationPlaying);
return rootView;
}
@@ -141,5 +143,7 @@ public class PrivateSpaceSetLockFragment extends InstrumentedFragment {
lottieAnimationView.playAnimation();
}
mIsAnimationPlaying = !mIsAnimationPlaying;
PrivateSpaceAccessibilityUtils.updateAccessibilityActionForAnimation(getContext(),
lottieAnimationView, mIsAnimationPlaying);
}
}

View File

@@ -83,6 +83,8 @@ public class SetupSuccessFragment extends InstrumentedFragment {
LottieAnimationView lottieAnimationView = rootView.findViewById(R.id.lottie_animation);
LottieColorUtils.applyDynamicColors(getContext(), lottieAnimationView);
lottieAnimationView.setOnClickListener(v -> handleAnimationClick(lottieAnimationView));
PrivateSpaceAccessibilityUtils.updateAccessibilityActionForAnimation(getContext(),
lottieAnimationView, mIsAnimationPlaying);
return rootView;
}
@@ -152,5 +154,7 @@ public class SetupSuccessFragment extends InstrumentedFragment {
lottieAnimationView.playAnimation();
}
mIsAnimationPlaying = !mIsAnimationPlaying;
PrivateSpaceAccessibilityUtils.updateAccessibilityActionForAnimation(getContext(),
lottieAnimationView, mIsAnimationPlaying);
}
}

View File

@@ -16,8 +16,13 @@
package com.android.settings.regionalpreferences;
import static android.app.settings.SettingsEnums.ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_POSITIVE_BTN_CLICKED;
import static android.app.settings.SettingsEnums.ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_NEGATIVE_BTN_CLICKED;
import static android.app.settings.SettingsEnums.ACTION_CHANGE_REGION_DIALOG_NEGATIVE_BTN_CLICKED;
import static android.app.settings.SettingsEnums.ACTION_CHANGE_REGION_DIALOG_POSITIVE_BTN_CLICKED;
import static android.app.settings.SettingsEnums.CHANGE_REGION_DIALOG;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
@@ -46,12 +51,17 @@ import java.util.Set;
* Create a dialog for system region events.
*/
public class RegionDialogFragment extends InstrumentedDialogFragment {
public static final String ARG_CALLING_PAGE = "arg_calling_page";
public static final int CALLING_PAGE_LANGUAGE_CHOOSE_A_REGION = 0;
public static final int CALLING_PAGE_REGIONAL_PREFERENCES_REGION_PICKER = 1;
public static final int DIALOG_CHANGE_SYSTEM_LOCALE_REGION = 1;
public static final int DIALOG_CHANGE_PREFERRED_LOCALE_REGION = 2;
public static final String ARG_DIALOG_TYPE = "arg_dialog_type";
public static final String ARG_TARGET_LOCALE = "arg_target_locale";
public static final String ARG_REPLACED_TARGET_LOCALE = "arg_replaced_target_locale";
private static final String TAG = "RegionDialogFragment";
static final int DIALOG_CHANGE_SYSTEM_LOCALE_REGION = 1;
static final int DIALOG_CHANGE_PREFERRED_LOCALE_REGION = 2;
static final String ARG_DIALOG_TYPE = "arg_dialog_type";
static final String ARG_TARGET_LOCALE = "arg_target_locale";
static final String ARG_REPLACED_TARGET_LOCALE = "arg_replaced_target_locale";
/**
* Use this factory method to create a new instance of
@@ -66,7 +76,7 @@ public class RegionDialogFragment extends InstrumentedDialogFragment {
@Override
public int getMetricsCategory() {
return SettingsEnums.CHANGE_REGION_DIALOG;
return CHANGE_REGION_DIALOG;
}
@NonNull
@@ -114,6 +124,7 @@ public class RegionDialogFragment extends InstrumentedDialogFragment {
class RegionDialogController implements DialogInterface.OnClickListener {
private final Context mContext;
private final int mDialogType;
private final int mCallingPage;
private final LocaleStore.LocaleInfo mLocaleInfo;
private final Locale mReplacedLocale;
private final MetricsFeatureProvider mMetricsFeatureProvider;
@@ -123,6 +134,7 @@ public class RegionDialogFragment extends InstrumentedDialogFragment {
mContext = context;
Bundle arguments = dialogFragment.getArguments();
mDialogType = arguments.getInt(ARG_DIALOG_TYPE);
mCallingPage = arguments.getInt(ARG_CALLING_PAGE);
mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable(ARG_TARGET_LOCALE);
mReplacedLocale = (Locale) arguments.getSerializable(ARG_REPLACED_TARGET_LOCALE);
mMetricsFeatureProvider =
@@ -137,8 +149,10 @@ public class RegionDialogFragment extends InstrumentedDialogFragment {
updateRegion(mLocaleInfo.getLocale().toLanguageTag());
mMetricsFeatureProvider.action(
mContext,
SettingsEnums.ACTION_CHANGE_REGION_DIALOG_POSITIVE_BTN_CLICKED);
// TODO: add new metrics for DIALOG_CHANGE_PREFERRED_LOCALE_REGION
mDialogType == DIALOG_CHANGE_SYSTEM_LOCALE_REGION
? ACTION_CHANGE_REGION_DIALOG_POSITIVE_BTN_CLICKED
: ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_POSITIVE_BTN_CLICKED,
mCallingPage);
dismiss();
if (getActivity() != null) {
getActivity().finish();
@@ -146,8 +160,10 @@ public class RegionDialogFragment extends InstrumentedDialogFragment {
} else {
mMetricsFeatureProvider.action(
mContext,
SettingsEnums.ACTION_CHANGE_REGION_DIALOG_NEGATIVE_BTN_CLICKED);
// TODO: add new metrics for DIALOG_CHANGE_PREFERRED_LOCALE_REGION
mDialogType == DIALOG_CHANGE_SYSTEM_LOCALE_REGION
? ACTION_CHANGE_REGION_DIALOG_NEGATIVE_BTN_CLICKED
: ACTION_CHANGE_PREFERRED_LANGUAGE_REGION_NEGATIVE_BTN_CLICKED,
mCallingPage);
dismiss();
}
}

View File

@@ -167,8 +167,12 @@ public abstract class RegionPickerBaseListPreferenceController extends BasePrefe
mFragmentManager = mParent.getChildFragmentManager();
Bundle args = new Bundle();
args.putInt(RegionDialogFragment.ARG_DIALOG_TYPE,
args.putInt(
RegionDialogFragment.ARG_DIALOG_TYPE,
RegionDialogFragment.DIALOG_CHANGE_SYSTEM_LOCALE_REGION);
args.putInt(
RegionDialogFragment.ARG_CALLING_PAGE,
RegionDialogFragment.CALLING_PAGE_REGIONAL_PREFERENCES_REGION_PICKER);
args.putSerializable(RegionDialogFragment.ARG_TARGET_LOCALE, localeInfo);
RegionDialogFragment regionDialogFragment = RegionDialogFragment.newInstance();
regionDialogFragment.setArguments(args);

View File

@@ -35,6 +35,10 @@ import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.network.SatelliteWarningDialogActivity
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.CUSTOM_CONTENT_BUTTON_NAME
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.CUSTOM_CONTENT_DESCRIPTION
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.CUSTOM_CONTENT_TITLE
import com.android.settings.network.SubscriptionUtil
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.SubscriptionActivationRepository
@@ -143,3 +147,14 @@ fun getAddSimIntent() = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTI
setPackage(Utils.PHONE_PACKAGE_NAME)
putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
}
fun startSatelliteWarningDialogFlow(context: Context) = context.startActivity(getSatelliteWarningDialogIntent(context))
fun getSatelliteWarningDialogIntent(context: Context) = Intent(context,
SatelliteWarningDialogActivity::class.java).apply {
val content = HashMap<Int, String>()
content.put(CUSTOM_CONTENT_TITLE, context.getString(R.string.title_satellite_dialog_for_sim_restriction))
content.put(CUSTOM_CONTENT_DESCRIPTION, context.getString(R.string.description_satellite_dialog_for_sim_restriction))
content.put(CUSTOM_CONTENT_BUTTON_NAME, context.getString(R.string.okay))
putExtra(SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_CUSTOMIZED_CONTENT, content)
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2025 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.supervision
import android.app.KeyguardManager
import android.content.Context
import android.os.UserHandle
import android.os.UserManager
import android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING
import androidx.annotation.VisibleForTesting
/** Convenience methods for interacting with the supervising user profile. */
open class SupervisionHelper private constructor(context: Context) {
private val mUserManager = context.getSystemService(UserManager::class.java)
private val mKeyguardManager = context.getSystemService(KeyguardManager::class.java)
fun getSupervisingUserHandle(): UserHandle? {
for (user in (mUserManager?.users ?: emptyList())) {
if (user.userType.equals(USER_TYPE_PROFILE_SUPERVISING)) {
return user.userHandle
}
}
return null
}
fun isSupervisingCredentialSet(): Boolean {
val supervisingUserId = getSupervisingUserHandle()?.identifier ?: return false
return mKeyguardManager?.isDeviceSecure(supervisingUserId) ?: false
}
companion object {
@Volatile @VisibleForTesting var sInstance: SupervisionHelper? = null
fun getInstance(context: Context): SupervisionHelper {
return sInstance
?: synchronized(this) {
sInstance ?: SupervisionHelper(context).also { sInstance = it }
}
}
}
}

View File

@@ -83,6 +83,7 @@ class SupervisionMainSwitchPreference(context: Context) :
val newValue = !supervisionMainSwitchStorage.getBoolean(KEY)!!
mainSwitchPreference.setChecked(newValue)
updateDependentPreferencesEnabledState(mainSwitchPreference, newValue)
context.notifyPreferenceChange(SupervisionPinManagementScreen.KEY)
}
return true
@@ -110,10 +111,7 @@ class SupervisionMainSwitchPreference(context: Context) :
isChecked: Boolean,
) {
preference?.parent?.forEachRecursively {
if (
it.parent?.key == SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1 ||
it.key == SupervisionPinManagementScreen.KEY
) {
if (it.parent?.key == SupervisionDashboardScreen.SUPERVISION_DYNAMIC_GROUP_1) {
it.isEnabled = isChecked
}
}

View File

@@ -17,16 +17,20 @@ package com.android.settings.supervision
import android.content.Context
import com.android.settings.R
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceScreenCreator
/** Pin Management landing page (Settings > Supervision > Manage Pin). */
@ProvidePreferenceScreen(SupervisionPinManagementScreen.KEY)
class SupervisionPinManagementScreen : PreferenceScreenCreator {
class SupervisionPinManagementScreen : PreferenceScreenCreator, PreferenceAvailabilityProvider {
override val key: String
get() = KEY
override fun isAvailable(context: Context) =
SupervisionHelper.getInstance(context).isSupervisingCredentialSet()
override val title: Int
get() = R.string.supervision_pin_management_preference_title
@@ -36,7 +40,7 @@ class SupervisionPinManagementScreen : PreferenceScreenCreator {
// TODO(b/391994031): dynamically update the icon according to PIN status.
override val icon: Int
get() = R.drawable.ic_pin
get() = R.drawable.ic_pin_outline
override fun fragmentClass() = SupervisionPinManagementFragment::class.java

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.accessibility;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settingslib.widget.TopIntroPreference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
/**
* Tests for {@link HearingDevicePairingIntroPreferenceController}.
*/
@RunWith(RobolectricTestRunner.class)
public class HearingDevicePairingIntroPreferenceControllerTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@Spy
private final Context mContext = ApplicationProvider.getApplicationContext();
@Spy
private final Resources mResources = mContext.getResources();
@Mock
private PreferenceScreen mScreen;
@Mock
private HearingAidHelper mHelper;
private HearingDevicePairingIntroPreferenceController mController;
private TopIntroPreference mPreference;
@Before
public void setUp() {
mController = new HearingDevicePairingIntroPreferenceController(mContext, "test_key",
mHelper);
mPreference = new TopIntroPreference(mContext);
mPreference.setKey("test_key");
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
when(mContext.getResources()).thenReturn(mResources);
}
@Test
public void getAvailabilityStatus_hearingAidSupported_available() {
when(mHelper.isHearingAidSupported()).thenReturn(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailabilityStatus_hearingAidNotSupported_unsupportedOnDevice() {
when(mHelper.isHearingAidSupported()).thenReturn(false);
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
public void displayPreference_ashaHapSupported_expectedTitle() {
when(mHelper.isAshaProfileSupported()).thenReturn(true);
when(mHelper.isHapClientProfileSupported()).thenReturn(true);
mController.displayPreference(mScreen);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.accessibility_hearing_device_pairing_intro));
}
@Test
public void displayPreference_ashaSupported_expectedTitle() {
when(mHelper.isAshaProfileSupported()).thenReturn(true);
when(mHelper.isHapClientProfileSupported()).thenReturn(false);
mController.displayPreference(mScreen);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.accessibility_hearing_device_pairing_asha_only_intro));
}
@Test
public void displayPreference_hapSupported_expectedTitle() {
when(mHelper.isAshaProfileSupported()).thenReturn(false);
when(mHelper.isHapClientProfileSupported()).thenReturn(true);
mController.displayPreference(mScreen);
assertThat(mPreference.getTitle().toString()).isEqualTo(
mContext.getString(R.string.accessibility_hearing_device_pairing_hap_only_intro));
}
}

View File

@@ -70,6 +70,7 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.biometrics.fingerprint.feature.FingerprintExtPreferencesProvider;
import com.android.settings.password.ChooseLockSettingsHelper;
import com.android.settings.password.ConfirmDeviceCredentialActivity;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -79,6 +80,7 @@ import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowSettingsPreferenceFragment;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.RestrictedSwitchPreference;
import org.junit.After;
@@ -123,6 +125,12 @@ public class FingerprintSettingsFragmentTest {
private PackageManager mPackageManager;
@Mock
private BiometricManager mBiometricManager;
@Mock
private FingerprintExtPreferencesProvider mExtPreferencesProvider;
@Mock
private RestrictedPreference mRestrictedPreference0;
@Mock
private RestrictedPreference mRestrictedPreference1;
@Captor
private ArgumentCaptor<CancellationSignal> mCancellationSignalArgumentCaptor =
@@ -159,6 +167,11 @@ public class FingerprintSettingsFragmentTest {
when(mFakeFeatureFactory.getFingerprintFeatureProvider()
.getFingerprintSettingsFeatureProvider())
.thenReturn(mFingerprintSettingsFeatureProvider);
when(mFakeFeatureFactory.getFingerprintFeatureProvider()
.getExtPreferenceProvider(mContext))
.thenReturn(mExtPreferencesProvider);
when(mExtPreferencesProvider.getSize()).thenReturn(0);
}
@After
@@ -417,6 +430,33 @@ public class FingerprintSettingsFragmentTest {
assertThat(checkEnrolledPerf).isNull();
}
@Test
public void testHasExtPreferences() {
String key0 = "ExtKey0";
String key1 = "ExtKey1";
when(mRestrictedPreference0.getKey()).thenReturn(key0);
when(mRestrictedPreference1.getKey()).thenReturn(key1);
when(mExtPreferencesProvider.getSize()).thenReturn(2);
when(mExtPreferencesProvider.newPreference(eq(0),
any(FingerprintExtPreferencesProvider.PreferenceInflater.class)))
.thenReturn(mRestrictedPreference0);
when(mExtPreferencesProvider.newPreference(eq(1),
any(FingerprintExtPreferencesProvider.PreferenceInflater.class)))
.thenReturn(mRestrictedPreference1);
Fingerprint fingerprint = new Fingerprint("Test", 0, 0);
doReturn(List.of(fingerprint)).when(mFingerprintManager).getEnrolledFingerprints(anyInt());
setUpFragment(false, PRIMARY_USER_ID, TYPE_UDFPS_OPTICAL, 5);
shadowOf(Looper.getMainLooper()).idle();
Preference preference0 = mFragment.findPreference(key0);
assertThat(preference0).isEqualTo(mRestrictedPreference0);
Preference preference1 = mFragment.findPreference(key1);
assertThat(preference1).isEqualTo(mRestrictedPreference1);
}
private void setSensor(@FingerprintSensorProperties.SensorType int sensorType,
int maxFingerprints) {
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();

View File

@@ -65,7 +65,7 @@ class SupervisionDashboardScreenTest {
val mainSwitchPreference =
fragment.findPreference<MainSwitchPreference>(SupervisionMainSwitchPreference.KEY)!!
val childPreference =
fragment.findPreference<Preference>(SupervisionPinManagementScreen.KEY)!!
fragment.findPreference<Preference>(SupervisionWebContentFiltersScreen.KEY)!!
assertThat(childPreference.isEnabled).isFalse()
@@ -89,7 +89,7 @@ class SupervisionDashboardScreenTest {
val mainSwitchPreference =
fragment.findPreference<MainSwitchPreference>(SupervisionMainSwitchPreference.KEY)!!
val childPreference =
fragment.findPreference<Preference>(SupervisionPinManagementScreen.KEY)!!
fragment.findPreference<Preference>(SupervisionWebContentFiltersScreen.KEY)!!
assertThat(childPreference.isEnabled).isFalse()

View File

@@ -15,25 +15,73 @@
*/
package com.android.settings.supervision
import android.app.KeyguardManager
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.UserInfo
import android.os.UserManager
import android.os.UserManager.USER_TYPE_PROFILE_SUPERVISING
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.mock
import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class SupervisionPinManagementScreenTest {
private val context: Context = ApplicationProvider.getApplicationContext()
private val mockKeyguardManager = mock<KeyguardManager>()
private val mockUserManager = mock<UserManager>()
private val context: Context =
object : ContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getSystemService(name: String): Any? =
when (name) {
Context.KEYGUARD_SERVICE -> mockKeyguardManager
Context.USER_SERVICE -> mockUserManager
else -> super.getSystemService(name)
}
}
private val supervisionPinManagementScreen = SupervisionPinManagementScreen()
@Before
fun setup() {
SupervisionHelper.sInstance = null
}
@Test
fun key() {
assertThat(supervisionPinManagementScreen.key).isEqualTo(SupervisionPinManagementScreen.KEY)
}
@Test
fun isAvailable() {
whenever(mockUserManager.users).thenReturn(listOf(SUPERVISING_USER_INFO))
whenever(mockKeyguardManager.isDeviceSecure(SUPERVISING_USER_ID)).thenReturn(true)
assertThat(supervisionPinManagementScreen.isAvailable(context)).isTrue()
}
@Test
fun isAvailable_noSupervisingUser_returnsFalse() {
whenever(mockUserManager.users).thenReturn(emptyList())
whenever(mockKeyguardManager.isDeviceSecure(SUPERVISING_USER_ID)).thenReturn(true)
assertThat(supervisionPinManagementScreen.isAvailable(context)).isFalse()
}
@Test
fun isAvailable_noSupervisingCredential_returnsFalse() {
whenever(mockUserManager.users).thenReturn(listOf(SUPERVISING_USER_INFO))
whenever(mockKeyguardManager.isDeviceSecure(SUPERVISING_USER_ID)).thenReturn(false)
assertThat(supervisionPinManagementScreen.isAvailable(context)).isFalse()
}
@Test
fun getTitle() {
assertThat(supervisionPinManagementScreen.title)
@@ -45,4 +93,16 @@ class SupervisionPinManagementScreenTest {
assertThat(supervisionPinManagementScreen.summary)
.isEqualTo(R.string.supervision_pin_management_preference_summary_add)
}
private companion object {
const val SUPERVISING_USER_ID = 5
val SUPERVISING_USER_INFO =
UserInfo(
SUPERVISING_USER_ID,
/* name */ "supervising",
/* iconPath */ "",
/* flags */ 0,
USER_TYPE_PROFILE_SUPERVISING,
)
}
}

View File

@@ -28,6 +28,10 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settings.network.SatelliteWarningDialogActivity
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.CUSTOM_CONTENT_BUTTON_NAME
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.CUSTOM_CONTENT_DESCRIPTION
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.CUSTOM_CONTENT_TITLE
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.EXTRA_TYPE_OF_SATELLITE_CUSTOMIZED_CONTENT
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.TYPE_IS_AIRPLANE_MODE
import com.android.settings.network.SatelliteWarningDialogActivity.Companion.TYPE_IS_BLUETOOTH
@@ -74,6 +78,33 @@ class SatelliteWarningDialogActivityTest {
scenario.close()
}
@Test
fun launchActivity_checkCustomizedContent_hasContentIntent() {
val scenario = launchCustomizedDialogActivity()
scenario.onActivity { activity ->
assert(activity.intent.hasExtra(EXTRA_TYPE_OF_SATELLITE_CUSTOMIZED_CONTENT))
}
scenario.close()
}
@Test
fun testCustomizedDialogIsExisted() {
val scenario = launchCustomizedDialogActivity()
composeTestRule.onNodeWithText(
getSatelliteTestContent().get(CUSTOM_CONTENT_BUTTON_NAME).toString()
).assertIsDisplayed()
composeTestRule.onNodeWithText(
getSatelliteTestContent().get(CUSTOM_CONTENT_TITLE).toString()
).assertIsDisplayed()
composeTestRule.onNodeWithText(
getSatelliteTestContent().get(CUSTOM_CONTENT_DESCRIPTION).toString()
).assertIsDisplayed()
scenario.close()
}
@Test
fun launchActivity_unknownType_destroyActivity() {
val scenario = launchDialogActivity(TYPE_IS_UNKNOWN)
@@ -117,10 +148,31 @@ class SatelliteWarningDialogActivityTest {
scenario.close()
}
private fun launchDialogActivity(type: Int): ActivityScenario<SatelliteWarningDialogActivity> = launch(
Intent(
private fun launchDialogActivity(type: Int): ActivityScenario<SatelliteWarningDialogActivity> =
launch(Intent(
context,
SatelliteWarningDialogActivity::class.java
).putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type)
)
private fun launchCustomizedDialogActivity(): ActivityScenario<SatelliteWarningDialogActivity> =
launch(Intent(
context,
SatelliteWarningDialogActivity::class.java
).putExtra(EXTRA_TYPE_OF_SATELLITE_CUSTOMIZED_CONTENT, getSatelliteTestContent())
)
private fun getSatelliteTestContent(): HashMap<Int, String> {
val content = HashMap<Int, String>()
content.put(CUSTOM_CONTENT_TITLE, TEST_TITLE)
content.put(CUSTOM_CONTENT_DESCRIPTION, TEST_DESCRIPTION)
content.put(CUSTOM_CONTENT_BUTTON_NAME, TEST_BUTTON_NAME)
return content
}
companion object {
const val TEST_TITLE = "TEST_TITLE"
const val TEST_DESCRIPTION = "TEST_DESCRIPTION"
const val TEST_BUTTON_NAME = "TEST_BUTTON_NAME"
}
}

View File

@@ -16,6 +16,9 @@
package com.android.settings.network.telephony.satellite;
import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC;
import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL;
import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT;
import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
@@ -81,6 +84,8 @@ public class SatelliteSettingAccountInfoControllerTest {
@Test
public void getAvailabilityStatus_entitlementNotSupport_returnConditionalUnavailable() {
mPersistableBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT,
CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC);
when(mContext.getSystemService(SatelliteManager.class)).thenReturn(null);
mController.init(TEST_SUB_ID, mPersistableBundle, false, false);
@@ -91,6 +96,8 @@ public class SatelliteSettingAccountInfoControllerTest {
@Test
public void getAvailabilityStatus_entitlementIsSupported_returnConditionalUnavailable() {
mPersistableBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT,
CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC);
mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
mController.init(TEST_SUB_ID, mPersistableBundle, false, false);
@@ -99,6 +106,17 @@ public class SatelliteSettingAccountInfoControllerTest {
assertThat(result).isEqualTo(AVAILABLE);
}
@Test
public void getAvailabilityStatus_connectionTypeISManual_returnAvailable() {
mPersistableBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT,
CARRIER_ROAMING_NTN_CONNECT_MANUAL);
mController.init(TEST_SUB_ID, mPersistableBundle, false, false);
int result = mController.getAvailabilityStatus(TEST_SUB_ID);
assertThat(result).isEqualTo(AVAILABLE);
}
@Test
public void displayPreference_showCategoryTitle_correctOperatorName() {
mPersistableBundle.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);

View File

@@ -40,6 +40,7 @@ import com.android.settings.testutils.ResourcesUtils;
import com.android.settingslib.widget.FooterPreference;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -115,6 +116,7 @@ public class SatelliteSettingFooterControllerTest {
}
@Test
@Ignore("b/405279842")
public void displayPreferenceScreen_emergencyMsgSupport_noEmergencyContent() {
mPersistableBundle.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, true);
PreferenceScreen screen = new PreferenceManager(mContext).createPreferenceScreen(mContext);