diff --git a/res/layout/vpn_dialog.xml b/res/layout/vpn_dialog.xml index 062772ee892..fadd2025f14 100644 --- a/res/layout/vpn_dialog.xml +++ b/res/layout/vpn_dialog.xml @@ -13,7 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + + - - + + - - + + + - - + + android:inputType="textPassword"/> + - + android:id="@+id/vpn_proxy_host_layout" + android:hint="@string/proxy_hostname_label" + app:endIconMode="clear_text" + app:helperTextEnabled="true" + app:helperText="@string/proxy_hostname_hint" + app:errorEnabled="true"> + + - - - - - + android:id="@+id/vpn_proxy_port_layout" + android:hint="@string/proxy_port_label" + app:endIconMode="clear_text" + app:helperTextEnabled="true" + app:helperText="@string/proxy_port_hint" + app:errorEnabled="true"> + + @@ -182,18 +212,28 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - + + - - + + android:inputType="textPassword"/> + Watch Unlock + Watch 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. diff --git a/res/xml/mouse_settings.xml b/res/xml/mouse_settings.xml index e4b3f1c8f79..ec1c39d31d2 100644 --- a/res/xml/mouse_settings.xml +++ b/res/xml/mouse_settings.xml @@ -28,11 +28,11 @@ settings:controller="com.android.settings.inputmethod.MousePointerAccelerationPreferenceController" /> + settings:controller="com.android.settings.inputmethod.MousePointerSpeedPreferenceController"/> - { 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 4e9cd92d6c0..347c7693356 100644 --- a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java @@ -228,10 +228,10 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment { 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/AccessibilityFeedbackFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilityFeedbackFeatureProvider.java index 018bd2e2ec1..7d03230ba96 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 @@ import androidx.annotation.Nullable; 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 917c5f64146..23818878327 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 @@ public class AccessibilityFeedbackFeatureProviderImpl implements @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 a9d7c0551d1..698efbeee90 100644 --- a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProvider.java +++ b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProvider.java @@ -21,9 +21,9 @@ import android.content.ComponentName; 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 @@ public interface AccessibilityMetricsFeatureProvider { * @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 0f85f38f571..acd8aab3291 100644 --- a/src/com/android/settings/accessibility/AccessibilityMetricsFeatureProviderImpl.java +++ b/src/com/android/settings/accessibility/AccessibilityPageIdFeatureProviderImpl.java @@ -19,14 +19,16 @@ package com.android.settings.accessibility; 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 8a22d820af9..703277422d6 100644 --- a/src/com/android/settings/accessibility/AccessibilityServicePreference.java +++ b/src/com/android/settings/accessibility/AccessibilityServicePreference.java @@ -123,13 +123,12 @@ public class AccessibilityServicePreference extends RestrictedPreference { 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 57eb4d5fba4..2c8247f959c 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -97,6 +97,7 @@ public class AccessibilitySettings extends DashboardFragment implements 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 class AccessibilitySettings extends DashboardFragment implements 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 @@ public class AccessibilitySettings extends DashboardFragment implements 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 52aefd22d31..dc4baa77d3b 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 @@ public class FeedbackManager { * 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 013fdeeb215..c6995b01af7 100644 --- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java +++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java @@ -31,8 +31,6 @@ import android.os.UserHandle; 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; @@ -59,6 +57,11 @@ public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeature return getArguments().getInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY); } + @Override + public int getFeedbackCategory() { + return getArguments().getInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -115,13 +118,6 @@ public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeature 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 infos = AccessibilityManager.getInstance( diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java index 5c18be8626c..ae6239af562 100644 --- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java +++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java @@ -217,6 +217,7 @@ public class RestrictedPreferenceHelper { 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 06bcdb7c2eb..a11ad466003 100644 --- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java @@ -39,8 +39,6 @@ import android.os.SystemClock; 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 @@ public class ToggleAccessibilityServicePreferenceFragment extends } @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 93672516339..66c32df1798 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -40,6 +40,9 @@ import android.service.quicksettings.TileService; 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; 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 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment // , 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 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment 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; @@ -240,6 +246,24 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment removeActionBarToggleSwitch(); } + @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) { @@ -739,4 +763,28 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment 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. + * + *

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/ToggleScreenReaderPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java index 10813a7e262..eb0c93b755f 100644 --- a/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/ToggleScreenReaderPreferenceFragmentForSetupWizard.java @@ -79,6 +79,12 @@ public class ToggleScreenReaderPreferenceFragmentForSetupWizard return SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SCREEN_READER; } + @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. diff --git a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java index 10796b5d218..14dc0bc1caf 100644 --- a/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java +++ b/src/com/android/settings/accessibility/ToggleSelectToSpeakPreferenceFragmentForSetupWizard.java @@ -79,6 +79,12 @@ public class ToggleSelectToSpeakPreferenceFragmentForSetupWizard return SettingsEnums.SUW_ACCESSIBILITY_TOGGLE_SELECT_TO_SPEAK; } + @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. diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java index 826583df866..ad1f823b48c 100644 --- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java +++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java @@ -95,8 +95,7 @@ public class ExternalSourcesDetails extends AppInfoWithHeader 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/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index ef1970995df..83f23bdcf7a 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -107,7 +107,10 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // intent will include this extra containing a bundle of the form: // "modality" -> consented (boolean). public static final String EXTRA_PARENTAL_CONSENT_STATUS = "consent_status"; - + // Whether the face enrollment should be launched first when there are multiple biometrics + // supported. + public static final String EXTRA_LAUNCH_FACE_ENROLL_FIRST = + "launch_face_enroll_first"; private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials"; private static final String SAVED_STATE_IS_SINGLE_ENROLLING = "is_single_enrolling"; @@ -130,6 +133,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private boolean mIsFingerprintEnrollable = false; private boolean mParentalOptionsRequired = false; private boolean mSkipReturnToParent = false; + private boolean mLaunchFaceEnrollFirst = false; private Bundle mParentalOptions; @Nullable private Long mGkPwHandle; @Nullable private ParentalConsentHelper mParentalConsentHelper; @@ -214,6 +218,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { mParentalOptionsRequired = intent.getBooleanExtra(EXTRA_REQUIRE_PARENTAL_CONSENT, false); mSkipReturnToParent = intent.getBooleanExtra(EXTRA_SKIP_RETURN_TO_PARENT, false); + mLaunchFaceEnrollFirst = intent.getBooleanExtra(EXTRA_LAUNCH_FACE_ENROLL_FIRST, false); // determine what can be enrolled final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); @@ -221,6 +226,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { Log.d(TAG, "parentalOptionsRequired: " + mParentalOptionsRequired + ", skipReturnToParent: " + mSkipReturnToParent + + ", launchFaceEnrollFirst: " + mLaunchFaceEnrollFirst + ", isSetupWizard: " + isSetupWizard + ", isMultiSensor: " + isMultiSensor); @@ -356,7 +362,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } else if (canUseFace || canUseFingerprint) { if (mGkPwHandle == null) { setOrConfirmCredentialsNow(); - } else if (canUseFingerprint && mIsFingerprintEnrollable) { + } else if (canUseFingerprint && mIsFingerprintEnrollable + && !(canUseFace && mIsFaceEnrollable && mLaunchFaceEnrollFirst)) { launchFingerprintOnlyEnroll(); } else if (canUseFace && mIsFaceEnrollable) { launchFaceOnlyEnroll(); @@ -510,7 +517,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { int requestCode, int resultCode, Intent data) { Log.d(TAG, "handleOnActivityResultWhileEnrolling, request = " + requestCode + "" - + ", resultCode = " + resultCode); + + ", resultCode = " + resultCode + ", launchFaceEnrollFirst=" + + mLaunchFaceEnrollFirst); switch (requestCode) { case REQUEST_HANDOFF_PARENT: setResult(RESULT_OK, newResultIntent()); @@ -526,7 +534,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // SetupFingerprintEnrollIntroduction/FingerprintEnrollmentActivity TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH); updateGatekeeperPasswordHandle(data); - if (mIsFingerprintEnrollable) { + if (mIsFingerprintEnrollable + && !(mIsFaceEnrollable && mLaunchFaceEnrollFirst)) { launchFingerprintOnlyEnroll(); } else { launchFaceOnlyEnroll(); @@ -548,7 +557,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } if ((resultCode == BiometricEnrollBase.RESULT_SKIP || resultCode == BiometricEnrollBase.RESULT_FINISHED) - && mIsFaceEnrollable) { + && mIsFaceEnrollable && !mLaunchFaceEnrollFirst) { // Apply forward animation during the transition from // SetupFingerprintEnroll*/FingerprintEnrollmentActivity to // SetupFaceEnrollIntroduction @@ -556,6 +565,9 @@ public class BiometricEnrollActivity extends InstrumentedActivity { mIsPreviousEnrollmentCanceled = resultCode != BiometricEnrollBase.RESULT_FINISHED; launchFaceOnlyEnroll(); + } else if (resultCode == Activity.RESULT_CANCELED && mIsFaceEnrollable + && mLaunchFaceEnrollFirst) { + launchFaceOnlyEnroll(); } else { notifySafetyIssueActionLaunchedIfNeeded(resultCode); finishOrLaunchHandToParent(resultCode); @@ -563,7 +575,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity { break; case REQUEST_SINGLE_ENROLL_FACE: mIsSingleEnrolling = false; - if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable) { + if ((resultCode == BiometricEnrollBase.RESULT_SKIP + || resultCode == BiometricEnrollBase.RESULT_FINISHED) + && mIsFingerprintEnrollable && mLaunchFaceEnrollFirst) { + mIsPreviousEnrollmentCanceled = + resultCode != BiometricEnrollBase.RESULT_FINISHED; + launchFingerprintOnlyEnroll(); + } else if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable + && !mLaunchFaceEnrollFirst) { mIsPreviousEnrollmentCanceled = true; launchFingerprintOnlyEnroll(); } else { diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java index db6abc3c979..21b0fa03f06 100644 --- a/src/com/android/settings/biometrics/BiometricUtils.java +++ b/src/com/android/settings/biometrics/BiometricUtils.java @@ -43,6 +43,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.settings.R; import com.android.settings.SetupWizardUtils; +import com.android.settings.biometrics.face.FaceEnroll; import com.android.settings.biometrics.fingerprint.FingerprintEnroll; import com.android.settings.biometrics.fingerprint.FingerprintEnrollFindSensor; import com.android.settings.biometrics.fingerprint.SetupFingerprintEnrollFindSensor; @@ -282,9 +283,7 @@ public class BiometricUtils { */ public static Intent getFaceIntroIntent(@NonNull Context context, @NonNull Intent activityIntent) { - final Intent intent = new Intent(context, - FeatureFactory.getFeatureFactory().getFaceFeatureProvider() - .getEnrollActivityClassProvider().getNext()); + final Intent intent = new Intent(context, FaceEnroll.class); WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent); return intent; } diff --git a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockContentListener.java index 8cc7d6af331..42029ff89f1 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.os.RemoteException; 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 @@ public class ActiveUnlockContentListener { 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 @@ public class ActiveUnlockContentListener { 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 1badb0f26ec..9e8176294e5 100644 --- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java +++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockDeviceNameListener.java @@ -21,8 +21,8 @@ import android.content.Context; /** 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 4ff2e900ae2..66485d37601 100644 --- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java +++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockStatusUtils.java @@ -155,10 +155,17 @@ public class ActiveUnlockStatusUtils { return BasePreferenceController.CONDITIONALLY_UNAVAILABLE; } + /** + * 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 @@ public class ActiveUnlockStatusUtils { 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 bcffe6297d1..38e137bd379 100644 --- a/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java +++ b/src/com/android/settings/biometrics/activeunlock/ActiveUnlockSummaryListener.java @@ -21,8 +21,8 @@ import android.content.Context; /** 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 1d8b7a10a3c..ed4b713df20 100644 --- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java +++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java @@ -66,7 +66,7 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { @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 700b601a608..a1459134b85 100644 --- a/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java +++ b/src/com/android/settings/display/NightDisplayIntensityPreferenceController.java @@ -25,11 +25,11 @@ import androidx.preference.PreferenceScreen; 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 @@ public class NightDisplayIntensityPreferenceController extends SliderPreferenceC @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 00000000000..bb91b3c73c6 --- /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 a3a4b8fee72..5c7958a29ca 100644 --- a/src/com/android/settings/localepicker/LocaleDialogFragment.java +++ b/src/com/android/settings/localepicker/LocaleDialogFragment.java @@ -26,11 +26,6 @@ import android.content.DialogInterface; 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 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { 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 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { private final int mDialogType; private final LocaleStore.LocaleInfo mLocaleInfo; private final MetricsFeatureProvider mMetricsFeatureProvider; + private final boolean mShowDialogForNotTranslated; private LocaleListEditor mParent; @@ -194,6 +191,7 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { 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 @@ public class LocaleDialogFragment extends InstrumentedDialogFragment { 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 907fe7bd722..af8b6681cae 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -364,12 +364,25 @@ class LocaleDragAndDropAdapter } 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 a7ebe32b841..df0af6392fd 100644 --- a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java +++ b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java @@ -151,7 +151,7 @@ public class LocaleLinearLayoutManager extends LinearLayoutManager { } 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 b1f005a79ab..e2da851f8e5 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -44,6 +44,7 @@ import android.view.ViewGroup; 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 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View 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 @@ public class LocaleListEditor extends RestrictedSettingsFragment implements View // 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 class LocaleListEditor extends RestrictedSettingsFragment implements View 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 46aa19b0d05..7e04f0d4373 100644 --- a/src/com/android/settings/overlay/FeatureFactory.kt +++ b/src/com/android/settings/overlay/FeatureFactory.kt @@ -17,7 +17,7 @@ package com.android.settings.overlay 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 class FeatureFactory { 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 08abf2bd466..4949c3f7f0c 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt @@ -22,8 +22,8 @@ import android.net.VpnManager 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 @@ open class FeatureFactoryImpl : FeatureFactory() { 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 00a4c676a80..61f05f7f02b 100644 --- a/src/com/android/settings/safetycenter/LockScreenSafetySource.java +++ b/src/com/android/settings/safetycenter/LockScreenSafetySource.java @@ -131,6 +131,7 @@ public final class LockScreenSafetySource { 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 a49b7e0f860..4cf40ddbdbe 100644 --- a/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java +++ b/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiver.java @@ -86,6 +86,9 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver { 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 @@ public class SafetySourceBroadcastReceiver extends BroadcastReceiver { 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 00000000000..a345096728b --- /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 7e160960efa..9bb3051efab 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 @@ private class AppInstallerInfoPresenter( } }.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/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java b/tests/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java index eb28dfbe624..267b5bc4c21 100644 --- a/tests/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java +++ b/tests/componenttests/src/com/android/settings/biometrics/BiometricEnrollActivityTest.java @@ -23,6 +23,7 @@ import static androidx.test.espresso.intent.Intents.intended; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra; +import static com.android.settings.biometrics.BiometricEnrollActivity.EXTRA_LAUNCH_FACE_ENROLL_FIRST; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT; @@ -39,6 +40,7 @@ import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; @@ -145,7 +147,7 @@ public class BiometricEnrollActivityTest { assumeTrue(mHasFace || mHasFingerprint); setPin(); - final Intent intent = getIntent(true /* useInternal */); + final Intent intent = getIntent(true /* useInternal */, null); LockPatternChecker.verifyCredential(new LockPatternUtils(mContext), LockscreenCredential.createPin(TEST_PIN), UserHandle.myUserId(), LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, (response, timeoutMs) -> { @@ -162,6 +164,26 @@ public class BiometricEnrollActivityTest { } } + @Test + public void launchWithPinAndPwHandle_confirmsPin_firstEnrollmentIsFace() throws Exception { + assumeTrue(mHasFace && mHasFingerprint); + + setPin(); + final Intent intent = getFaceEnrollFirstIntent(); + LockPatternChecker.verifyCredential(new LockPatternUtils(mContext), + LockscreenCredential.createPin(TEST_PIN), UserHandle.myUserId(), + LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE, (response, timeoutMs) -> { + assertThat(response.containsGatekeeperPasswordHandle()).isTrue(); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, + response.getGatekeeperPasswordHandle()); + }).get(); + + try (ActivityScenario scenario = + ActivityScenario.launch(intent)) { + intended(hasComponent(FaceEnroll.class.getName())); + } + } + @Test public void launchWithStrongBiometricAllowed_doNotEnrollWeak() throws Exception { assumeTrue(mHasFace || mHasFingerprint); @@ -184,13 +206,22 @@ public class BiometricEnrollActivityTest { } private Intent getIntent() { - return getIntent(false /* useInternal */); + return getIntent(false /* useInternal */, null); } - private Intent getIntent(boolean useInternal) { + private Intent getFaceEnrollFirstIntent() { + final Bundle bundle = new Bundle(); + bundle.putBoolean(EXTRA_LAUNCH_FACE_ENROLL_FIRST, true); + return getIntent(true /* useInternal */, bundle); + } + + private Intent getIntent(boolean useInternal, Bundle bundle) { final Intent intent = new Intent(mContext, useInternal ? BiometricEnrollActivity.InternalActivity.class : BiometricEnrollActivity.class); intent.setAction(ACTION_BIOMETRIC_ENROLL); + if (bundle != null && !bundle.isEmpty()) { + intent.putExtras(bundle); + } return intent; } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index e590a80b27d..6710da90788 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 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.never; import static org.mockito.Mockito.verify; @@ -458,12 +457,10 @@ public class AccessibilitySettingsTest { 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 @@ public class AccessibilitySettingsTest { 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 @@ public class AccessibilitySettingsTest { 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 @@ public class AccessibilitySettingsTest { 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/ToggleFeaturePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java index 8f9d2e1fbd0..571075cba31 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.android.internal.accessibility.common.ShortcutConstants.UserSh 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.Intent; 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.Test; 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; @@ -83,6 +90,8 @@ import java.util.Locale; ShadowAccessibilityManager.class }) public class ToggleFeaturePreferenceFragmentTest { + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -96,6 +105,7 @@ public class ToggleFeaturePreferenceFragmentTest { 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 @@ public class ToggleFeaturePreferenceFragmentTest { 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)); @@ -169,6 +182,61 @@ public class ToggleFeaturePreferenceFragmentTest { any(AccessibilitySettingsContentObserver.class)); } + @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/applications/appinfo/ExternalSourcesDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java index 65c9caf4302..7149d4f0c72 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 @@ public class ExternalSourcesDetailsTest { @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 @@ public class ExternalSourcesDetailsTest { @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 @@ public class ExternalSourcesDetailsTest { 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 563974d5287..6da6aa78103 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 @@ public class ActiveUnlockStatusUtilsTest { .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 00000000000..82afec2693a --- /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 4272afe8eba..22d39e37c12 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.Activity; 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 class LocaleListEditorTest { 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 @@ public class LocaleListEditorTest { @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 class LocaleListEditorTest { 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); @@ -299,6 +304,41 @@ public class LocaleListEditorTest { eq(TAG_DIALOG_CONFIRM_SYSTEM_DEFAULT)); } + @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 @@ public class LocaleListEditorTest { 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 @@ public class LocaleListEditorTest { 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 @@ public class LocaleListEditorTest { @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 @@ public class LocaleListEditorTest { @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 @@ public class LocaleListEditorTest { 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 c5d4c36228a..e002de1e391 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 static org.mockito.Mockito.mock; 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 class FakeFeatureFactory extends FeatureFactory { 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 @@ public class FakeFeatureFactory extends FeatureFactory { 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 @@ public class FakeFeatureFactory extends FeatureFactory { } @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 6297c62a015..52ee077c4d2 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 @@ -121,6 +121,25 @@ class AppInstallerInfoPreferenceTest { composeTestRule.onRoot().assertIsNotDisplayed() } + @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)) 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 56dd444b474..7b1bdc0ed9c 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 @@ package com.android.settings.testutils 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 @@ class FakeFeatureFactory : FeatureFactory() { 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 f16113ab13d..6e46d2be551 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 @@ public class LockScreenSafetySourceTest { 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 e65d041e248..836247c38c5 100644 --- a/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/SafetySourceBroadcastReceiverTest.java @@ -245,6 +245,25 @@ public class SafetySourceBroadcastReceiverTest { assertThat(captor.getValue()).isEqualTo(FaceSafetySource.SAFETY_SOURCE_ID); } + @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 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); @@ -332,7 +351,7 @@ public class SafetySourceBroadcastReceiverTest { new SafetySourceBroadcastReceiver().onReceive(mApplicationContext, intent); ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(mSafetyCenterManagerWrapper, times(5)) + verify(mSafetyCenterManagerWrapper, times(6)) .setSafetySourceData(any(), captor.capture(), any(), any()); List safetySourceIdList = captor.getAllValues(); @@ -353,6 +372,11 @@ public class SafetySourceBroadcastReceiverTest { .anyMatch( id -> id.equals(FingerprintSafetySource.SAFETY_SOURCE_ID))) .isTrue(); + assertThat( + safetySourceIdList.stream() + .anyMatch( + id -> id.equals(WearSafetySource.SAFETY_SOURCE_ID))) + .isTrue(); assertThat( safetySourceIdList.stream() .anyMatch( 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 00000000000..c0c982d44fe --- /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 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 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 d77d7a4ff01..eda0aeb934c 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 static org.mockito.Mockito.mock; 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 class FakeFeatureFactory extends FeatureFactory { 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 @@ public class FakeFeatureFactory extends FeatureFactory { 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 @@ public class FakeFeatureFactory extends FeatureFactory { } @Override - public AccessibilityMetricsFeatureProvider getAccessibilityMetricsFeatureProvider() { - return mAccessibilityMetricsFeatureProvider; + public AccessibilityPageIdFeatureProvider getAccessibilityPageIdFeatureProvider() { + return mAccessibilityPageIdFeatureProvider; } @Override