Merge "[A11y] Add battery chart slot selected status in content description." into main
diff --git a/res/layout/accessibility_magnification_mode_header.xml b/res/layout/accessibility_dialog_header.xml
similarity index 93%
rename from res/layout/accessibility_magnification_mode_header.xml
rename to res/layout/accessibility_dialog_header.xml
index e476553..ace8b23 100644
--- a/res/layout/accessibility_magnification_mode_header.xml
+++ b/res/layout/accessibility_dialog_header.xml
@@ -21,9 +21,9 @@
android:padding="?android:attr/dialogPreferredPadding">
<TextView
+ android:id="@+id/accessibility_dialog_header_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/accessibility_magnification_area_settings_message"
android:textSize="16sp"
style="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorAlertDialogListItem"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6193263..0f51ddd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1054,6 +1054,7 @@
<!-- Watch unlock enrollment and settings --><skip />
<!-- Title shown for menu item that launches watch unlock settings. [CHAR LIMIT=40] -->
<string name ="security_settings_activeunlock_preference_title">Watch Unlock</string>
+ <string name="security_settings_activeunlock">Watch</string>
<!-- Introduction shown in face and fingerprint page to introduce the biometric feature. [CHAR LIMIT=NONE]-->
<string name="biometric_settings_intro_with_activeunlock">When you set up Face Unlock and Fingerprint Unlock, your phone will ask for your fingerprint when you wear a mask or are in a dark area.\n\nWatch Unlock is another convenient way to unlock your phone, for example, when your fingers are wet or face isn\u2019t recognized.</string>
<!-- Introduction shown in fingerprint page to explain that watch unlock can be used if fingerprint isn't recognized. [CHAR LIMIT=NONE]-->
@@ -5219,6 +5220,16 @@
<string name="accessibility_screen_magnification_title">Magnification</string>
<!-- Title for accessibility shortcut preference for magnification. [CHAR LIMIT=60] -->
<string name="accessibility_screen_magnification_shortcut_title">Magnification shortcut</string>
+ <!-- Title of cursor following mode preference for magnification. [CHAR LIMIT=60] -->
+ <string name="accessibility_magnification_cursor_following_title">Cursor following</string>
+ <!-- Header message of cursor following mode dialog for magnification. [CHAR LIMIT=none] -->
+ <string name="accessibility_magnification_cursor_following_header">Choose how Magnification follows your cursor.</string>
+ <!-- Option title of cursor following continuous mode in the mode selection dialog. [CHAR LIMIT=none] -->
+ <string name="accessibility_magnification_cursor_following_continuous">Move screen continuously as mouse moves</string>
+ <!-- Option title of cursor following center mode in the mode selection dialog. [CHAR LIMIT=none] -->
+ <string name="accessibility_magnification_cursor_following_center">Move screen keeping mouse at center of screen</string>
+ <!-- Option title of cursor following edge mode in the mode selection dialog. [CHAR LIMIT=none] -->
+ <string name="accessibility_magnification_cursor_following_edge">Move screen when mouse touches edges of screen</string>
<!-- Title for accessibility follow typing preference for magnification. [CHAR LIMIT=35] -->
<string name="accessibility_screen_magnification_follow_typing_title">Magnify typing</string>
<!-- Summary for accessibility follow typing preference for magnification. [CHAR LIMIT=none] -->
diff --git a/res/xml/mouse_settings.xml b/res/xml/mouse_settings.xml
index e4b3f1c..ec1c39d 100644
--- a/res/xml/mouse_settings.xml
+++ b/res/xml/mouse_settings.xml
@@ -28,11 +28,11 @@
settings:controller="com.android.settings.inputmethod.MousePointerAccelerationPreferenceController" />
<com.android.settings.widget.SeekBarPreference
- android:key="trackpad_pointer_speed"
- android:title="@string/trackpad_pointer_speed"
+ android:key="pointer_speed"
+ android:title="@string/pointer_speed"
android:order="20"
android:selectable="false"
- settings:controller="com.android.settings.inputmethod.TouchpadPointerSpeedPreferenceController"/>
+ settings:controller="com.android.settings.inputmethod.MousePointerSpeedPreferenceController"/>
<SwitchPreferenceCompat
android:key="mouse_swap_primary_button"
diff --git a/res/xml/night_display_settings.xml b/res/xml/night_display_settings.xml
index 95d5034..d75619d 100644
--- a/res/xml/night_display_settings.xml
+++ b/res/xml/night_display_settings.xml
@@ -52,7 +52,7 @@
android:title="@string/night_display_end_time_title"
settings:controller="com.android.settings.display.NightDisplayCustomEndTimePreferenceController"/>
- <com.android.settings.widget.SeekBarPreference
+ <com.android.settingslib.widget.SliderPreference
android:key="night_display_temperature"
android:title="@string/night_display_temperature_title"
settings:keywords="@string/keywords_display_night_display"
diff --git a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
index a8e456d..3ae64fc 100644
--- a/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityActivityPreference.java
@@ -127,14 +127,13 @@
final String htmlDescription = mA11yShortcutInfo.loadHtmlDescription(mPm);
final String settingsClassName = mA11yShortcutInfo.getSettingsActivityName();
final String tileServiceClassName = mA11yShortcutInfo.getTileServiceName();
- final int metricsCategory = FeatureFactory.getFeatureFactory()
- .getAccessibilityMetricsFeatureProvider()
- .getDownloadedFeatureMetricsCategory(mComponentName);
+ final int pageIdCategory = FeatureFactory.getFeatureFactory()
+ .getAccessibilityPageIdFeatureProvider().getCategory(mComponentName);
ThreadUtils.getUiThreadHandler().post(() -> {
RestrictedPreferenceHelper.putBasicExtras(
this, prefKey, getTitle(), intro, description, imageRes,
- htmlDescription, mComponentName, metricsCategory);
+ htmlDescription, mComponentName, pageIdCategory);
RestrictedPreferenceHelper.putSettingsExtras(this, getPackageName(), settingsClassName);
RestrictedPreferenceHelper.putTileServiceExtras(
this, getPackageName(), tileServiceClassName);
diff --git a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
index 4e9cd92..347c769 100644
--- a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
@@ -228,10 +228,10 @@
new ComponentName(packageName, tileServiceClassName).flattenToString());
}
- final int metricsCategory = FeatureFactory.getFeatureFactory()
- .getAccessibilityMetricsFeatureProvider()
- .getDownloadedFeatureMetricsCategory(componentName);
- extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
+ final int pageIdCategory = FeatureFactory.getFeatureFactory()
+ .getAccessibilityPageIdFeatureProvider().getCategory(componentName);
+ extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, pageIdCategory);
+ extras.putInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY, pageIdCategory);
extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes());
diff --git a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
index c89b8d7..dc49008 100644
--- a/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
+++ b/src/com/android/settings/accessibility/AccessibilityDialogUtils.java
@@ -104,6 +104,11 @@
* screen / Switch between full and partial screen > Save.
*/
int DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING = 1011;
+
+ /**
+ * OPEN: Settings > Accessibility > Magnification > Cursor following.
+ */
+ int DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE = 1012;
}
/**
diff --git a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java
index 018bd2e..7d03230 100644
--- a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java
+++ b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java
@@ -15,8 +15,6 @@
*/
package com.android.settings.accessibility;
-import android.content.ComponentName;
-
import androidx.annotation.Nullable;
/**
@@ -25,11 +23,11 @@
public interface AccessibilityFeedbackFeatureProvider {
/**
- * Returns value according to the {@code componentName}.
+ * Returns value according to the {@code pageId}.
*
- * @param componentName the component name of the downloaded service or activity
- * @return Feedback bucket ID
+ * @param pageId The unique identifier of the page.
+ * @return Feedback bucket ID associated with the page, or {@code null} if is not found.
*/
@Nullable
- String getCategory(@Nullable ComponentName componentName);
+ String getCategory(int pageId);
}
diff --git a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java
index 917c5f6..2381887 100644
--- a/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java
+++ b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProviderImpl.java
@@ -15,8 +15,6 @@
*/
package com.android.settings.accessibility;
-import android.content.ComponentName;
-
import androidx.annotation.Nullable;
/** Default implementation of {@link AccessibilityFeedbackFeatureProvider}. */
@@ -25,7 +23,7 @@
@Override
@Nullable
- public String getCategory(@Nullable ComponentName componentName) {
- return "";
+ public String getCategory(int pageId) {
+ return null;
}
}
diff --git a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProvider.java
similarity index 84%
rename from src/com/android/settings/accessibility/AccessibilityMetricsFeatureProvider.java
rename to src/com/android/settings/accessibility/AccessibilityPageIdFeatureProvider.java
index a9d7c05..698efbe 100644
--- a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProvider.java
+++ b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProvider.java
@@ -21,9 +21,9 @@
import androidx.annotation.Nullable;
/**
- * Provider for Accessibility metrics related features.
+ * Provider for Accessibility page id related features.
*/
-public interface AccessibilityMetricsFeatureProvider {
+public interface AccessibilityPageIdFeatureProvider {
/**
* Returns {@link android.app.settings.SettingsEnums} value according to the {@code
@@ -32,5 +32,5 @@
* @param componentName the component name of the downloaded service or activity
* @return value in {@link android.app.settings.SettingsEnums}
*/
- int getDownloadedFeatureMetricsCategory(@Nullable ComponentName componentName);
+ int getCategory(@Nullable ComponentName componentName);
}
diff --git a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProviderImpl.java
similarity index 75%
rename from src/com/android/settings/accessibility/AccessibilityMetricsFeatureProviderImpl.java
rename to src/com/android/settings/accessibility/AccessibilityPageIdFeatureProviderImpl.java
index 0f85f38..acd8aab 100644
--- a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProviderImpl.java
+++ b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProviderImpl.java
@@ -19,14 +19,16 @@
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
+import androidx.annotation.Nullable;
+
/**
- * Provider implementation for Accessibility metrics related features.
+ * Provider implementation for Accessibility page id related features.
*/
-public class AccessibilityMetricsFeatureProviderImpl implements
- AccessibilityMetricsFeatureProvider {
+public class AccessibilityPageIdFeatureProviderImpl implements
+ AccessibilityPageIdFeatureProvider {
@Override
- public int getDownloadedFeatureMetricsCategory(ComponentName componentName) {
+ public int getCategory(@Nullable ComponentName componentName) {
return SettingsEnums.ACCESSIBILITY_SERVICE;
}
}
diff --git a/src/com/android/settings/accessibility/AccessibilityServicePreference.java b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
index 8a22d82..7032774 100644
--- a/src/com/android/settings/accessibility/AccessibilityServicePreference.java
+++ b/src/com/android/settings/accessibility/AccessibilityServicePreference.java
@@ -123,13 +123,12 @@
final String settingsClassName = mA11yServiceInfo.getSettingsActivityName();
final String tileServiceClassName = mA11yServiceInfo.getTileServiceName();
final ResolveInfo resolveInfo = mA11yServiceInfo.getResolveInfo();
- final int metricsCategory = FeatureFactory.getFeatureFactory()
- .getAccessibilityMetricsFeatureProvider()
- .getDownloadedFeatureMetricsCategory(mComponentName);
+ final int pageIdCategory = FeatureFactory.getFeatureFactory()
+ .getAccessibilityPageIdFeatureProvider().getCategory(mComponentName);
ThreadUtils.getUiThreadHandler().post(() -> {
RestrictedPreferenceHelper.putBasicExtras(
this, prefKey, getTitle(), intro, description, imageRes,
- htmlDescription, mComponentName, metricsCategory);
+ htmlDescription, mComponentName, pageIdCategory);
RestrictedPreferenceHelper.putServiceExtras(this, resolveInfo, mServiceEnabled);
RestrictedPreferenceHelper.putSettingsExtras(this, getPackageName(), settingsClassName);
RestrictedPreferenceHelper.putTileServiceExtras(
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 57eb4d5..2c8247f 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -97,6 +97,7 @@
static final String EXTRA_HTML_DESCRIPTION = "html_description";
static final String EXTRA_TIME_FOR_LOGGING = "start_time_to_log_a11y_tool";
static final String EXTRA_METRICS_CATEGORY = "metrics_category";
+ static final String EXTRA_FEEDBACK_CATEGORY = "feedback_category";
// Timeout before we update the services if packages are added/removed
// since the AccessibilityManagerService has to do that processing first
@@ -255,7 +256,7 @@
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
if (getFeedbackManager().isAvailable()) {
menu.add(Menu.NONE, MENU_ID_SEND_FEEDBACK, Menu.NONE,
- getPrefContext().getText(R.string.accessibility_send_feedback_title));
+ R.string.accessibility_send_feedback_title);
}
super.onCreateOptionsMenu(menu, inflater);
}
@@ -286,7 +287,7 @@
private FeedbackManager getFeedbackManager() {
if (mFeedbackManager == null) {
- mFeedbackManager = new FeedbackManager(getActivity());
+ mFeedbackManager = new FeedbackManager(getActivity(), SettingsEnums.ACCESSIBILITY);
}
return mFeedbackManager;
}
diff --git a/src/com/android/settings/accessibility/FeedbackManager.java b/src/com/android/settings/accessibility/FeedbackManager.java
index 52aefd2..dc4baa7 100644
--- a/src/com/android/settings/accessibility/FeedbackManager.java
+++ b/src/com/android/settings/accessibility/FeedbackManager.java
@@ -16,7 +16,6 @@
package com.android.settings.accessibility;
import android.app.Activity;
-import android.content.ComponentName;
import android.content.Intent;
import android.text.TextUtils;
@@ -46,23 +45,14 @@
* Constructs a new FeedbackManager.
*
* @param activity The activity context. A WeakReference is used to prevent memory leaks.
+ * @param pageId The unique identifier of the page associated with the feedback.
*/
- public FeedbackManager(@Nullable Activity activity) {
- this(activity, /* componentName= */ null);
- }
-
- /**
- * Constructs a new FeedbackManager.
- *
- * @param activity The activity context. A WeakReference is used to prevent memory leaks.
- * @param componentName The component name associated with the feedback.
- */
- public FeedbackManager(@Nullable Activity activity, @Nullable ComponentName componentName) {
+ public FeedbackManager(@Nullable Activity activity, int pageId) {
this(activity,
DeviceInfoUtils.getFeedbackReporterPackage(activity),
FeatureFactory.getFeatureFactory()
.getAccessibilityFeedbackFeatureProvider()
- .getCategory(componentName));
+ .getCategory(pageId));
}
/**
diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
index 013fdee..c6995b0 100644
--- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java
@@ -31,8 +31,6 @@
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
@@ -60,6 +58,11 @@
}
@Override
+ public int getFeedbackCategory() {
+ return getArguments().getInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY);
+ }
+
+ @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Init new preference to replace the switch preference instead.
@@ -115,13 +118,6 @@
return mTileComponentName;
}
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- // Do not call super. We don't want to see the "Help & feedback" option on this page so as
- // not to confuse users who think they might be able to send feedback about a specific
- // accessibility service from this page.
- }
-
// IMPORTANT: Refresh the info since there are dynamically changing capabilities.
private AccessibilityShortcutInfo getAccessibilityShortcutInfo() {
final List<AccessibilityShortcutInfo> infos = AccessibilityManager.getInstance(
diff --git a/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java b/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java
new file mode 100644
index 0000000..d217ead
--- /dev/null
+++ b/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceController.java
@@ -0,0 +1,221 @@
+/*
+ * 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.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.provider.Settings;
+import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.util.Preconditions;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.DialogCreatable;
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums;
+import com.android.settings.core.BasePreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller that shows the magnification cursor following mode and the preference click behavior.
+ */
+public class MagnificationCursorFollowingModePreferenceController extends
+ BasePreferenceController implements DialogCreatable {
+ static final String PREF_KEY =
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
+
+ private static final String TAG =
+ MagnificationCursorFollowingModePreferenceController.class.getSimpleName();
+
+ private final List<ModeInfo> mModeList = new ArrayList<>();
+ @Nullable
+ private DialogHelper mDialogHelper;
+ @VisibleForTesting
+ @Nullable
+ ListView mModeListView;
+ @Nullable
+ private Preference mModePreference;
+
+ public MagnificationCursorFollowingModePreferenceController(@NonNull Context context,
+ @NonNull String preferenceKey) {
+ super(context, preferenceKey);
+ initModeList();
+ }
+
+ public void setDialogHelper(@NonNull DialogHelper dialogHelper) {
+ mDialogHelper = dialogHelper;
+ }
+
+ private void initModeList() {
+ mModeList.add(new ModeInfo(mContext.getString(
+ R.string.accessibility_magnification_cursor_following_continuous),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS));
+ mModeList.add(new ModeInfo(
+ mContext.getString(R.string.accessibility_magnification_cursor_following_center),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER));
+ mModeList.add(new ModeInfo(
+ mContext.getString(R.string.accessibility_magnification_cursor_following_edge),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE));
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @NonNull
+ @Override
+ public CharSequence getSummary() {
+ return getCursorFollowingModeSummary(getCurrentMagnificationCursorFollowingMode());
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mModePreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(@NonNull Preference preference) {
+ if (!TextUtils.equals(preference.getKey(), getPreferenceKey()) || mModePreference == null) {
+ return super.handlePreferenceTreeClick(preference);
+ }
+
+ Preconditions.checkNotNull(mDialogHelper).showDialog(
+ DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE);
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ Preconditions.checkArgument(
+ dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
+ "This only handles cursor following mode dialog");
+ return createMagnificationCursorFollowingModeDialog();
+ }
+
+ @Override
+ public int getDialogMetricsCategory(int dialogId) {
+ Preconditions.checkArgument(
+ dialogId == DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE,
+ "This only handles cursor following mode dialog");
+ return SettingsEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING;
+ }
+
+ @NonNull
+ private Dialog createMagnificationCursorFollowingModeDialog() {
+ mModeListView = AccessibilityDialogUtils.createSingleChoiceListView(mContext, mModeList,
+ /* itemListener= */null);
+ final View headerView = LayoutInflater.from(mContext).inflate(
+ R.layout.accessibility_dialog_header, mModeListView,
+ /* attachToRoot= */false);
+ final TextView textView = Preconditions.checkNotNull(headerView.findViewById(
+ R.id.accessibility_dialog_header_text_view));
+ textView.setText(
+ mContext.getString(R.string.accessibility_magnification_cursor_following_header));
+ textView.setVisibility(View.VISIBLE);
+ mModeListView.addHeaderView(headerView, /* data= */null, /* isSelectable= */false);
+ final int selectionIndex = computeSelectionIndex();
+ if (selectionIndex != AdapterView.INVALID_POSITION) {
+ mModeListView.setItemChecked(selectionIndex, /* value= */true);
+ }
+ final CharSequence title = mContext.getString(
+ R.string.accessibility_magnification_cursor_following_title);
+ final CharSequence positiveBtnText = mContext.getString(R.string.save);
+ final CharSequence negativeBtnText = mContext.getString(R.string.cancel);
+ return AccessibilityDialogUtils.createCustomDialog(mContext, title, mModeListView,
+ positiveBtnText,
+ this::onMagnificationCursorFollowingModeDialogPositiveButtonClicked,
+ negativeBtnText, /* negativeListener= */null);
+ }
+
+ void onMagnificationCursorFollowingModeDialogPositiveButtonClicked(
+ DialogInterface dialogInterface, int which) {
+ ListView listView = Preconditions.checkNotNull(mModeListView);
+ final int selectionIndex = listView.getCheckedItemPosition();
+ if (selectionIndex == AdapterView.INVALID_POSITION) {
+ Log.w(TAG, "Selected positive button with INVALID_POSITION index");
+ return;
+ }
+ ModeInfo cursorFollowingMode = (ModeInfo) listView.getItemAtPosition(selectionIndex);
+ if (cursorFollowingMode != null) {
+ Preconditions.checkNotNull(mModePreference).setSummary(
+ getCursorFollowingModeSummary(cursorFollowingMode.mMode));
+ Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY,
+ cursorFollowingMode.mMode);
+ }
+ }
+
+ private int computeSelectionIndex() {
+ ListView listView = Preconditions.checkNotNull(mModeListView);
+ @AccessibilityMagnificationCursorFollowingMode
+ final int currentMode = getCurrentMagnificationCursorFollowingMode();
+ for (int i = 0; i < listView.getCount(); i++) {
+ final ModeInfo mode = (ModeInfo) listView.getItemAtPosition(i);
+ if (mode != null && mode.mMode == currentMode) {
+ return i;
+ }
+ }
+ return AdapterView.INVALID_POSITION;
+ }
+
+ @NonNull
+ private CharSequence getCursorFollowingModeSummary(
+ @AccessibilityMagnificationCursorFollowingMode int cursorFollowingMode) {
+ int stringId = switch (cursorFollowingMode) {
+ case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER ->
+ R.string.accessibility_magnification_cursor_following_center;
+ case Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE ->
+ R.string.accessibility_magnification_cursor_following_edge;
+ default ->
+ R.string.accessibility_magnification_cursor_following_continuous;
+ };
+ return mContext.getString(stringId);
+ }
+
+ private @AccessibilityMagnificationCursorFollowingMode int
+ getCurrentMagnificationCursorFollowingMode() {
+ return Settings.Secure.getInt(mContext.getContentResolver(), PREF_KEY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
+ }
+
+ static class ModeInfo extends ItemInfoArrayAdapter.ItemInfo {
+ @AccessibilityMagnificationCursorFollowingMode
+ public final int mMode;
+
+ ModeInfo(@NonNull CharSequence title,
+ @AccessibilityMagnificationCursorFollowingMode int mode) {
+ super(title, /* summary= */null, /* drawableId= */null);
+ mMode = mode;
+ }
+ }
+}
diff --git a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
index 71ea4c2..93cb23b 100644
--- a/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
+++ b/src/com/android/settings/accessibility/MagnificationModePreferenceController.java
@@ -176,8 +176,12 @@
mContext, mModeInfos, this::onMagnificationModeSelected);
final View headerView = LayoutInflater.from(mContext).inflate(
- R.layout.accessibility_magnification_mode_header,
- getMagnificationModesListView(), /* attachToRoot= */false);
+ R.layout.accessibility_dialog_header, getMagnificationModesListView(),
+ /* attachToRoot= */false);
+ final TextView textView = Preconditions.checkNotNull(headerView.findViewById(
+ R.id.accessibility_dialog_header_text_view));
+ textView.setText(
+ mContext.getString(R.string.accessibility_magnification_area_settings_message));
getMagnificationModesListView().addHeaderView(headerView, /* data= */null,
/* isSelectable= */false);
diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
index 5c18be8..ae6239a 100644
--- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
+++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
@@ -217,6 +217,7 @@
extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, imageRes);
extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
+ extras.putInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY, metricsCategory);
}
/**
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index 06bcdb7..a11ad46 100644
--- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -39,8 +39,6 @@
import android.text.BidiFormatter;
import android.text.TextUtils;
import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
import android.view.accessibility.AccessibilityManager;
import android.widget.CompoundButton;
@@ -76,10 +74,8 @@
}
@Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- // Do not call super. We don't want to see the "Help & feedback" option on this page so as
- // not to confuse users who think they might be able to send feedback about a specific
- // accessibility service from this page.
+ public int getFeedbackCategory() {
+ return getArguments().getInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY);
}
@Override
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index 9367251..66c32df 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -40,6 +40,9 @@
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
@@ -48,6 +51,7 @@
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -89,6 +93,7 @@
// <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
private static final String IMG_PREFIX = "R.drawable.";
private static final String DRAWABLE_FOLDER = "drawable";
+ static final int MENU_ID_SEND_FEEDBACK = 0;
protected TopIntroPreference mTopIntroPreference;
protected SettingsMainSwitchPreference mToggleServiceSwitchPreference;
@@ -102,6 +107,7 @@
protected Intent mSettingsIntent;
// The mComponentName maybe null, such as Magnify
protected ComponentName mComponentName;
+ @Nullable private FeedbackManager mFeedbackManager;
protected CharSequence mFeatureName;
protected Uri mImageUri;
protected CharSequence mHtmlDescription;
@@ -241,6 +247,24 @@
}
@Override
+ public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+ if (getFeedbackManager().isAvailable()) {
+ menu.add(Menu.NONE, MENU_ID_SEND_FEEDBACK, Menu.NONE,
+ R.string.accessibility_send_feedback_title);
+ }
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == MENU_ID_SEND_FEEDBACK) {
+ getFeedbackManager().sendFeedback();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL:
@@ -739,4 +763,28 @@
super.onCreateRecyclerView(inflater, parent, savedInstanceState);
return AccessibilityFragmentUtils.addCollectionInfoToAccessibilityDelegate(recyclerView);
}
+
+ @VisibleForTesting
+ void setFeedbackManager(FeedbackManager feedbackManager) {
+ this.mFeedbackManager = feedbackManager;
+ }
+
+ private FeedbackManager getFeedbackManager() {
+ if (mFeedbackManager == null) {
+ mFeedbackManager = new FeedbackManager(getActivity(), getFeedbackCategory());
+ }
+ return mFeedbackManager;
+ }
+
+ /**
+ * Returns the category of the feedback page.
+ *
+ * <p>By default, this method returns {@link SettingsEnums#PAGE_UNKNOWN}. This indicates that
+ * the feedback category is unknown, and the absence of a feedback menu.
+ *
+ * @return The feedback category, which is {@link SettingsEnums#PAGE_UNKNOWN} by default.
+ */
+ protected int getFeedbackCategory() {
+ return SettingsEnums.PAGE_UNKNOWN;
+ }
}
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
index 8b52507..71c95c0 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
@@ -93,6 +93,8 @@
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
@Nullable
private DialogCreatable mMagnificationModeDialogDelegate;
+ @Nullable
+ private DialogCreatable mMagnificationCursorFollowingModeDialogDelegate;
@Nullable
MagnificationOneFingerPanningPreferenceController mOneFingerPanningPreferenceController;
@@ -104,6 +106,12 @@
mMagnificationModeDialogDelegate = delegate;
}
+ @VisibleForTesting
+ public void setMagnificationCursorFollowingModeDialogDelegate(
+ @NonNull DialogCreatable delegate) {
+ mMagnificationCursorFollowingModeDialogDelegate = delegate;
+ }
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -186,6 +194,9 @@
case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
.onCreateDialog(dialogId);
+ case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
+ return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
+ .onCreateDialog(dialogId);
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
return AccessibilityShortcutsTutorial
.showAccessibilityGestureTutorialDialog(getPrefContext());
@@ -201,6 +212,11 @@
PackageManager.FEATURE_WINDOW_MAGNIFICATION);
}
+ private static boolean isMagnificationCursorFollowingModeDialogSupported() {
+ // TODO(b/398066000): Hide the setting when no pointer device exists for most form factors.
+ return com.android.settings.accessibility.Flags.enableMagnificationCursorFollowingDialog();
+ }
+
@Override
protected void initSettingsPreference() {
final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY);
@@ -213,6 +229,7 @@
addJoystickSetting(generalCategory);
// LINT.ThenChange(:search_data)
}
+ addCursorFollowingSetting(generalCategory);
addFeedbackSetting(generalCategory);
}
@@ -286,6 +303,31 @@
addPreferenceController(magnificationModePreferenceController);
}
+ private static Preference createCursorFollowingPreference(Context context) {
+ final Preference pref = new Preference(context);
+ pref.setTitle(R.string.accessibility_magnification_cursor_following_title);
+ pref.setKey(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
+ pref.setPersistent(false);
+ return pref;
+ }
+
+ private void addCursorFollowingSetting(PreferenceCategory generalCategory) {
+ if (!isMagnificationCursorFollowingModeDialogSupported()) {
+ return;
+ }
+
+ generalCategory.addPreference(createCursorFollowingPreference(getPrefContext()));
+
+ final MagnificationCursorFollowingModePreferenceController controller =
+ new MagnificationCursorFollowingModePreferenceController(
+ getContext(),
+ MagnificationCursorFollowingModePreferenceController.PREF_KEY);
+ controller.setDialogHelper(/* dialogHelper= */this);
+ mMagnificationCursorFollowingModeDialogDelegate = controller;
+ controller.displayPreference(getPreferenceScreen());
+ addPreferenceController(controller);
+ }
+
private static Preference createFollowTypingPreference(Context context) {
final Preference pref = new SwitchPreferenceCompat(context);
pref.setTitle(R.string.accessibility_screen_magnification_follow_typing_title);
@@ -510,6 +552,9 @@
case DialogEnums.DIALOG_MAGNIFICATION_TRIPLE_TAP_WARNING:
return Preconditions.checkNotNull(mMagnificationModeDialogDelegate)
.getDialogMetricsCategory(dialogId);
+ case DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE:
+ return Preconditions.checkNotNull(mMagnificationCursorFollowingModeDialogDelegate)
+ .getDialogMetricsCategory(dialogId);
case DialogEnums.GESTURE_NAVIGATION_TUTORIAL:
return SettingsEnums.DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION;
case DialogEnums.ACCESSIBILITY_BUTTON_TUTORIAL:
@@ -667,6 +712,11 @@
return rawData;
}
+ // Add all preferences to search raw data so that they are included in
+ // indexing, which happens infrequently. Irrelevant preferences should be
+ // hidden from the live returned search results by `getNonIndexableKeys`,
+ // which is called every time a search occurs. This allows for dynamic search
+ // entries that hide or show depending on current device state.
rawData.add(createShortcutPreferenceSearchData(context));
Stream.of(
createMagnificationModePreference(context),
@@ -674,6 +724,7 @@
createOneFingerPanningPreference(context),
createAlwaysOnPreference(context),
createJoystickPreference(context),
+ createCursorFollowingPreference(context),
createFeedbackPreference(context)
)
.forEach(pref ->
@@ -714,6 +765,10 @@
}
}
+ if (!isMagnificationCursorFollowingModeDialogSupported()) {
+ niks.add(MagnificationCursorFollowingModePreferenceController.PREF_KEY);
+ }
+
if (!Flags.enableLowVisionHats()) {
niks.add(MagnificationFeedbackPreferenceController.PREF_KEY);
}
diff --git a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
index 10813a7..eb0c93b 100644
--- a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java
@@ -80,6 +80,12 @@
}
@Override
+ public int getFeedbackCategory() {
+ // The feedback options should not be displayed on the setup wizard page.
+ return SettingsEnums.PAGE_UNKNOWN;
+ }
+
+ @Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
diff --git a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
index 10796b5..14dc0bc 100644
--- a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
+++ b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java
@@ -80,6 +80,12 @@
}
@Override
+ public int getFeedbackCategory() {
+ // The feedback options should not be displayed on the setup wizard page.
+ return SettingsEnums.PAGE_UNKNOWN;
+ }
+
+ @Override
public void onStop() {
// Log the final choice in value if it's different from the previous value.
if (mToggleServiceSwitchPreference.isChecked() != mToggleSwitchWasInitiallyChecked) {
diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
index 826583d..ad1f823 100644
--- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
+++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
@@ -95,8 +95,7 @@
userHandle)) {
if (RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(context,
DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, userHandle.getIdentifier())) {
- return context.getString(com.android.settingslib.widget.restricted
- .R.string.disabled_by_advanced_protection);
+ return context.getString(com.android.settingslib.R.string.disabled);
} else {
return context.getString(
com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
index 8cc7d6a..42029ff 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java
@@ -28,6 +28,7 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.utils.ThreadUtils;
@@ -76,17 +77,22 @@
mContentKey = contentKey;
String authority = new ActiveUnlockStatusUtils(mContext).getAuthority();
if (authority != null) {
- mUri = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(authority)
- .appendPath(CONTENT_PROVIDER_PATH)
- .build();
+ mUri = getUri(authority);
} else {
mUri = null;
}
}
+ /** Returns Active Unlock Uri. */
+ public static @NonNull Uri getUri(@NonNull String authority) {
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority)
+ .appendPath(CONTENT_PROVIDER_PATH)
+ .build();
+ }
+
/** Returns true if start listening for updates from the ContentProvider, false otherwise. */
public synchronized boolean subscribe() {
if (mSubscribed || mUri == null) {
@@ -123,25 +129,40 @@
Log.e(mLogTag, "Uri null when trying to fetch content");
return;
}
- ContentResolver contentResolver = mContext.getContentResolver();
- ContentProviderClient client = contentResolver.acquireContentProviderClient(mUri);
- Bundle bundle;
- try {
- bundle = client.call(mMethodName, null /* arg */, null /* extras */);
- } catch (RemoteException e) {
- Log.e(mLogTag, "Failed to call contentProvider", e);
- return;
- } finally {
- client.close();
- }
- if (bundle == null) {
- Log.e(mLogTag, "Null bundle returned from contentProvider");
- return;
- }
- String newValue = bundle.getString(mContentKey);
+
+ @Nullable String newValue = getContentFromUri(
+ mContext, mUri, mLogTag, mMethodName, mContentKey);
if (!TextUtils.equals(mContent, newValue)) {
mContent = newValue;
mContentChangedListener.onContentChanged(mContent);
}
}
+
+ /** Get the content from Uri. */
+ public static @Nullable String getContentFromUri(
+ @NonNull Context context,
+ @NonNull Uri uri,
+ @NonNull String logTag,
+ @NonNull String methodName,
+ @NonNull String contentKey) {
+ ContentResolver contentResolver = context.getContentResolver();
+ ContentProviderClient client = contentResolver.acquireContentProviderClient(uri);
+
+ @Nullable Bundle bundle = null;
+
+ try {
+ bundle = client.call(methodName, /* arg= */ null, /* extras = */ null);
+ } catch (RemoteException e) {
+ Log.e(logTag, "Failed to call contentProvider", e);
+ } finally {
+ client.close();
+ }
+
+ if (bundle == null) {
+ Log.e(logTag, "Null bundle returned from contentProvider");
+ return null;
+ }
+
+ return bundle.getString(contentKey);
+ }
}
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
index 1badb0f..9e81762 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java
@@ -21,8 +21,8 @@
/** Listens to device name updates from the content provider and fetches the latest value. */
public class ActiveUnlockDeviceNameListener {
private static final String TAG = "ActiveUnlockDeviceNameListener";
- private static final String METHOD_NAME = "getDeviceName";
- private static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
+ static final String METHOD_NAME = "getDeviceName";
+ static final String DEVICE_NAME_KEY = "com.android.settings.active_unlock.device_name";
private final ActiveUnlockContentListener mActiveUnlockContentListener;
public ActiveUnlockDeviceNameListener(
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
index 4ff2e90..66485d3 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java
@@ -156,9 +156,16 @@
}
/**
+ * Returns the title of active unlock only.
+ */
+ public @NonNull String getTitleForActiveUnlockOnly() {
+ return mContext.getString(R.string.security_settings_activeunlock);
+ }
+
+ /**
* Returns the title of the combined biometric settings entity when active unlock is enabled.
*/
- public String getTitleForActiveUnlock() {
+ public @NonNull String getTitleForActiveUnlock() {
final boolean faceAllowed = Utils.hasFaceHardware(mContext);
final boolean fingerprintAllowed = Utils.hasFingerprintHardware(mContext);
return mContext.getString(getTitleRes(faceAllowed, fingerprintAllowed));
@@ -264,6 +271,30 @@
return mContext.getString(getUseBiometricTitleRes(faceAllowed, fingerprintAllowed));
}
+ /**
+ * Returns the summary from content provider.
+ */
+ @Nullable
+ public static String getSummaryFromContentProvider(
+ @NonNull Context context, @NonNull String authority, @NonNull String logTag) {
+ return ActiveUnlockContentListener.getContentFromUri(
+ context, ActiveUnlockContentListener.getUri(authority), logTag,
+ ActiveUnlockSummaryListener.METHOD_NAME,
+ ActiveUnlockSummaryListener.SUMMARY_KEY);
+ }
+
+ /**
+ * Returns the device name from content provider.
+ */
+ @Nullable
+ public static String getDeviceNameFromContentProvider(
+ @NonNull Context context, @NonNull String authority, @NonNull String logTag) {
+ return ActiveUnlockContentListener.getContentFromUri(
+ context, ActiveUnlockContentListener.getUri(authority), logTag,
+ ActiveUnlockDeviceNameListener.METHOD_NAME,
+ ActiveUnlockDeviceNameListener.DEVICE_NAME_KEY);
+ }
+
@StringRes
private static int getUseBiometricTitleRes(
boolean isFaceAllowed, boolean isFingerprintAllowed) {
diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
index bcffe62..38e137b 100644
--- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
+++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java
@@ -21,8 +21,8 @@
/** Listens to summary updates from the content provider and fetches the latest value. */
public class ActiveUnlockSummaryListener {
private static final String TAG = "ActiveUnlockSummaryListener";
- private static final String METHOD_NAME = "getSummary";
- private static final String SUMMARY_KEY = "com.android.settings.summary";
+ static final String METHOD_NAME = "getSummary";
+ static final String SUMMARY_KEY = "com.android.settings.summary";
private final ActiveUnlockContentListener mContentListener;
public ActiveUnlockSummaryListener(
diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
index 1d8b7a1..ed4b713 100644
--- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
+++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java
@@ -66,7 +66,7 @@
@VisibleForTesting
static final int CONFIRM_REQUEST = 2001;
private static final int CHOOSE_LOCK_REQUEST = 2002;
- protected static final int ACTIVE_UNLOCK_REQUEST = 2003;
+ public static final int ACTIVE_UNLOCK_REQUEST = 2003;
@VisibleForTesting
static final int BIOMETRIC_AUTH_REQUEST = 2004;
diff --git a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
index 700b601..a145913 100644
--- a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
+++ b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java
@@ -25,11 +25,11 @@
import com.android.settings.R;
import com.android.settings.core.SliderPreferenceController;
-import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.widget.SliderPreference;
public class NightDisplayIntensityPreferenceController extends SliderPreferenceController {
- private ColorDisplayManager mColorDisplayManager;
+ private final ColorDisplayManager mColorDisplayManager;
public NightDisplayIntensityPreferenceController(Context context, String key) {
super(context, key);
@@ -64,11 +64,11 @@
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- final SeekBarPreference preference = screen.findPreference(getPreferenceKey());
- preference.setContinuousUpdates(true);
+ SliderPreference preference = screen.findPreference(getPreferenceKey());
+ preference.setUpdatesContinuously(true);
preference.setMax(getMax());
preference.setMin(getMin());
- preference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
+ // TODO(b/394828723) add haptic feedback
}
@Override
diff --git a/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceController.java b/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceController.java
new file mode 100644
index 0000000..bb91b3c
--- /dev/null
+++ b/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 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.inputmethod;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.hardware.input.InputSettings;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.SliderPreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+public class MousePointerSpeedPreferenceController extends SliderPreferenceController {
+
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+
+ public MousePointerSpeedPreferenceController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ SeekBarPreference preference = screen.findPreference(getPreferenceKey());
+ preference.setMax(getMax());
+ preference.setMin(getMin());
+ preference.setProgress(getSliderPosition());
+ updateState(preference);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public boolean setSliderPosition(int position) {
+ if (position < getMin() || position > getMax()) {
+ return false;
+ }
+ InputSettings.setPointerSpeed(mContext, position);
+ mMetricsFeatureProvider.action(
+ mContext, SettingsEnums.ACTION_GESTURE_POINTER_SPEED_CHANGED, position);
+ return true;
+ }
+
+ @Override
+ public int getSliderPosition() {
+ return InputSettings.getPointerSpeed(mContext);
+ }
+
+ @Override
+ public int getMin() {
+ return InputSettings.MIN_POINTER_SPEED;
+ }
+
+ @Override
+ public int getMax() {
+ return InputSettings.MAX_POINTER_SPEED;
+ }
+}
diff --git a/src/com/android/settings/localepicker/LocaleDialogFragment.java b/src/com/android/settings/localepicker/LocaleDialogFragment.java
index a3a4b8f..5c7958a 100644
--- a/src/com/android/settings/localepicker/LocaleDialogFragment.java
+++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java
@@ -26,11 +26,6 @@
import android.content.Intent;
import android.os.Bundle;
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 android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
@@ -57,6 +52,7 @@
static final String ARG_DIALOG_TYPE = "arg_dialog_type";
static final String ARG_TARGET_LOCALE = "arg_target_locale";
static final String ARG_SHOW_DIALOG = "arg_show_dialog";
+ static final String ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED = "arg_show_dialog_for_not_translated";
private boolean mShouldKeepDialog;
private OnBackInvokedDispatcher mBackDispatcher;
@@ -185,6 +181,7 @@
private final int mDialogType;
private final LocaleStore.LocaleInfo mLocaleInfo;
private final MetricsFeatureProvider mMetricsFeatureProvider;
+ private final boolean mShowDialogForNotTranslated;
private LocaleListEditor mParent;
@@ -194,6 +191,7 @@
mContext = context;
Bundle arguments = dialogFragment.getArguments();
mDialogType = arguments.getInt(ARG_DIALOG_TYPE);
+ mShowDialogForNotTranslated = arguments.getBoolean(ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED);
mLocaleInfo = (LocaleStore.LocaleInfo) arguments.getSerializable(ARG_TARGET_LOCALE);
mMetricsFeatureProvider =
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
@@ -215,6 +213,7 @@
bundle.putInt(ARG_DIALOG_TYPE, mDialogType);
bundle.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, mLocaleInfo);
intent.putExtras(bundle);
+ intent.putExtra(ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED, mShowDialogForNotTranslated);
mParent.onActivityResult(mDialogType, result, intent);
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CHANGE_LANGUAGE,
changed);
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
index 907fe7b..af8b668 100644
--- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -364,12 +364,25 @@
}
public void notifyListChanged(LocaleStore.LocaleInfo localeInfo) {
- if (!localeInfo.getLocale().equals(mCacheItemList.get(0).getLocale())) {
+ if (listChanged()) {
mFeedItemList = new ArrayList<>(mCacheItemList);
notifyDataSetChanged();
}
}
+ private boolean listChanged() {
+ if (mFeedItemList.size() == mCacheItemList.size()) {
+ for (int i = 0; i < mFeedItemList.size(); i++) {
+ if (!mFeedItemList.get(i).getLocale().equals(mCacheItemList.get(i).getLocale())) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return true;
+ }
+ }
+
public void setCacheItemList() {
mCacheItemList = new ArrayList<>(mFeedItemList);
}
diff --git a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
index a7ebe32..df0af63 100644
--- a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
+++ b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
@@ -151,7 +151,7 @@
}
if (result) {
- mLocaleListEditor.showConfirmDialog(false, mAdapter.getFeedItemList().get(0));
+ mLocaleListEditor.showConfirmDialog(mAdapter.getFeedItemList().get(0), null);
}
return result;
}
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index b1f005a..e2da851 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -44,6 +44,7 @@
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
@@ -235,7 +236,9 @@
localeInfo = mAdapter.getFeedItemList().get(0);
if (resultCode == Activity.RESULT_OK) {
mAdapter.doTheUpdate();
- if (!localeInfo.isTranslated()) {
+ boolean showNotTranslatedDialog = data.getBooleanExtra(
+ LocaleDialogFragment.ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED, true);
+ if (showNotTranslatedDialog && !localeInfo.isTranslated()) {
Bundle args = new Bundle();
args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE,
LocaleDialogFragment.DIALOG_NOT_AVAILABLE_LOCALE);
@@ -428,13 +431,10 @@
// to remove.
mRemoveMode = false;
mShowingRemoveDialog = false;
- LocaleStore.LocaleInfo firstLocale =
- mAdapter.getFeedItemList().get(0);
+ Locale defaultBeforeRemoval = Locale.getDefault();
mAdapter.removeChecked();
- boolean isFirstRemoved =
- firstLocale != mAdapter.getFeedItemList().get(0);
- showConfirmDialog(isFirstRemoved, isFirstRemoved ? firstLocale
- : mAdapter.getFeedItemList().get(0));
+ showConfirmDialog(mAdapter.getFeedItemList().get(0),
+ defaultBeforeRemoval);
setRemoveMode(false);
dialogHelper.getDialog().dismiss();
})
@@ -520,27 +520,73 @@
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP
|| event.getAction() == MotionEvent.ACTION_CANCEL) {
- showConfirmDialog(false, mAdapter.getFeedItemList().get(0));
+ showConfirmDialog(mAdapter.getFeedItemList().get(0), null);
}
return false;
}
- public void showConfirmDialog(boolean isFirstRemoved, LocaleStore.LocaleInfo localeInfo) {
+ protected void showConfirmDialog(LocaleStore.LocaleInfo localeInfo,
+ @Nullable Locale defaultLocaleBeforeRemoval) {
Locale currentSystemLocale = LocalePicker.getLocales().get(0);
if (!localeInfo.getLocale().equals(currentSystemLocale)) {
- final LocaleDialogFragment localeDialogFragment =
- LocaleDialogFragment.newInstance();
- Bundle args = new Bundle();
- args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
- args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE,
- isFirstRemoved ? LocaleStore.getLocaleInfo(currentSystemLocale) : localeInfo);
- localeDialogFragment.setArguments(args);
- localeDialogFragment.show(mFragmentManager, TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT);
+ displayDialogFragment(localeInfo, true);
} else {
- mAdapter.doTheUpdate();
+ if (!localeInfo.isTranslated()) {
+ if (defaultLocaleBeforeRemoval == null) {
+ showDialogDueToDragAndDrop();
+ } else {
+ showDialogDueToRemoval(defaultLocaleBeforeRemoval);
+ }
+ } else {
+ mAdapter.doTheUpdate();
+ }
}
}
+ private void showDialogDueToDragAndDrop() {
+ LocaleStore.LocaleInfo newLocale = mAdapter.getFeedItemList().stream().filter(
+ i -> i.isTranslated()).findFirst().orElse(null);
+ if (newLocale == null) {
+ return;
+ }
+ LocaleStore.LocaleInfo oldLocale = null;
+ final LocaleList localeList = LocalePicker.getLocales();
+ for (int i = 0; i < localeList.size(); i++) {
+ LocaleStore.LocaleInfo temp = LocaleStore.getLocaleInfo(localeList.get(i));
+ if (temp.isTranslated()) {
+ oldLocale = temp;
+ break;
+ }
+ }
+ if (oldLocale != null && !newLocale.getLocale().equals(
+ oldLocale.getLocale())) {
+ displayDialogFragment(newLocale, false);
+ }
+ }
+
+ private void showDialogDueToRemoval(Locale preDefault) {
+ if (preDefault == null) {
+ return;
+ }
+ LocaleStore.LocaleInfo currentDefault = mAdapter.getFeedItemList().stream().filter(
+ i -> i.isTranslated()).findFirst().orElse(null);
+ if (currentDefault != null && !preDefault.equals(currentDefault.getLocale())) {
+ displayDialogFragment(currentDefault, false);
+ }
+ }
+
+ private void displayDialogFragment(LocaleStore.LocaleInfo localeInfo,
+ boolean showDialogForNotTranslated) {
+ final LocaleDialogFragment localeDialogFragment = LocaleDialogFragment.newInstance();
+ Bundle args = new Bundle();
+ args.putBoolean(LocaleDialogFragment.ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED,
+ showDialogForNotTranslated);
+ args.putInt(LocaleDialogFragment.ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
+ args.putSerializable(LocaleDialogFragment.ARG_TARGET_LOCALE, localeInfo);
+ localeDialogFragment.setArguments(args);
+ localeDialogFragment.show(mFragmentManager, TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT);
+ }
+
// Hide the "Remove" menu if there is only one locale in the list, show it otherwise
// This is called when the menu is first created, and then one add / remove locale
private void updateVisibilityOfRemoveMenu() {
diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt
index 46aa19b..7e04f0d 100644
--- a/src/com/android/settings/overlay/FeatureFactory.kt
+++ b/src/com/android/settings/overlay/FeatureFactory.kt
@@ -17,7 +17,7 @@
import android.content.Context
import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider
import com.android.settings.accessibility.AccessibilitySearchFeatureProvider
import com.android.settings.accounts.AccountFeatureProvider
import com.android.settings.applications.ApplicationFeatureProvider
@@ -145,9 +145,9 @@
abstract val accessibilitySearchFeatureProvider: AccessibilitySearchFeatureProvider
/**
- * Retrieves implementation for Accessibility metrics category feature.
+ * Retrieves implementation for Accessibility page id category feature.
*/
- abstract val accessibilityMetricsFeatureProvider: AccessibilityMetricsFeatureProvider
+ abstract val accessibilityPageIdFeatureProvider: AccessibilityPageIdFeatureProvider
/**
* Retrieves implementation for advanced vpn feature.
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
index 08abf2b..4949c3f 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
@@ -22,8 +22,8 @@
import android.os.UserManager
import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider
import com.android.settings.accessibility.AccessibilityFeedbackFeatureProviderImpl
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProviderImpl
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProviderImpl
import com.android.settings.accessibility.AccessibilitySearchFeatureProvider
import com.android.settings.accessibility.AccessibilitySearchFeatureProviderImpl
import com.android.settings.accounts.AccountFeatureProvider
@@ -174,8 +174,8 @@
AccessibilitySearchFeatureProviderImpl()
}
- override val accessibilityMetricsFeatureProvider: AccessibilityMetricsFeatureProvider by lazy {
- AccessibilityMetricsFeatureProviderImpl()
+ override val accessibilityPageIdFeatureProvider: AccessibilityPageIdFeatureProvider by lazy {
+ AccessibilityPageIdFeatureProviderImpl()
}
override val advancedVpnFeatureProvider by lazy { AdvancedVpnFeatureProviderImpl() }
diff --git a/src/com/android/settings/safetycenter/LockScreenSafetySource.java b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
index 00a4c67..61f05f7 100644
--- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java
+++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java
@@ -131,6 +131,7 @@
if (Flags.biometricsOnboardingEducation()) {
FaceSafetySource.onBiometricsChanged(context);
FingerprintSafetySource.onBiometricsChanged(context);
+ WearSafetySource.onBiometricsChanged(context);
} else {
BiometricsSafetySource.onBiometricsChanged(context);
}
diff --git a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
index a49b7e0..4cf40dd 100644
--- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
+++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java
@@ -86,6 +86,9 @@
if (sourceIds.contains(FingerprintSafetySource.SAFETY_SOURCE_ID)) {
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
}
+ if (sourceIds.contains(WearSafetySource.SAFETY_SOURCE_ID)) {
+ WearSafetySource.setSafetySourceData(context, safetyEvent);
+ }
}
private static void refreshAllSafetySources(Context context, SafetyEvent safetyEvent) {
@@ -95,5 +98,6 @@
PrivateSpaceSafetySource.setSafetySourceData(context, safetyEvent);
FaceSafetySource.setSafetySourceData(context, safetyEvent);
FingerprintSafetySource.setSafetySourceData(context, safetyEvent);
+ WearSafetySource.setSafetySourceData(context, safetyEvent);
}
}
diff --git a/src/com/android/settings/safetycenter/WearSafetySource.java b/src/com/android/settings/safetycenter/WearSafetySource.java
new file mode 100644
index 0000000..a345096
--- /dev/null
+++ b/src/com/android/settings/safetycenter/WearSafetySource.java
@@ -0,0 +1,147 @@
+/*
+ * 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.safetycenter;
+
+import static com.android.settings.biometrics.combination.BiometricsSettingsBase.ACTIVE_UNLOCK_REQUEST;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.UserManager;
+import android.safetycenter.SafetyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
+import com.android.settings.flags.Flags;
+
+/** Wear Safety Source for Safety Center. */
+public final class WearSafetySource {
+
+ private static final String TAG = "WearSafetySource";
+ public static final String SAFETY_SOURCE_ID = "AndroidWearUnlock";
+ private static boolean sIsTestingEnv = false;
+ private static String sSummaryForTesting = "";
+ private static boolean sHasEnrolledForTesting;
+
+ private WearSafetySource() {}
+
+ /** Sets test value for summary. */
+ @VisibleForTesting
+ public static void setSummaryForTesting(@NonNull String summary) {
+ sIsTestingEnv = true;
+ sSummaryForTesting = summary;
+ }
+
+ /** Sets test value for hasEnrolled. */
+ @VisibleForTesting
+ public static void setHasEnrolledForTesting(boolean hasEnrolled) {
+ sIsTestingEnv = true;
+ sHasEnrolledForTesting = hasEnrolled;
+ }
+
+ /** Sets biometric safety data for Safety Center. */
+ public static void setSafetySourceData(
+ @NonNull Context context, @NonNull SafetyEvent safetyEvent) {
+ if (!SafetyCenterManagerWrapper.get().isEnabled(context)) {
+ return;
+ }
+ if (!Flags.biometricsOnboardingEducation()) { // this source is effectively turned off
+ sendNullData(context, safetyEvent);
+ return;
+ }
+
+ // Handle private profile case.
+ UserManager userManager = UserManager.get(context);
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && userManager.isPrivateProfile()) {
+ // SC always expects a response from the source if the broadcast has been sent for this
+ // source, therefore, we need to send a null SafetySourceData.
+ sendNullData(context, safetyEvent);
+ return;
+ }
+
+ ActiveUnlockStatusUtils activeUnlockStatusUtils = new ActiveUnlockStatusUtils(context);
+ if (!userManager.isProfile() && activeUnlockStatusUtils.isAvailable()) {
+ boolean hasEnrolled = false;
+ String summary = "";
+
+ if (sIsTestingEnv) {
+ hasEnrolled = sHasEnrolledForTesting;
+ summary = sSummaryForTesting;
+ } else {
+ String authority = new ActiveUnlockStatusUtils(context).getAuthority();
+ hasEnrolled = getHasEnrolledFromContentProvider(context, authority);
+ summary = getSummaryFromContentProvider(context, authority);
+ }
+
+ BiometricSourcesUtils.setBiometricSafetySourceData(
+ SAFETY_SOURCE_ID,
+ context,
+ activeUnlockStatusUtils.getTitleForActiveUnlockOnly(),
+ summary,
+ PendingIntent.getActivity(context, ACTIVE_UNLOCK_REQUEST,
+ activeUnlockStatusUtils.getIntent(),
+ PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT),
+ /* enabled= */ true,
+ hasEnrolled,
+ safetyEvent);
+ return;
+ }
+
+ sendNullData(context, safetyEvent);
+ }
+
+ private static void sendNullData(Context context, SafetyEvent safetyEvent) {
+ SafetyCenterManagerWrapper.get()
+ .setSafetySourceData(
+ context, SAFETY_SOURCE_ID, /* safetySourceData= */ null, safetyEvent);
+ }
+
+ /** Notifies Safety Center of a change in wear biometrics settings. */
+ public static void onBiometricsChanged(@NonNull Context context) {
+ setSafetySourceData(
+ context,
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED)
+ .build());
+ }
+
+ private static boolean getHasEnrolledFromContentProvider(
+ @NonNull Context context, @Nullable String authority) {
+ if (authority == null) {
+ return false;
+ }
+ return ActiveUnlockStatusUtils.getDeviceNameFromContentProvider(context, authority, TAG)
+ != null;
+ }
+
+ private static String getSummaryFromContentProvider(
+ @NonNull Context context, @Nullable String authority) {
+ if (authority == null) {
+ return "";
+ }
+ String summary = ActiveUnlockStatusUtils.getSummaryFromContentProvider(
+ context, authority, TAG);
+ if (summary == null) {
+ return "";
+ }
+ return summary;
+ }
+
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
index 7e16096..9bb3051 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
@@ -91,10 +91,15 @@
}
}.sharedFlow()
- val isAvailableFlow = installerLabelFlow.map { installerLabel ->
- withContext(Dispatchers.IO) {
- !AppUtils.isMainlineModule(packageManager, app.packageName) &&
- installerLabel != null
+ val isAvailableFlow = installerLabelFlow.map() { installerLabel ->
+ // Do not show the install info for the special case of the Play Store app.
+ if (app.packageName == context.getString(R.string.config_mainline_module_update_package)) {
+ false
+ } else {
+ withContext(Dispatchers.IO) {
+ val isMainlineModule = AppUtils.isMainlineModule(packageManager, app.packageName)
+ !isMainlineModule && installerLabel != null
+ }
}
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
index e590a80..6710da9 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java
@@ -22,7 +22,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -458,12 +457,10 @@
setupFragment();
mFragment.setFeedbackManager(
new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
- when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
- verify(mMenu).add(anyInt(), eq(AccessibilitySettings.MENU_ID_SEND_FEEDBACK),
- anyInt(), eq(mContext.getText(R.string.accessibility_send_feedback_title)));
+ verify(mMenu).add(anyInt(), anyInt(), anyInt(), anyInt());
}
@Test
@@ -472,12 +469,10 @@
setupFragment();
mFragment.setFeedbackManager(
new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
- when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
- verify(mMenu, never()).add(anyInt(), eq(AccessibilitySettings.MENU_ID_SEND_FEEDBACK),
- anyInt(), eq(mContext.getText(R.string.accessibility_send_feedback_title)));
+ verify(mMenu, never()).add(anyInt(), anyInt(), anyInt(), anyInt());
}
@Test
@@ -486,8 +481,6 @@
setupFragment();
mFragment.setFeedbackManager(
new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
- when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
- mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
when(mMenuItem.getItemId()).thenReturn(AccessibilitySettings.MENU_ID_SEND_FEEDBACK);
mFragment.onOptionsItemSelected(mMenuItem);
@@ -502,8 +495,6 @@
setupFragment();
mFragment.setFeedbackManager(
new FeedbackManager(mFragment.getActivity(), PACKAGE_NAME, DEFAULT_CATEGORY));
- when(mMenu.add(anyInt(), anyInt(), anyInt(), anyInt())).thenReturn(mMenuItem);
- mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
when(mMenuItem.getItemId()).thenReturn(AccessibilitySettings.MENU_ID_SEND_FEEDBACK);
mFragment.onOptionsItemSelected(mMenuItem);
diff --git a/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java
new file mode 100644
index 0000000..42efdfe
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/MagnificationCursorFollowingModePreferenceControllerTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.provider.Settings;
+import android.provider.Settings.Secure.AccessibilityMagnificationCursorFollowingMode;
+import android.text.TextUtils;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.DialogCreatable;
+import com.android.settings.R;
+import com.android.settings.accessibility.MagnificationCursorFollowingModePreferenceController.ModeInfo;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link MagnificationCursorFollowingModePreferenceController}. */
+@RunWith(RobolectricTestRunner.class)
+public class MagnificationCursorFollowingModePreferenceControllerTest {
+ private static final String PREF_KEY =
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
+
+ @Rule
+ public MockitoRule mocks = MockitoJUnit.rule();
+
+ @Spy
+ private TestDialogHelper mDialogHelper = new TestDialogHelper();
+
+ private PreferenceScreen mScreen;
+ private Context mContext;
+ private MagnificationCursorFollowingModePreferenceController mController;
+ private Preference mModePreference;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat);
+ final PreferenceManager preferenceManager = new PreferenceManager(mContext);
+ mScreen = preferenceManager.createPreferenceScreen(mContext);
+ mModePreference = new Preference(mContext);
+ mModePreference.setKey(PREF_KEY);
+ mScreen.addPreference(mModePreference);
+ mController = new MagnificationCursorFollowingModePreferenceController(mContext, PREF_KEY);
+ mController.setDialogHelper(mDialogHelper);
+ mDialogHelper.setDialogDelegate(mController);
+ showPreferenceOnTheScreen();
+ }
+
+ private void showPreferenceOnTheScreen() {
+ mController.displayPreference(mScreen);
+ }
+
+ @AccessibilityMagnificationCursorFollowingMode
+ private int getCheckedModeFromDialog() {
+ final ListView listView = mController.mModeListView;
+ assertThat(listView).isNotNull();
+
+ final int checkedPosition = listView.getCheckedItemPosition();
+ assertWithMessage("No mode is checked").that(checkedPosition)
+ .isNotEqualTo(AdapterView.INVALID_POSITION);
+
+ final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(checkedPosition);
+ return modeInfo.mMode;
+ }
+
+ private void performItemClickWith(@AccessibilityMagnificationCursorFollowingMode int mode) {
+ final ListView listView = mController.mModeListView;
+ assertThat(listView).isNotNull();
+
+ int modeIndex = AdapterView.NO_ID;
+ for (int i = 0; i < listView.getAdapter().getCount(); i++) {
+ final ModeInfo modeInfo = (ModeInfo) listView.getAdapter().getItem(i);
+ if (modeInfo != null && modeInfo.mMode == mode) {
+ modeIndex = i;
+ break;
+ }
+ }
+ assertWithMessage("The mode could not be found").that(modeIndex)
+ .isNotEqualTo(AdapterView.NO_ID);
+
+ listView.performItemClick(listView.getChildAt(modeIndex), modeIndex, modeIndex);
+ }
+
+ @Test
+ public void clickPreference_defaultMode_selectionIsDefault() {
+ mController.handlePreferenceTreeClick(mModePreference);
+
+ assertThat(getCheckedModeFromDialog()).isEqualTo(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
+ }
+
+ @Test
+ public void clickPreference_nonDefaultMode_selectionIsExpected() {
+ Settings.Secure.putInt(mContext.getContentResolver(), PREF_KEY,
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER);
+
+ mController.handlePreferenceTreeClick(mModePreference);
+
+ assertThat(getCheckedModeFromDialog()).isEqualTo(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER);
+ }
+
+ @Test
+ public void selectItemInDialog_selectionIsExpected() {
+ mController.handlePreferenceTreeClick(mModePreference);
+
+ performItemClickWith(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+
+ assertThat(getCheckedModeFromDialog()).isEqualTo(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+ }
+
+ @Test
+ public void selectItemInDialog_dismissWithoutSave_selectionNotPersists() {
+ mController.handlePreferenceTreeClick(mModePreference);
+
+ performItemClickWith(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+
+ showPreferenceOnTheScreen();
+
+ mController.handlePreferenceTreeClick(mModePreference);
+
+ assertThat(getCheckedModeFromDialog()).isEqualTo(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS);
+ assertThat(TextUtils.equals(mController.getSummary(), mContext.getString(
+ R.string.accessibility_magnification_cursor_following_continuous))).isTrue();
+ }
+
+ @Test
+ public void selectItemInDialog_saveAndDismiss_selectionPersists() {
+ mController.handlePreferenceTreeClick(mModePreference);
+
+ performItemClickWith(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+ mController.onMagnificationCursorFollowingModeDialogPositiveButtonClicked(
+ mDialogHelper.getDialog(), DialogInterface.BUTTON_POSITIVE);
+
+ showPreferenceOnTheScreen();
+
+ mController.handlePreferenceTreeClick(mModePreference);
+
+ assertThat(getCheckedModeFromDialog()).isEqualTo(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE);
+ assertThat(TextUtils.equals(mController.getSummary(), mContext.getString(
+ R.string.accessibility_magnification_cursor_following_edge))).isTrue();
+ }
+
+ private static class TestDialogHelper implements DialogHelper {
+ private DialogCreatable mDialogDelegate;
+ private Dialog mDialog;
+
+ @Override
+ public void showDialog(int dialogId) {
+ mDialog = mDialogDelegate.onCreateDialog(dialogId);
+ }
+
+ public void setDialogDelegate(@NonNull DialogCreatable delegate) {
+ mDialogDelegate = delegate;
+ }
+
+ public Dialog getDialog() {
+ return mDialog;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
index 8f9d2e1..571075c 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
@@ -23,9 +23,12 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -37,10 +40,13 @@
import android.content.pm.PackageManager;
import android.icu.text.CaseMap;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
@@ -66,8 +72,9 @@
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@@ -84,6 +91,8 @@
})
public class ToggleFeaturePreferenceFragmentTest {
@Rule
+ public final MockitoRule mocks = MockitoJUnit.rule();
+ @Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String PLACEHOLDER_PACKAGE_NAME = "com.placeholder.example";
@@ -96,6 +105,7 @@
PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_TILE_CLASS_NAME);
private static final String PLACEHOLDER_TILE_TOOLTIP_CONTENT =
PLACEHOLDER_PACKAGE_NAME + "tooltip_content";
+ private static final String PLACEHOLDER_CATEGORY = "category";
private static final String PLACEHOLDER_DIALOG_TITLE = "title";
private static final String DEFAULT_SUMMARY = "default summary";
private static final String DEFAULT_DESCRIPTION = "default description";
@@ -120,10 +130,13 @@
private ContentResolver mContentResolver;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private Menu mMenu;
+ @Mock
+ private MenuItem mMenuItem;
@Before
public void setUpTestFragment() {
- MockitoAnnotations.initMocks(this);
mShadowAccessibilityManager = Shadow.extract(
mContext.getSystemService(AccessibilityManager.class));
@@ -170,6 +183,61 @@
}
@Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+ public void onCreateOptionsMenu_enableLowVisionGenericFeedback_shouldAddSendFeedbackMenu() {
+ mFragment.setFeedbackManager(
+ new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+
+ mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
+
+ verify(mMenu).add(anyInt(), eq(ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK),
+ anyInt(), eq(R.string.accessibility_send_feedback_title));
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+ public void onCreateOptionsMenu_disableLowVisionGenericFeedback_shouldNotAddSendFeedbackMenu() {
+ mFragment.setFeedbackManager(
+ new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+
+ mFragment.onCreateOptionsMenu(mMenu, /* inflater= */ null);
+
+ verify(mMenu, never()).add(anyInt(),
+ eq(ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK), anyInt(),
+ eq(R.string.accessibility_send_feedback_title));
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+ public void onOptionsItemSelected_enableLowVisionGenericFeedback_shouldStartSendFeedback() {
+ mFragment.setFeedbackManager(
+ new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+ when(mMenuItem.getItemId()).thenReturn(
+ ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK);
+
+ mFragment.onOptionsItemSelected(mMenuItem);
+
+ verify(mActivity).startActivityForResult(
+ argThat(intent -> intent != null
+ && Intent.ACTION_BUG_REPORT.equals(intent.getAction())), anyInt());
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_LOW_VISION_GENERIC_FEEDBACK)
+ public void onOptionsItemSelected_disableLowVisionGenericFeedback_shouldNotStartSendFeedback() {
+ mFragment.setFeedbackManager(
+ new FeedbackManager(mActivity, PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CATEGORY));
+ when(mMenuItem.getItemId()).thenReturn(
+ ToggleFeaturePreferenceFragment.MENU_ID_SEND_FEEDBACK);
+
+ mFragment.onOptionsItemSelected(mMenuItem);
+
+ verify(mActivity, never()).startActivityForResult(
+ argThat(intent -> intent != null
+ && Intent.ACTION_BUG_REPORT.equals(intent.getAction())), anyInt());
+ }
+
+ @Test
public void updateShortcutPreferenceData_assignDefaultValueToVariable() {
mFragment.mComponentName = PLACEHOLDER_COMPONENT_NAME;
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java
index 3c136f0..6407c08 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java
@@ -614,6 +614,24 @@
}
@Test
+ @EnableFlags(com.android.settings.accessibility.Flags
+ .FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG)
+ public void onCreateDialog_setCursorFollowingModeDialogDelegate_invokeDialogDelegate() {
+ ToggleScreenMagnificationPreferenceFragment fragment =
+ mFragController.create(
+ R.id.main_content, /* bundle= */ null).start().resume().get();
+ final DialogCreatable dialogDelegate = mock(DialogCreatable.class, RETURNS_DEEP_STUBS);
+ final int dialogId = DialogEnums.DIALOG_MAGNIFICATION_CURSOR_FOLLOWING_MODE;
+ when(dialogDelegate.getDialogMetricsCategory(anyInt())).thenReturn(dialogId);
+ fragment.setMagnificationCursorFollowingModeDialogDelegate(dialogDelegate);
+
+ fragment.onCreateDialog(dialogId);
+ fragment.getDialogMetricsCategory(dialogId);
+ verify(dialogDelegate).onCreateDialog(dialogId);
+ verify(dialogDelegate).getDialogMetricsCategory(dialogId);
+ }
+
+ @Test
public void getMetricsCategory_returnsCorrectCategory() {
ToggleScreenMagnificationPreferenceFragment fragment =
mFragController.create(
@@ -826,6 +844,7 @@
MagnificationOneFingerPanningPreferenceController.PREF_KEY,
MagnificationAlwaysOnPreferenceController.PREF_KEY,
MagnificationJoystickPreferenceController.PREF_KEY,
+ MagnificationCursorFollowingModePreferenceController.PREF_KEY,
MagnificationFeedbackPreferenceController.PREF_KEY);
final List<SearchIndexableRaw> rawData = ToggleScreenMagnificationPreferenceFragment
@@ -881,7 +900,9 @@
@EnableFlags({
com.android.settings.accessibility.Flags.FLAG_FIX_A11Y_SETTINGS_SEARCH,
Flags.FLAG_ENABLE_MAGNIFICATION_ONE_FINGER_PANNING_GESTURE,
- Flags.FLAG_ENABLE_LOW_VISION_HATS})
+ Flags.FLAG_ENABLE_LOW_VISION_HATS,
+ com.android.settings.accessibility.Flags
+ .FLAG_ENABLE_MAGNIFICATION_CURSOR_FOLLOWING_DIALOG})
public void getNonIndexableKeys_hasShortcutAndAllFeaturesEnabled_allItemsSearchable() {
mShadowAccessibilityManager.setAccessibilityShortcutTargets(
TRIPLETAP, List.of(MAGNIFICATION_CONTROLLER_NAME));
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
index 65c9caf..7149d4f 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
@@ -333,7 +333,7 @@
@RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_FEATURE_DISABLE_INSTALL_UNKNOWN_SOURCES)
@Test
- public void getPreferenceSummary_restrictedGlobally_adminString() {
+ public void getPreferenceSummary_restrictedGloballyByAdmin_adminString() {
final EnforcingAdmin nonAdvancedProtectionEnforcingAdmin = new EnforcingAdmin("test.pkg",
UnknownAuthority.UNKNOWN_AUTHORITY, mUserHandle, new ComponentName("", ""));
@@ -353,7 +353,7 @@
@RequiresFlagsEnabled(Flags.FLAG_AAPM_FEATURE_DISABLE_INSTALL_UNKNOWN_SOURCES)
@Test
- public void getPreferenceSummary_restrictedGlobally_advancedProtectionString() {
+ public void getPreferenceSummary_restrictedGloballyByAdvancedProtection_disabledString() {
final EnforcingAdmin advancedProtectionEnforcingAdmin = new EnforcingAdmin("test.pkg",
new UnknownAuthority(ADVANCED_PROTECTION_SYSTEM_ENTITY), mUserHandle,
new ComponentName("", ""));
@@ -363,12 +363,10 @@
advancedProtectionEnforcingAdmin);
when(mUserManager.hasUserRestrictionForUser(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
mUserHandle)).thenReturn(true);
- when(mContext.getString(
- com.android.settingslib.widget.restricted.R.string.disabled_by_advanced_protection))
- .thenReturn("disabled_by_advanced_protection");
+ when(mContext.getString(com.android.settingslib.R.string.disabled)).thenReturn("disabled");
CharSequence summary = ExternalSourcesDetails.getPreferenceSummary(mContext, mAppEntry);
- assertEquals("disabled_by_advanced_protection", summary.toString());
+ assertEquals("disabled", summary.toString());
}
}
diff --git a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
index 563974d..6da6aa7 100644
--- a/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtilsTest.java
@@ -243,4 +243,11 @@
.isEqualTo(mApplicationContext.getString(
R.string.biometric_settings_use_watch_for));
}
+
+ @Test
+ public void getTitleForActiveUnlockOnly_returnsTile() {
+ assertThat(mActiveUnlockStatusUtils.getTitleForActiveUnlockOnly())
+ .isEqualTo(mApplicationContext.getString(
+ R.string.security_settings_activeunlock));
+ }
}
diff --git a/tests/robotests/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceControllerTest.java
new file mode 100644
index 0000000..82afec2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/inputmethod/MousePointerSpeedPreferenceControllerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.hardware.input.InputSettings;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link MousePointerSpeedPreferenceController} */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+ com.android.settings.testutils.shadow.ShadowSystemSettings.class,
+})
+public class MousePointerSpeedPreferenceControllerTest {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ private static final String PREFERENCE_KEY = "pointer_speed";
+ private static final String SETTING_KEY = Settings.System.POINTER_SPEED;
+
+ private MousePointerSpeedPreferenceController mController;
+ private int mDefaultSpeed;
+ private FakeFeatureFactory mFeatureFactory;
+
+ @Before
+ public void setUp() {
+ Context context = ApplicationProvider.getApplicationContext();
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mController = new MousePointerSpeedPreferenceController(context, PREFERENCE_KEY);
+ mDefaultSpeed = Settings.System.getIntForUser(
+ context.getContentResolver(),
+ SETTING_KEY,
+ InputSettings.DEFAULT_POINTER_SPEED,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Test
+ public void setSliderPosition_speedValue1_shouldReturnTrue() {
+ int inputSpeed = 1;
+
+ boolean result = mController.setSliderPosition(inputSpeed);
+
+ assertThat(result).isTrue();
+ assertThat(mController.getSliderPosition()).isEqualTo(inputSpeed);
+ verify(mFeatureFactory.metricsFeatureProvider).action(
+ any(),
+ eq(SettingsEnums.ACTION_GESTURE_POINTER_SPEED_CHANGED),
+ eq(1));
+ }
+
+ @Test
+ public void setSliderPosition_speedValueOverMaxValue_shouldReturnFalse() {
+ int inputSpeed = InputSettings.MAX_POINTER_SPEED + 1;
+
+ boolean result = mController.setSliderPosition(inputSpeed);
+
+ assertThat(result).isFalse();
+ assertThat(mController.getSliderPosition()).isEqualTo(mDefaultSpeed);
+ }
+
+ @Test
+ public void setSliderPosition_speedValueOverMinValue_shouldReturnFalse() {
+ int inputSpeed = InputSettings.MIN_POINTER_SPEED - 1;
+
+ boolean result = mController.setSliderPosition(inputSpeed);
+
+ assertThat(result).isFalse();
+ assertThat(mController.getSliderPosition()).isEqualTo(mDefaultSpeed);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
index 4272afe..22d39e3 100644
--- a/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/LocaleListEditorTest.java
@@ -36,7 +36,6 @@
import android.app.Dialog;
import android.app.IActivityManager;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -100,6 +99,8 @@
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static final String ARG_DIALOG_TYPE = "arg_dialog_type";
+ private static final String
+ ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED = "arg_show_dialog_for_not_translated";
private static final String TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT = "dialog_confirm_system_default";
private static final String TAG_DIALOG_NOT_AVAILABLE = "dialog_not_available_locale";
private static final String TAG_DIALOG_ADD_SYSTEM_LOCALE = "dialog_add_system_locale";
@@ -123,6 +124,10 @@
@Mock
private LocaleStore.LocaleInfo mLocaleInfo;
@Mock
+ private LocaleStore.LocaleInfo mLocaleInfo1;
+ @Mock
+ private LocaleStore.LocaleInfo mLocaleInfo2;
+ @Mock
private FragmentManager mFragmentManager;
@Mock
private FragmentTransaction mFragmentTransaction;
@@ -270,7 +275,7 @@
public void showConfirmDialog_systemLocaleSelected_shouldShowLocaleChangeDialog()
throws Exception {
//pre-condition
- setUpLocaleConditions();
+ setUpLocaleConditions(true);
final Configuration config = new Configuration();
config.setLocales((LocaleList.forLanguageTags("zh-TW,en-US")));
when(mActivityService.getConfiguration()).thenReturn(config);
@@ -300,6 +305,41 @@
}
@Test
+ public void showConfirmDialog_2ndLocaleSelected_shouldShowLocaleChangeDialog()
+ throws Exception {
+ //pre-condition
+ Locale.setDefault(Locale.forLanguageTag("en-US"));
+ setUpLocaleConditions2();
+ final Configuration config = new Configuration();
+ config.setLocales((LocaleList.forLanguageTags("blo-BJ,en-US,zh-TW")));
+ when(mActivityService.getConfiguration()).thenReturn(config);
+ when(mAdapter.getFeedItemList()).thenReturn(mLocaleList);
+ when(mAdapter.getCheckedCount()).thenReturn(1);
+ when(mAdapter.getItemCount()).thenReturn(3);
+ when(mAdapter.isFirstLocaleChecked()).thenReturn(false);
+ ReflectionHelpers.setField(mLocaleListEditor, "mRemoveMode", true);
+ ReflectionHelpers.setField(mLocaleListEditor, "mShowingRemoveDialog", true);
+
+ //launch the first dialog
+ mLocaleListEditor.showRemoveLocaleWarningDialog();
+
+ final Dialog dialog = ShadowDialog.getLatestDialog();
+
+ assertThat(dialog).isNotNull();
+
+ // click the remove button
+ dialog.findViewById(R.id.button_ok).performClick();
+ ShadowLooper.idleMainLooper();
+
+ assertThat(dialog.isShowing()).isFalse();
+
+ // check the second dialog is showing
+ verify(mFragmentTransaction).add(any(LocaleDialogFragment.class),
+ eq(TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT));
+ }
+
+
+ @Test
public void mayAppendUnicodeTags_appendUnicodeTags_success() {
LocaleStore.LocaleInfo localeInfo = LocaleStore.fromLocale(Locale.forLanguageTag("en-US"));
@@ -315,7 +355,8 @@
Bundle bundle = new Bundle();
bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
mIntent.putExtras(bundle);
- setUpLocaleConditions();
+ mIntent.putExtra(ARG_SHOW_DIALOG_FOR_NOT_TRANSLATED, true);
+ setUpLocaleConditions(false);
mLocaleListEditor.onActivityResult(REQUEST_CONFIRM_SYSTEM_DEFAULT, Activity.RESULT_OK,
mIntent);
@@ -328,7 +369,7 @@
Bundle bundle = new Bundle();
bundle.putInt(ARG_DIALOG_TYPE, DIALOG_CONFIRM_SYSTEM_DEFAULT);
mIntent.putExtras(bundle);
- setUpLocaleConditions();
+ setUpLocaleConditions(true);
mLocaleListEditor.onActivityResult(REQUEST_CONFIRM_SYSTEM_DEFAULT, Activity.RESULT_CANCELED,
mIntent);
@@ -338,7 +379,7 @@
@Test
public void onTouch_dragDifferentLocaleToTop_showConfirmDialog() throws Exception {
MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
- setUpLocaleConditions();
+ setUpLocaleConditions(true);
final Configuration config = new Configuration();
config.setLocales((LocaleList.forLanguageTags("zh-TW,en-US")));
when(mActivityService.getConfiguration()).thenReturn(config);
@@ -352,7 +393,7 @@
@Test
public void onTouch_dragSameLocaleToTop_updateAdapter() throws Exception {
MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
- setUpLocaleConditions();
+ setUpLocaleConditions(true);
final Configuration config = new Configuration();
config.setLocales((LocaleList.forLanguageTags("en-US,zh-TW")));
when(mActivityService.getConfiguration()).thenReturn(config);
@@ -490,12 +531,26 @@
verify(mAdapter).setCheckBoxDescription(any(LocaleDragCell.class), any(), anyBoolean());
}
- private void setUpLocaleConditions() {
+ private void setUpLocaleConditions(boolean isTranslated) {
ShadowActivityManager.setService(mActivityService);
mLocaleList = new ArrayList<>();
mLocaleList.add(mLocaleInfo);
when(mLocaleInfo.getFullNameNative()).thenReturn("English");
when(mLocaleInfo.getLocale()).thenReturn(LocaleList.forLanguageTags("en-US").get(0));
+ when(mLocaleInfo.isTranslated()).thenReturn(isTranslated);
+ when(mAdapter.getFeedItemList()).thenReturn(mLocaleList);
+ }
+
+ private void setUpLocaleConditions2() {
+ ShadowActivityManager.setService(mActivityService);
+ mLocaleList = new ArrayList<>();
+ mLocaleList.add(mLocaleInfo);
+ mLocaleList.add(mLocaleInfo1);
+ mLocaleList.add(mLocaleInfo2);
+ when(mLocaleInfo.getLocale()).thenReturn(Locale.forLanguageTag("blo-BJ"));
+ when(mLocaleInfo.isTranslated()).thenReturn(false);
+ when(mLocaleInfo2.getLocale()).thenReturn(Locale.forLanguageTag("zh-TW"));
+ when(mLocaleInfo2.isTranslated()).thenReturn(true);
when(mAdapter.getFeedItemList()).thenReturn(mLocaleList);
}
}
diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
index c5d4c36..e002de1 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
@@ -20,7 +20,7 @@
import android.content.Context;
import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider;
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider;
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider;
import com.android.settings.accessibility.AccessibilitySearchFeatureProvider;
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
@@ -94,7 +94,7 @@
public WifiTrackerLibProvider wifiTrackerLibProvider;
public SecuritySettingsFeatureProvider securitySettingsFeatureProvider;
public AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider;
- public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider;
+ public AccessibilityPageIdFeatureProvider mAccessibilityPageIdFeatureProvider;
public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider;
public WifiFeatureProvider mWifiFeatureProvider;
public KeyboardSettingsFeatureProvider mKeyboardSettingsFeatureProvider;
@@ -145,7 +145,7 @@
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
- mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class);
+ mAccessibilityPageIdFeatureProvider = mock(AccessibilityPageIdFeatureProvider.class);
mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class);
mWifiFeatureProvider = mock(WifiFeatureProvider.class);
mKeyboardSettingsFeatureProvider = mock(KeyboardSettingsFeatureProvider.class);
@@ -294,8 +294,8 @@
}
@Override
- public AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider() {
- return mAccessibilityMetricsFeatureProvider;
+ public AccessibilityPageIdFeatureProvider getAccessibilityPageIdFeatureProvider() {
+ return mAccessibilityPageIdFeatureProvider;
}
@Override
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
index 6297c62..52ee077 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreferenceTest.kt
@@ -122,6 +122,25 @@
}
@Test
+ fun whenIsPlayStoreApp_notDisplayed() {
+ val playStorePackageName = "com.android.vending"
+ whenever(
+ AppStoreUtil.getInstallerPackageNameAndInstallSourceInfo(
+ any(),
+ eq(playStorePackageName)
+ )
+ )
+ .thenReturn(Pair(INSTALLER_PACKAGE_NAME, INSTALL_SOURCE_INFO))
+ val playStoreApp = ApplicationInfo().apply {
+ packageName = playStorePackageName
+ uid = UID
+ }
+ setContent(playStoreApp)
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
fun whenStoreLinkIsNull_disabled() {
whenever(AppStoreUtil.getAppStoreLink(context, INSTALLER_PACKAGE_NAME, PACKAGE_NAME))
.thenReturn(null)
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 56dd444..7b1bdc0 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -18,7 +18,7 @@
import android.content.Context
import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider
import com.android.settings.accessibility.AccessibilitySearchFeatureProvider
import com.android.settings.accounts.AccountFeatureProvider
import com.android.settings.applications.ApplicationFeatureProvider
@@ -130,7 +130,7 @@
get() = TODO("Not yet implemented")
override val accessibilitySearchFeatureProvider: AccessibilitySearchFeatureProvider
get() = TODO("Not yet implemented")
- override val accessibilityMetricsFeatureProvider: AccessibilityMetricsFeatureProvider
+ override val accessibilityPageIdFeatureProvider: AccessibilityPageIdFeatureProvider
get() = TODO("Not yet implemented")
override val advancedVpnFeatureProvider: AdvancedVpnFeatureProvider
get() = TODO("Not yet implemented")
diff --git a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
index f16113a..6e46d2b 100644
--- a/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/LockScreenSafetySourceTest.java
@@ -527,6 +527,9 @@
verify(mSafetyCenterManagerWrapper)
.setSafetySourceData(
any(), eq(FingerprintSafetySource.SAFETY_SOURCE_ID), any(), any());
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(WearSafetySource.SAFETY_SOURCE_ID), any(), any());
}
@Test
diff --git a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
index e65d041..836247c 100644
--- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
+++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java
@@ -246,6 +246,25 @@
}
@Test
+ public void onReceive_onRefresh_withWearUnlockSourceId_setsWearUnlockData() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+ Intent intent =
+ new Intent()
+ .setAction(ACTION_REFRESH_SAFETY_SOURCES)
+ .putExtra(
+ EXTRA_REFRESH_SAFETY_SOURCE_IDS,
+ new String[] {WearSafetySource.SAFETY_SOURCE_ID})
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_BROADCAST_ID);
+
+ new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ verify(mSafetyCenterManagerWrapper, times(1))
+ .setSafetySourceData(any(), captor.capture(), any(), any());
+
+ assertThat(captor.getValue()).isEqualTo(WearSafetySource.SAFETY_SOURCE_ID);
+ }
+
+ @Test
public void onReceive_onRefresh_withFingerprintUnlockSourceId_setsFingerprintUnlockData() {
when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
Intent intent =
@@ -332,7 +351,7 @@
new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
- verify(mSafetyCenterManagerWrapper, times(5))
+ verify(mSafetyCenterManagerWrapper, times(6))
.setSafetySourceData(any(), captor.capture(), any(), any());
List<String> safetySourceIdList = captor.getAllValues();
@@ -356,6 +375,11 @@
assertThat(
safetySourceIdList.stream()
.anyMatch(
+ id -> id.equals(WearSafetySource.SAFETY_SOURCE_ID)))
+ .isTrue();
+ assertThat(
+ safetySourceIdList.stream()
+ .anyMatch(
id -> id.equals(PrivateSpaceSafetySource.SAFETY_SOURCE_ID)))
.isTrue();
}
diff --git a/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java
new file mode 100644
index 0000000..c0c982d
--- /dev/null
+++ b/tests/unit/src/com/android/settings/safetycenter/WearSafetySourceTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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.safetycenter;
+
+import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.UserHandle;
+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;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceStatus;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.biometrics.activeunlock.ActiveUnlockStatusUtils;
+import com.android.settings.flags.Flags;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.ResourcesUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class WearSafetySourceTest {
+
+ private static final ComponentName COMPONENT_NAME = new ComponentName("package", "class");
+ private static final UserHandle USER_HANDLE = new UserHandle(UserHandle.myUserId());
+ private static final SafetyEvent EVENT_SOURCE_STATE_CHANGED =
+ new SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build();
+ public static final String TARGET = "com.active.unlock.target";
+ public static final String PROVIDER = "com.active.unlock.provider";
+ public static final String TARGET_SETTING = "active_unlock_target";
+ public static final String PROVIDER_SETTING = "active_unlock_provider";
+ public static final String SUMMARY = "Wear Summary";
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private Context mApplicationContext;
+
+ @Mock private PackageManager mPackageManager;
+ @Mock private DevicePolicyManager mDevicePolicyManager;
+ @Mock private FingerprintManager mFingerprintManager;
+ @Mock private LockPatternUtils mLockPatternUtils;
+ @Mock private SafetyCenterManagerWrapper mSafetyCenterManagerWrapper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mApplicationContext = spy(ApplicationProvider.getApplicationContext());
+ when(mApplicationContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
+ when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(USER_HANDLE))
+ .thenReturn(COMPONENT_NAME);
+ when(mApplicationContext.getSystemService(Context.FINGERPRINT_SERVICE))
+ .thenReturn(mFingerprintManager);
+ when(mApplicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
+ .thenReturn(mDevicePolicyManager);
+ FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+ when(featureFactory.securityFeatureProvider.getLockPatternUtils(mApplicationContext))
+ .thenReturn(mLockPatternUtils);
+ doReturn(true).when(mLockPatternUtils).isSecure(anyInt());
+ SafetyCenterManagerWrapper.sInstance = mSafetyCenterManagerWrapper;
+ }
+
+ @After
+ public void tearDown() {
+ SafetyCenterManagerWrapper.sInstance = null;
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetyData_whenSafetyCenterIsDisabled_doesNotSetData() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(false);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper, never())
+ .setSafetySourceData(any(), any(), any(), any());
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_whenSeparateBiometricsFlagOff_setsNullData() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(WearSafetySource.SAFETY_SOURCE_ID), eq(null), any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_whenSafetyCenterIsEnabled_activeUnlockDisabled_setsNullData() {
+ disableActiveUnlock(mApplicationContext);
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(), eq(WearSafetySource.SAFETY_SOURCE_ID), eq(null), any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_setsDataWithCorrectSafetyEvent() {
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(any(), any(), any(), eq(EVENT_SOURCE_STATE_CHANGED));
+ }
+
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_withWearEnabled_whenWearEnrolled_setsData() {
+ enableActiveUnlock(mApplicationContext);
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
+
+ WearSafetySource.setHasEnrolledForTesting(true);
+ WearSafetySource.setSummaryForTesting(SUMMARY);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ assertSafetySourceEnabledDataSet(
+ ResourcesUtils.getResourcesString(mApplicationContext,
+ "security_settings_activeunlock"),
+ SUMMARY);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ public void setSafetySourceData_withWearEnabled_whenWearNotEnrolled_setsData() {
+ enableActiveUnlock(mApplicationContext);
+ when(mSafetyCenterManagerWrapper.isEnabled(mApplicationContext)).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(COMPONENT_NAME)).thenReturn(0);
+
+ WearSafetySource.setHasEnrolledForTesting(false);
+ WearSafetySource.setSummaryForTesting(SUMMARY);
+
+ WearSafetySource.setSafetySourceData(
+ mApplicationContext, EVENT_SOURCE_STATE_CHANGED);
+
+ assertSafetySourceDisabledDataSet(
+ ResourcesUtils.getResourcesString(mApplicationContext,
+ "security_settings_activeunlock"),
+ SUMMARY);
+ }
+
+ private static void disableActiveUnlock(Context context) {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_REMOTE_AUTH,
+ ActiveUnlockStatusUtils.CONFIG_FLAG_NAME,
+ /* value= */ null,
+ /* makeDefault=*/ false);
+ Settings.Secure.putString(context.getContentResolver(), TARGET_SETTING, null);
+ Settings.Secure.putString(context.getContentResolver(), PROVIDER_SETTING, null);
+ }
+
+ private static void enableActiveUnlock(Context context) {
+ Settings.Secure.putString(
+ context.getContentResolver(), TARGET_SETTING, TARGET);
+ Settings.Secure.putString(
+ context.getContentResolver(), PROVIDER_SETTING, PROVIDER);
+
+ PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.applicationInfo = applicationInfo;
+ when(packageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo);
+
+ ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.authority = PROVIDER;
+ providerInfo.applicationInfo = applicationInfo;
+ when(packageManager.resolveContentProvider(anyString(), any())).thenReturn(providerInfo);
+
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_REMOTE_AUTH,
+ ActiveUnlockStatusUtils.CONFIG_FLAG_NAME,
+ "unlock_intent_layout",
+ false /* makeDefault */);
+ }
+
+ private void assertSafetySourceDisabledDataSet(String expectedTitle, String expectedSummary) {
+ ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(WearSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
+ SafetySourceData safetySourceData = captor.getValue();
+ SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+ assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle);
+ assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary);
+ assertThat(safetySourceStatus.isEnabled()).isTrue();
+ assertThat(safetySourceStatus.getSeverityLevel())
+ .isEqualTo(SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED);
+
+ Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent();
+ assertThat(clickIntent).isNotNull();
+ assertThat(clickIntent.getAction()).isEqualTo(TARGET);
+ }
+
+ private void assertSafetySourceEnabledDataSet(
+ String expectedTitle, String expectedSummary) {
+ ArgumentCaptor<SafetySourceData> captor = ArgumentCaptor.forClass(SafetySourceData.class);
+ verify(mSafetyCenterManagerWrapper)
+ .setSafetySourceData(
+ any(),
+ eq(WearSafetySource.SAFETY_SOURCE_ID),
+ captor.capture(),
+ any());
+ SafetySourceData safetySourceData = captor.getValue();
+ SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+ assertThat(safetySourceStatus.getTitle().toString()).isEqualTo(expectedTitle);
+ assertThat(safetySourceStatus.getSummary().toString()).isEqualTo(expectedSummary);
+ assertThat(safetySourceStatus.isEnabled()).isTrue();
+ assertThat(safetySourceStatus.getSeverityLevel())
+ .isEqualTo(SafetySourceData.SEVERITY_LEVEL_INFORMATION);
+ Intent clickIntent = safetySourceStatus.getPendingIntent().getIntent();
+ assertThat(clickIntent).isNotNull();
+ }
+}
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index d77d7a4..eda0aeb 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -20,7 +20,7 @@
import android.content.Context;
import com.android.settings.accessibility.AccessibilityFeedbackFeatureProvider;
-import com.android.settings.accessibility.AccessibilityMetricsFeatureProvider;
+import com.android.settings.accessibility.AccessibilityPageIdFeatureProvider;
import com.android.settings.accessibility.AccessibilitySearchFeatureProvider;
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
@@ -93,7 +93,7 @@
public WifiTrackerLibProvider wifiTrackerLibProvider;
public SecuritySettingsFeatureProvider securitySettingsFeatureProvider;
public AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider;
- public AccessibilityMetricsFeatureProvider mAccessibilityMetricsFeatureProvider;
+ public AccessibilityPageIdFeatureProvider mAccessibilityPageIdFeatureProvider;
public AdvancedVpnFeatureProvider mAdvancedVpnFeatureProvider;
public WifiFeatureProvider mWifiFeatureProvider;
public KeyboardSettingsFeatureProvider mKeyboardSettingsFeatureProvider;
@@ -146,7 +146,7 @@
wifiTrackerLibProvider = mock(WifiTrackerLibProvider.class);
securitySettingsFeatureProvider = mock(SecuritySettingsFeatureProvider.class);
mAccessibilitySearchFeatureProvider = mock(AccessibilitySearchFeatureProvider.class);
- mAccessibilityMetricsFeatureProvider = mock(AccessibilityMetricsFeatureProvider.class);
+ mAccessibilityPageIdFeatureProvider = mock(AccessibilityPageIdFeatureProvider.class);
mAdvancedVpnFeatureProvider = mock(AdvancedVpnFeatureProvider.class);
mWifiFeatureProvider = mock(WifiFeatureProvider.class);
mKeyboardSettingsFeatureProvider = mock(KeyboardSettingsFeatureProvider.class);
@@ -295,8 +295,8 @@
}
@Override
- public AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider() {
- return mAccessibilityMetricsFeatureProvider;
+ public AccessibilityPageIdFeatureProvider getAccessibilityPageIdFeatureProvider() {
+ return mAccessibilityPageIdFeatureProvider;
}
@Override