From feef134aaa47774a6e1945d938edf796aacb2d92 Mon Sep 17 00:00:00 2001 From: Owner Cleanup Bot Date: Thu, 30 Nov 2023 16:38:41 +0000 Subject: [PATCH 01/19] Remove palanki@google.com from src/com/android/settings/location/OWNERS This suggested change is automatically generated based on group memberships and affiliations. If this change is unnecessary or in error, vote CR -1 and the bot will abandon it. Vote CR +1/2 to approve this change. See the owner's recent activity for context: https://android-review.googlesource.com/q/palanki@google.com To report an issue, file a bug in the Infra>Codereview component. Change-Id: I7bc967cf24e1cdf46991fdfa736d2fd841280d6e --- src/com/android/settings/location/OWNERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/android/settings/location/OWNERS b/src/com/android/settings/location/OWNERS index 5feda773f3a..4509725ac4b 100644 --- a/src/com/android/settings/location/OWNERS +++ b/src/com/android/settings/location/OWNERS @@ -2,9 +2,8 @@ asalo@google.com lifu@google.com mstogaitis@google.com -palanki@google.com sooniln@google.com weiwa@google.com wyattriley@google.com -# Emergency approvers in case the above are not available \ No newline at end of file +# Emergency approvers in case the above are not available From f2c23e50a3068da715f28f22631dc1fe048dd337 Mon Sep 17 00:00:00 2001 From: Owner Cleanup Bot Date: Sun, 11 Feb 2024 17:15:04 +0000 Subject: [PATCH 02/19] Remove jaggies@google.com from src/com/android/settings/biometrics/OWNERS This suggested change is automatically generated based on group memberships and affiliations. If this change is unnecessary or in error, vote CR -1 and the bot will abandon it. Vote CR +1/2 to approve this change. See the owner's recent activity for context: https://android-review.googlesource.com/q/jaggies@google.com To report an issue, file a bug in the Infra>Codereview component. Change-Id: I762403dd58ce196b0bff1a9a0607ce73f3ddcc0e --- src/com/android/settings/biometrics/OWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/android/settings/biometrics/OWNERS b/src/com/android/settings/biometrics/OWNERS index cb0d0341828..1d29873414f 100644 --- a/src/com/android/settings/biometrics/OWNERS +++ b/src/com/android/settings/biometrics/OWNERS @@ -1,6 +1,5 @@ graciecheng@google.com ilyamaty@google.com -jaggies@google.com jbolinger@google.com jeffpu@google.com joshmccloskey@google.com From c03e7ee9d6e66323408e522e8e2fead1a70d867d Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Wed, 5 Feb 2025 17:52:19 +0800 Subject: [PATCH 03/19] Use BatteryOptimizeUtils to add packageName into PowerSaveWhitelistUserApps allowlist, which will force set app into Unrestricted Mode Fix: 372831500 Fix: 393033745 Test: test manually by app "Baidu Cloud"> auto-backup settings > add into battery allowlist. Test: atest RequestIgnoreBatteryOptimizationsTest Flag: EXEMPT for simple fix Change-Id: Ia0b232389b1c11b48724af750721e6af4313deaf --- .../fuelgauge/BatteryOptimizeUtils.java | 9 +- .../RequestIgnoreBatteryOptimizations.java | 45 ++-- ...RequestIgnoreBatteryOptimizationsTest.java | 224 ++++++++++++++++++ 3 files changed, 260 insertions(+), 18 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java index 6c95823853f..8bb526f87b8 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -152,8 +152,8 @@ public class BatteryOptimizeUtils { } /** Sets the {@link OptimizationMode} for associated app. */ - public void setAppUsageState(@OptimizationMode int mode, Action action) { - if (getAppOptimizationMode() == mode) { + public void setAppUsageState(@OptimizationMode int mode, Action action, boolean forceMode) { + if (!forceMode && getAppOptimizationMode() == mode) { Log.w(TAG, "set the same optimization mode for: " + mPackageName); return; } @@ -161,6 +161,11 @@ public class BatteryOptimizeUtils { mContext, mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend, action); } + /** Sets the {@link OptimizationMode} for associated app. */ + public void setAppUsageState(@OptimizationMode int mode, Action action) { + setAppUsageState(mode, action, /* forceMode= */ false); + } + /** Return {@code true} if it is disabled for default optimized mode only. */ public boolean isDisabledForOptimizeModeOnly() { return getForceBatteryOptimizeModeList(mContext).contains(mPackageName) diff --git a/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java b/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java index d948cc0f13a..6cb6442628b 100644 --- a/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java +++ b/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java @@ -16,6 +16,8 @@ package com.android.settings.fuelgauge; +import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED; + import android.Manifest; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; @@ -24,20 +26,25 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.PowerManager; -import android.os.PowerWhitelistManager; +import android.text.TextUtils; import android.util.Log; +import androidx.annotation.VisibleForTesting; + import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; public class RequestIgnoreBatteryOptimizations extends AlertActivity implements DialogInterface.OnClickListener { private static final String TAG = "RequestIgnoreBatteryOptimizations"; private static final boolean DEBUG = false; - private PowerWhitelistManager mPowerWhitelistManager; - private String mPackageName; + @VisibleForTesting + static BatteryOptimizeUtils sTestBatteryOptimizeUtils = null; + + private ApplicationInfo mApplicationInfo; @Override public void onCreate(Bundle savedInstanceState) { @@ -47,8 +54,6 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity android.view.WindowManager.LayoutParams .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); - mPowerWhitelistManager = getSystemService(PowerWhitelistManager.class); - Uri data = getIntent().getData(); if (data == null) { debugLog( @@ -56,17 +61,18 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity finish(); return; } - mPackageName = data.getSchemeSpecificPart(); - if (mPackageName == null) { + final String packageName = data.getSchemeSpecificPart(); + if (TextUtils.isEmpty(packageName)) { debugLog( "No data supplied for IGNORE_BATTERY_OPTIMIZATION_SETTINGS in: " + getIntent()); finish(); return; } + // Package in Unrestricted mode already ignoring the battery optimizations. PowerManager power = getSystemService(PowerManager.class); - if (power.isIgnoringBatteryOptimizations(mPackageName)) { - debugLog("Not should prompt, already ignoring optimizations: " + mPackageName); + if (power.isIgnoringBatteryOptimizations(packageName)) { + debugLog("Not should prompt, already ignoring optimizations: " + packageName); finish(); return; } @@ -74,29 +80,28 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity if (getPackageManager() .checkPermission( Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, - mPackageName) + packageName) != PackageManager.PERMISSION_GRANTED) { debugLog( "Requested package " - + mPackageName + + packageName + " does not hold permission " + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); finish(); return; } - ApplicationInfo ai; try { - ai = getPackageManager().getApplicationInfo(mPackageName, 0); + mApplicationInfo = getPackageManager().getApplicationInfo(packageName, 0); } catch (PackageManager.NameNotFoundException e) { - debugLog("Requested package doesn't exist: " + mPackageName); + debugLog("Requested package doesn't exist: " + packageName); finish(); return; } final AlertController.AlertParams p = mAlertParams; final CharSequence appLabel = - ai.loadSafeLabel( + mApplicationInfo.loadSafeLabel( getPackageManager(), PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, PackageItemInfo.SAFE_LABEL_FLAG_TRIM @@ -114,7 +119,15 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity public void onClick(DialogInterface dialog, int which) { switch (which) { case BUTTON_POSITIVE: - mPowerWhitelistManager.addToWhitelist(mPackageName); + BatteryOptimizeUtils batteryOptimizeUtils = + sTestBatteryOptimizeUtils != null + ? sTestBatteryOptimizeUtils + : new BatteryOptimizeUtils( + getApplicationContext(), + mApplicationInfo.uid, + mApplicationInfo.packageName); + batteryOptimizeUtils.setAppUsageState( + MODE_UNRESTRICTED, Action.APPLY, /* forceMode= */ true); break; case BUTTON_NEGATIVE: break; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java new file mode 100644 index 00000000000..b7ffa38285b --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java @@ -0,0 +1,224 @@ +/* + * 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.fuelgauge; + +import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED; +import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; + +import com.android.settings.testutils.shadow.ShadowUtils; +import com.android.settingslib.fuelgauge.PowerAllowlistBackend; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowUtils.class}) +public class RequestIgnoreBatteryOptimizationsTest { + private static final int UID = 12345; + private static final String PACKAGE_NAME = "com.android.app"; + private static final String UNKNOWN_PACKAGE_NAME = "com.android.unknown"; + private static final String PACKAGE_LABEL = "app"; + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private Context mContext; + private RequestIgnoreBatteryOptimizations mActivity; + private BatteryOptimizeUtils mBatteryOptimizeUtils; + private PowerAllowlistBackend mPowerAllowlistBackend; + + @Mock private PowerManager mMockPowerManager; + @Mock private PackageManager mMockPackageManager; + @Mock private ApplicationInfo mMockApplicationInfo; + @Mock private BatteryUtils mMockBatteryUtils; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mActivity = spy(Robolectric.setupActivity(RequestIgnoreBatteryOptimizations.class)); + mBatteryOptimizeUtils = spy(new BatteryOptimizeUtils(mContext, UID, PACKAGE_NAME)); + mPowerAllowlistBackend = spy(PowerAllowlistBackend.getInstance(mContext)); + mBatteryOptimizeUtils.mPowerAllowListBackend = mPowerAllowlistBackend; + mBatteryOptimizeUtils.mBatteryUtils = mMockBatteryUtils; + RequestIgnoreBatteryOptimizations.sTestBatteryOptimizeUtils = mBatteryOptimizeUtils; + + when(mActivity.getApplicationContext()).thenReturn(mContext); + doReturn(mMockPowerManager).when(mActivity).getSystemService(PowerManager.class); + doReturn(mMockPackageManager).when(mActivity).getPackageManager(); + doReturn(mMockApplicationInfo) + .when(mMockPackageManager) + .getApplicationInfo(PACKAGE_NAME, 0); + doThrow(new PackageManager.NameNotFoundException("")) + .when(mMockPackageManager) + .getApplicationInfo(UNKNOWN_PACKAGE_NAME, 0); + doReturn(PACKAGE_LABEL) + .when(mMockApplicationInfo) + .loadSafeLabel( + mMockPackageManager, + PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, + PackageItemInfo.SAFE_LABEL_FLAG_TRIM + | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE); + + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mMockPackageManager) + .checkPermission( + eq(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS), anyString()); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + PowerAllowlistBackend.resetInstance(); + } + + @Test + public void onCreate_withIntent_shouldNotFinish() { + mActivity.setIntent(createIntent(PACKAGE_NAME)); + + mActivity.onCreate(new Bundle()); + + verify(mActivity, never()).finish(); + } + + @Test + public void onCreate_withNoDataIntent_shouldFinish() { + mActivity.setIntent(new Intent()); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withEmptyPackageName_shouldFinish() { + mActivity.setIntent(createIntent("")); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withPkgAlreadyIgnoreOptimization_shouldFinish() { + mActivity.setIntent(createIntent(PACKAGE_NAME)); + doReturn(true).when(mMockPowerManager).isIgnoringBatteryOptimizations(PACKAGE_NAME); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withPkgWithoutPermission_shouldFinish() { + mActivity.setIntent(createIntent(PACKAGE_NAME)); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mMockPackageManager) + .checkPermission( + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, PACKAGE_NAME); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withPkgNameNotFound_shouldFinish() { + mActivity.setIntent(createIntent(UNKNOWN_PACKAGE_NAME)); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onClick_clickNegativeButton_doNothing() { + mActivity.onClick(null, DialogInterface.BUTTON_NEGATIVE); + + verifyNoInteractions(mBatteryOptimizeUtils); + } + + @Test + public void onClick_clickPositiveButtonWithUnrestrictedMode_addAllowlist() { + when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(MODE_UNRESTRICTED); + + mActivity.onClick(null, DialogInterface.BUTTON_POSITIVE); + + verify(mBatteryOptimizeUtils) + .setAppUsageState( + MODE_UNRESTRICTED, + BatteryOptimizeHistoricalLogEntry.Action.APPLY, + /* forceMode= */ true); + verify(mPowerAllowlistBackend).addApp(PACKAGE_NAME, UID); + verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + } + + @Test + public void onClick_clickPositiveButtonWithRestrictedMode_addAllowlistAndSetStandby() { + when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(MODE_RESTRICTED); + doNothing().when(mMockBatteryUtils).setForceAppStandby(anyInt(), anyString(), anyInt()); + + mActivity.onClick(null, DialogInterface.BUTTON_POSITIVE); + + verify(mBatteryOptimizeUtils) + .setAppUsageState( + MODE_UNRESTRICTED, + BatteryOptimizeHistoricalLogEntry.Action.APPLY, + /* forceMode= */ true); + verify(mPowerAllowlistBackend).addApp(PACKAGE_NAME, UID); + verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + } + + private Intent createIntent(String packageName) { + final Intent intent = new Intent(); + intent.setData(new Uri.Builder().scheme("package").opaquePart(packageName).build()); + return intent; + } +} From 1b6ffcbecbc37ebd6cbbb53fb86e000ee55e17f7 Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Wed, 5 Feb 2025 10:56:30 +0000 Subject: [PATCH 04/19] Avoid explicit reference to 'mobile' in tethering_footer_info The Hotspot & Tethering feature allows sharing the current primary data connection of the device with others using either a Wi-Fi hotspot or USB/Bluetooth/Ethernet tethering. The primary data connection is typically a mobile data connection on a phone, but that is not the case on other form factors, e.g. in the desktop experience, where sharing your Ethernet connection via Wi-Fi (i.e. Ethernet is the 'upstream' technology instead of cellular/mobile) may be a much more common usecase. Given that devices can support Hotspot & Tethering without supporting Telephony, there should be no explicit reference to a "mobile data connection", as it is not accurate. Bug: 393312760 Test: Manual check Flag: EXEMPT strings only change Change-Id: If71134deca4610a2304630f331e3a4fe62f0bc63 --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 80b0e8aad8e..300bdf522d9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3860,7 +3860,7 @@ Ethernet tethering - Use hotspot and tethering to provide internet to other devices through your mobile data connection. Apps can also create a hotspot to share content with nearby devices. + Use hotspot and tethering to provide internet to other devices through your data connection. Apps can also create a hotspot to share content with nearby devices. Use hotspot and tethering to provide internet to other devices through your Wi\u2011Fi or mobile data connection. Apps can also create a hotspot to share content with nearby devices. From 6f4f66e5ed41e05a748a7520df679ffcb999251f Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 6 Feb 2025 05:42:16 +0000 Subject: [PATCH 05/19] Support customization for biometrics SafeSourceIssue Notify issue action launch when enrollment process is canceled Bug: 370940762 Test: atest FaceEnrollTest FingerprintEnrollTest Flag: com.android.settings.flags.biometrics_onboarding_education Change-Id: I91e2dcf44ee8cfd3e7c74d05f51a1ef30ea6f8b2 --- .../biometrics/BiometricEnrollActivity.java | 18 +++++++++++++++ .../CombinedBiometricStatusUtils.java | 7 ++++++ .../settings/biometrics/face/FaceEnroll.kt | 21 ++++++++++++++++-- .../fingerprint/FingerprintEnroll.kt | 22 +++++++++++++++++-- .../biometrics/face/FaceEnrollTest.kt | 1 - .../fingerprint/FingerprintEnrollTest.kt | 1 - 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index ec215670d0a..ef1970995df 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -22,6 +22,7 @@ import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED; import static com.android.settings.biometrics.BiometricEnrollBase.BIOMETRIC_AUTH_REQUEST; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED; +import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED; import static com.google.android.setupdesign.transition.TransitionHelper.TRANSITION_FADE_THROUGH; @@ -53,6 +54,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; import com.android.settings.SetupWizardUtils; import com.android.settings.Utils; +import com.android.settings.biometrics.combination.CombinedBiometricStatusUtils; import com.android.settings.core.InstrumentedActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ChooseLockGeneric; @@ -131,6 +133,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private Bundle mParentalOptions; @Nullable private Long mGkPwHandle; @Nullable private ParentalConsentHelper mParentalConsentHelper; + private boolean mIsPreviousEnrollmentCanceled = false; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -531,6 +534,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } else { Log.d(TAG, "Unknown result for set/choose lock: " + resultCode); setResult(resultCode, newResultIntent()); + notifySafetyIssueActionLaunchedIfNeeded(resultCode); finish(); } break; @@ -549,16 +553,21 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // SetupFingerprintEnroll*/FingerprintEnrollmentActivity to // SetupFaceEnrollIntroduction TransitionHelper.applyForwardTransition(this, TRANSITION_FADE_THROUGH); + mIsPreviousEnrollmentCanceled = + resultCode != BiometricEnrollBase.RESULT_FINISHED; launchFaceOnlyEnroll(); } else { + notifySafetyIssueActionLaunchedIfNeeded(resultCode); finishOrLaunchHandToParent(resultCode); } break; case REQUEST_SINGLE_ENROLL_FACE: mIsSingleEnrolling = false; if (resultCode == Activity.RESULT_CANCELED && mIsFingerprintEnrollable) { + mIsPreviousEnrollmentCanceled = true; launchFingerprintOnlyEnroll(); } else { + notifySafetyIssueActionLaunchedIfNeeded(resultCode); finishOrLaunchHandToParent(resultCode); } break; @@ -742,6 +751,15 @@ public class BiometricEnrollActivity extends InstrumentedActivity { startActivityForResult(intent, REQUEST_HANDOFF_PARENT); } + private void notifySafetyIssueActionLaunchedIfNeeded(int resultCode) { + if (getIntent().getBooleanExtra( + CombinedBiometricStatusUtils.EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE, false) + && (resultCode != RESULT_FINISHED || mIsPreviousEnrollmentCanceled)) { + FeatureFactory.getFeatureFactory().getBiometricsFeatureProvider() + .notifySafetyIssueActionLaunched(); + } + } + @Override public int getMetricsCategory() { return SettingsEnums.BIOMETRIC_ENROLL_ACTIVITY; diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java index 92b6d6042e4..6956faa2289 100644 --- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java +++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusUtils.java @@ -36,6 +36,13 @@ import com.android.settingslib.utils.StringUtil; */ public class CombinedBiometricStatusUtils { + /** + * An intent extra indicates that the enrollment process is launched from biometric + * SafetySourceIssue action. + */ + public static final String EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE = + "launch_from_safety_source_issue"; + private final int mUserId; private final Context mContext; @Nullable diff --git a/src/com/android/settings/biometrics/face/FaceEnroll.kt b/src/com/android/settings/biometrics/face/FaceEnroll.kt index 0a3dae5d0a2..2ed628d30ed 100644 --- a/src/com/android/settings/biometrics/face/FaceEnroll.kt +++ b/src/com/android/settings/biometrics/face/FaceEnroll.kt @@ -16,10 +16,13 @@ package com.android.settings.biometrics.face +import android.app.ComponentCaller import android.content.Intent import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity +import com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED +import com.android.settings.biometrics.combination.CombinedBiometricStatusUtils import com.android.settings.overlay.FeatureFactory.Companion.featureFactory @@ -46,9 +49,23 @@ class FaceEnroll: AppCompatActivity() { */ Log.d("FaceEnroll", "forward to $nextActivityClass") val nextIntent = Intent(this, nextActivityClass) - nextIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) nextIntent.putExtras(intent) - startActivity(nextIntent) + startActivityForResult(nextIntent, 0) + } + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent?, + caller: ComponentCaller + ) { + super.onActivityResult(requestCode, resultCode, data, caller) + if (intent.getBooleanExtra( + CombinedBiometricStatusUtils.EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE, false) + && resultCode != RESULT_FINISHED) { + featureFactory.biometricsFeatureProvider.notifySafetyIssueActionLaunched() + } + setResult(resultCode, data) finish() } } \ No newline at end of file diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnroll.kt b/src/com/android/settings/biometrics/fingerprint/FingerprintEnroll.kt index 6439fe6922c..795be132e64 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnroll.kt +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnroll.kt @@ -16,10 +16,13 @@ package com.android.settings.biometrics.fingerprint +import android.app.ComponentCaller import android.content.Intent import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity +import com.android.settings.biometrics.BiometricEnrollBase +import com.android.settings.biometrics.combination.CombinedBiometricStatusUtils import com.android.settings.overlay.FeatureFactory.Companion.featureFactory /** @@ -69,9 +72,24 @@ open class FingerprintEnroll: AppCompatActivity() { */ Log.d("FingerprintEnroll", "forward to $nextActivityClass") val nextIntent = Intent(this, nextActivityClass) - nextIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) nextIntent.putExtras(intent) - startActivity(nextIntent) + startActivityForResult(nextIntent, 0) + } + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent?, + caller: ComponentCaller + ) { + super.onActivityResult(requestCode, resultCode, data, caller) + if (intent.getBooleanExtra( + CombinedBiometricStatusUtils.EXTRA_LAUNCH_FROM_SAFETY_SOURCE_ISSUE, false) + && resultCode != BiometricEnrollBase.RESULT_FINISHED + ) { + featureFactory.biometricsFeatureProvider.notifySafetyIssueActionLaunched() + } + setResult(resultCode, data) finish() } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt index e600061b316..f4786388f50 100644 --- a/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt +++ b/tests/robotests/src/com/android/settings/biometrics/face/FaceEnrollTest.kt @@ -68,7 +68,6 @@ class FaceEnrollTest { currentActivityInstance : FaceEnroll, nextActivityClass: Class ) { - Truth.assertThat(currentActivityInstance.isFinishing).isTrue() val nextActivityIntent = Shadows.shadowOf(currentActivityInstance).nextStartedActivity assertThat(nextActivityIntent.component!!.className).isEqualTo(nextActivityClass.name) assertThat(nextActivityIntent.extras!!.size()).isEqualTo(1) diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollTest.kt b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollTest.kt index 0bfa9f31220..b7d3d41af12 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollTest.kt +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollTest.kt @@ -95,7 +95,6 @@ class FingerprintEnrollTest { currentActivityInstance : FingerprintEnroll, nextActivityClass: Class ) { - assertThat(currentActivityInstance.isFinishing).isTrue() val nextActivityIntent = Shadows.shadowOf(currentActivityInstance).nextStartedActivity assertThat(nextActivityIntent.component!!.className).isEqualTo(nextActivityClass.name) assertThat(nextActivityIntent.extras!!.size()).isEqualTo(1) From ba7738cba034e1cfc8a83eb0c7e08aebb1ff3719 Mon Sep 17 00:00:00 2001 From: Yuhan Yang Date: Wed, 5 Feb 2025 10:01:45 +0000 Subject: [PATCH 06/19] Add mouse scrolling category title Add a scrolling category title in mouse settings page to match the new design. Mock: go/al-peripherals-ux page 3. Screenshot: go/screenshot-8mtcgnjkabci8qt Bug: 383555305 Bug: 384795606 Test: Local DUT with aconfig flags enabled Flag: com.android.hardware.input.mouse_scrolling_acceleration Change-Id: I3aa734a9e588c41dc9d48c40d779544074654a0a --- res/values/strings.xml | 2 ++ res/xml/mouse_settings.xml | 65 ++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 483eb130658..0c6241283e9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4721,6 +4721,8 @@ Content moves up when you scroll down + + Scrolling Controlled scrolling diff --git a/res/xml/mouse_settings.xml b/res/xml/mouse_settings.xml index 35279ec35ed..e4b3f1c8f79 100644 --- a/res/xml/mouse_settings.xml +++ b/res/xml/mouse_settings.xml @@ -20,37 +20,27 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/mouse_settings"> - - - - - - + + - + - + + + + + + + From 916643c8fe6e258c8e4127eab04f1a4c6c73ae95 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 7 Feb 2025 06:06:51 +0000 Subject: [PATCH 07/19] [Catalyst] Implement metrics/tags for "Mobile data" Bug: 394002861 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: I9990c8295b26b4b4ed85db41179a8c93d07f48cd --- .../android/settings/network/MobileDataPreference.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/network/MobileDataPreference.kt b/src/com/android/settings/network/MobileDataPreference.kt index 80f58e0c704..6cb3da259cf 100644 --- a/src/com/android/settings/network/MobileDataPreference.kt +++ b/src/com/android/settings/network/MobileDataPreference.kt @@ -17,9 +17,12 @@ package com.android.settings.network import android.Manifest +import android.app.settings.SettingsEnums.ACTION_MOBILE_DATA import android.content.Context import android.telephony.SubscriptionManager +import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.R +import com.android.settings.contract.KEY_MOBILE_DATA import com.android.settings.network.telephony.MobileDataRepository import com.android.settings.network.telephony.SubscriptionRepository import com.android.settingslib.datastore.KeyValueStore @@ -38,8 +41,14 @@ class MobileDataPreference : R.string.mobile_data_settings_title, R.string.mobile_data_settings_summary, ), + PreferenceActionMetricsProvider, PreferenceAvailabilityProvider { + override val preferenceActionMetrics: Int + get() = ACTION_MOBILE_DATA + + override fun tags(context: Context) = arrayOf(KEY_MOBILE_DATA) + override fun isAvailable(context: Context) = SubscriptionRepository(context).getSelectableSubscriptionInfoList().any { it.simSlotIndex > -1 @@ -59,7 +68,7 @@ class MobileDataPreference : override fun getWritePermissions(context: Context) = Permissions.allOf( // TelephonyManager.setDataEnabledForReason - Manifest.permission.MODIFY_PHONE_STATE, + Manifest.permission.MODIFY_PHONE_STATE ) override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = From dcc13e91f3a240ba3aa75012166f3c29b8891c44 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 7 Feb 2025 06:48:09 +0000 Subject: [PATCH 08/19] [Catalyst] Implement metrics/tags for "Wi-Fi Calling" Bug: 394002861 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: I5cd0141639415eee0d230187beb056692cd7694e --- .../wifi/calling/WifiCallingMainSwitchPreference.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt b/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt index 8765547a957..ccf89d0192a 100644 --- a/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt +++ b/src/com/android/settings/wifi/calling/WifiCallingMainSwitchPreference.kt @@ -19,12 +19,15 @@ package com.android.settings.wifi.calling import android.Manifest.permission.MODIFY_PHONE_STATE import android.Manifest.permission.READ_PRECISE_PHONE_STATE import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE +import android.app.settings.SettingsEnums.ACTION_WIFI_CALLING import android.content.Context import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.ims.ImsMmTelManager import android.util.Log +import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.R +import com.android.settings.contract.KEY_WIFI_CALLING import com.android.settings.network.ims.WifiCallingQueryImsState import com.android.settings.network.telephony.wificalling.WifiCallingRepository import com.android.settings.widget.SettingsMainSwitchPreference @@ -47,7 +50,10 @@ import kotlinx.coroutines.runBlocking * TODO(b/372732219): apply metadata to UI */ class WifiCallingMainSwitchPreference(private val subId: Int) : - BooleanValuePreference, BooleanValuePreferenceBinding, PreferenceAvailabilityProvider { + BooleanValuePreference, + BooleanValuePreferenceBinding, + PreferenceActionMetricsProvider, + PreferenceAvailabilityProvider { override val key: String get() = KEY @@ -55,6 +61,11 @@ class WifiCallingMainSwitchPreference(private val subId: Int) : override val title: Int get() = R.string.wifi_calling_main_switch_title + override val preferenceActionMetrics: Int + get() = ACTION_WIFI_CALLING + + override fun tags(context: Context) = arrayOf(KEY_WIFI_CALLING) + override fun isEnabled(context: Context) = context.isCallStateIdle(subId) && WifiCallingQueryImsState(context, subId).isAllowUserControl From 476dfa913114e6fc448e6b7177c9f74b22515b57 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 7 Feb 2025 07:17:55 +0000 Subject: [PATCH 09/19] [Catalyst] Implement metrics/tags for "Wi-Fi" NO_IFTTT=Catalyst only Bug: 394002861 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: I5c271aca902d74eadb7f8d462c967a31472d0c9f --- src/com/android/settings/wifi/WifiSwitchPreference.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/com/android/settings/wifi/WifiSwitchPreference.kt b/src/com/android/settings/wifi/WifiSwitchPreference.kt index 2b2b3443f60..53445c5701a 100644 --- a/src/com/android/settings/wifi/WifiSwitchPreference.kt +++ b/src/com/android/settings/wifi/WifiSwitchPreference.kt @@ -17,6 +17,7 @@ package com.android.settings.wifi import android.Manifest +import android.app.settings.SettingsEnums.ACTION_WIFI import android.app.settings.SettingsEnums.ACTION_WIFI_OFF import android.app.settings.SettingsEnums.ACTION_WIFI_ON import android.content.BroadcastReceiver @@ -29,8 +30,10 @@ import android.provider.Settings import android.widget.Toast import androidx.preference.Preference import androidx.preference.Preference.OnPreferenceChangeListener +import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R +import com.android.settings.contract.KEY_WIFI import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn import com.android.settings.network.SatelliteWarningDialogActivity import com.android.settings.overlay.FeatureFactory.Companion.featureFactory @@ -54,6 +57,7 @@ import com.android.settingslib.preference.SwitchPreferenceBinding class WifiSwitchPreference : SwitchPreference(KEY, R.string.wifi), SwitchPreferenceBinding, + PreferenceActionMetricsProvider, OnPreferenceChangeListener, PreferenceLifecycleProvider, PreferenceRestrictionMixin { @@ -61,6 +65,11 @@ class WifiSwitchPreference : override val keywords: Int get() = R.string.keywords_wifi + override val preferenceActionMetrics: Int + get() = ACTION_WIFI + + override fun tags(context: Context) = arrayOf(KEY_WIFI) + override fun isEnabled(context: Context) = super.isEnabled(context) override val restrictionKeys From 763d2e3b6c97da789cdd3eae0c8c3717697ba761 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 7 Feb 2025 07:46:15 +0000 Subject: [PATCH 10/19] [Catalyst] Implement metrics/tags for "Battery Saver" NO_IFTTT=Catalyst only Bug: 394002861 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: I1adb1795a566275989db4ff1fbed180531af4d89 --- .../fuelgauge/batterysaver/BatterySaverPreference.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt index 3e5cee9f132..c98b7ba54a8 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverPreference.kt @@ -16,9 +16,12 @@ package com.android.settings.fuelgauge.batterysaver import android.Manifest +import android.app.settings.SettingsEnums.ACTION_BATTERY_SAVER import android.content.Context import android.os.PowerManager +import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.R +import com.android.settings.contract.KEY_BATTERY_SAVER import com.android.settings.fuelgauge.BatterySaverReceiver import com.android.settings.fuelgauge.BatterySaverReceiver.BatterySaverListener import com.android.settingslib.datastore.AbstractKeyedDataObservable @@ -40,7 +43,13 @@ import kotlinx.coroutines.launch // LINT.IfChange class BatterySaverPreference : - MainSwitchPreference(KEY, R.string.battery_saver_master_switch_title) { + MainSwitchPreference(KEY, R.string.battery_saver_master_switch_title), + PreferenceActionMetricsProvider { + + override val preferenceActionMetrics: Int + get() = ACTION_BATTERY_SAVER + + override fun tags(context: Context) = arrayOf(KEY_BATTERY_SAVER) override fun storage(context: Context) = BatterySaverStore(context) From bf3f9a0bda51310902437d657d7136ede055f287 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 7 Feb 2025 05:50:14 +0000 Subject: [PATCH 11/19] Fixed incorrect screen lock settings category title Bug: 394999568 Test: manual - Go to Screen lock settings page and check the title Flag: com.android.settings.flags.biometrics_onboarding_education Change-Id: I51352a43297e226cd1a72bd9edfe1211f02a4191 --- res/values/strings.xml | 3 +++ res/xml/security_settings_picker.xml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c6f7909d8ec..faebc7f6ce4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1671,6 +1671,9 @@ If you forget this lock, ask your IT admin to reset it + + Screen lock settings + Screen lock options diff --git a/res/xml/security_settings_picker.xml b/res/xml/security_settings_picker.xml index aa77b4fe179..2536264b5fe 100644 --- a/res/xml/security_settings_picker.xml +++ b/res/xml/security_settings_picker.xml @@ -69,7 +69,7 @@ From ea89c3ebfca8f909186696e87417fdbd8e043dc8 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 7 Feb 2025 08:50:35 +0000 Subject: [PATCH 12/19] [Catalyst] Implement metrics/tags for "Adaptive brightness" NO_IFTTT=Catalyst only Bug: 394002861 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: Ida22810a8e3335eeff3632a989885fa7b6da433d --- src/com/android/settings/display/AutoBrightnessScreen.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/com/android/settings/display/AutoBrightnessScreen.kt b/src/com/android/settings/display/AutoBrightnessScreen.kt index b594b558192..7018f81d767 100644 --- a/src/com/android/settings/display/AutoBrightnessScreen.kt +++ b/src/com/android/settings/display/AutoBrightnessScreen.kt @@ -15,6 +15,7 @@ */ package com.android.settings.display +import android.app.settings.SettingsEnums.ACTION_ADAPTIVE_BRIGHTNESS import android.content.Context import android.os.UserManager import android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE @@ -22,8 +23,10 @@ import android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC import android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL import androidx.preference.Preference import androidx.preference.PreferenceScreen +import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R +import com.android.settings.contract.KEY_ADAPTIVE_BRIGHTNESS import com.android.settings.flags.Flags import com.android.settingslib.PrimarySwitchPreferenceBinding import com.android.settingslib.datastore.AbstractKeyedDataObservable @@ -47,6 +50,7 @@ class AutoBrightnessScreen : PreferenceScreenCreator, PreferenceScreenBinding, // binding for screen page PrimarySwitchPreferenceBinding, // binding for screen entry point widget + PreferenceActionMetricsProvider, PreferenceAvailabilityProvider, PreferenceRestrictionMixin, BooleanValuePreference { @@ -56,6 +60,11 @@ class AutoBrightnessScreen : override val title: Int get() = R.string.auto_brightness_title + override val preferenceActionMetrics: Int + get() = ACTION_ADAPTIVE_BRIGHTNESS + + override fun tags(context: Context) = arrayOf(KEY_ADAPTIVE_BRIGHTNESS) + override fun isFlagEnabled(context: Context) = Flags.catalystScreenBrightnessMode() override fun fragmentClass() = AutoBrightnessSettings::class.java From d5915b775a9ba0866d86f0de9d31482bf14bc791 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 7 Feb 2025 09:03:44 +0000 Subject: [PATCH 13/19] [Catalyst] Implement metrics/tags for "Screen attention" NO_IFTTT=Catalyst only Bug: 394002861 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: I1ed0d0113d7efac4c6cfa9b95ea130b8edd04407 --- .../android/settings/display/AdaptiveSleepPreference.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/com/android/settings/display/AdaptiveSleepPreference.kt b/src/com/android/settings/display/AdaptiveSleepPreference.kt index 45b05ec3471..e9efc2f2c37 100644 --- a/src/com/android/settings/display/AdaptiveSleepPreference.kt +++ b/src/com/android/settings/display/AdaptiveSleepPreference.kt @@ -16,6 +16,7 @@ package com.android.settings.display +import android.app.settings.SettingsEnums.ACTION_SCREEN_ATTENTION_CHANGED import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -26,8 +27,10 @@ import android.hardware.SensorPrivacyManager.Sensors.CAMERA import android.os.PowerManager import android.os.UserManager import android.provider.Settings +import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R +import com.android.settings.contract.KEY_SCREEN_ATTENTION import com.android.settingslib.RestrictedSwitchPreference import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.KeyedObservableDelegate @@ -46,6 +49,7 @@ import com.android.settingslib.preference.SwitchPreferenceBinding class AdaptiveSleepPreference : BooleanValuePreference, SwitchPreferenceBinding, + PreferenceActionMetricsProvider, PreferenceLifecycleProvider, PreferenceBindingPlaceholder, // not needed once controller class is cleaned up PreferenceAvailabilityProvider, @@ -63,6 +67,11 @@ class AdaptiveSleepPreference : override val summary: Int get() = R.string.adaptive_sleep_description + override val preferenceActionMetrics: Int + get() = ACTION_SCREEN_ATTENTION_CHANGED + + override fun tags(context: Context) = arrayOf(KEY_SCREEN_ATTENTION) + override fun isIndexable(context: Context) = false override fun isEnabled(context: Context) = From 1995fb420d1279616d7e260d6180732bad1f4eca Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Fri, 7 Feb 2025 09:16:56 +0000 Subject: [PATCH 14/19] [Catalyst] Implement metrics/tags for "Bluetooth" NO_IFTTT=Catalyst only Bug: 394002861 Flag: com.android.settings.flags.catalyst Test: devtool Change-Id: I793f81bc5195ae83927acd19f5da3e96a09ba430 --- .../settings/connecteddevice/BluetoothPreference.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/connecteddevice/BluetoothPreference.kt b/src/com/android/settings/connecteddevice/BluetoothPreference.kt index 8c12024ffc3..6ff41a815a0 100644 --- a/src/com/android/settings/connecteddevice/BluetoothPreference.kt +++ b/src/com/android/settings/connecteddevice/BluetoothPreference.kt @@ -18,6 +18,7 @@ package com.android.settings.connecteddevice import android.Manifest import android.annotation.SuppressLint +import android.app.settings.SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE import android.bluetooth.BluetoothAdapter import android.content.BroadcastReceiver import android.content.Context @@ -27,8 +28,10 @@ import android.os.UserManager import android.provider.Settings import android.widget.Toast import androidx.preference.Preference +import com.android.settings.PreferenceActionMetricsProvider import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R +import com.android.settings.contract.KEY_BLUETOOTH import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn import com.android.settings.network.SatelliteWarningDialogActivity import com.android.settings.widget.MainSwitchBarMetadata @@ -43,7 +46,10 @@ import com.android.settingslib.metadata.SensitivityLevel @SuppressLint("MissingPermission") class BluetoothPreference(private val bluetoothDataStore: BluetoothDataStore) : - MainSwitchBarMetadata, PreferenceRestrictionMixin, Preference.OnPreferenceChangeListener { + MainSwitchBarMetadata, + PreferenceActionMetricsProvider, + PreferenceRestrictionMixin, + Preference.OnPreferenceChangeListener { override val key get() = KEY @@ -51,6 +57,11 @@ class BluetoothPreference(private val bluetoothDataStore: BluetoothDataStore) : override val title get() = R.string.bluetooth_main_switch_title + override val preferenceActionMetrics: Int + get() = ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE + + override fun tags(context: Context) = arrayOf(KEY_BLUETOOTH) + override val restrictionKeys: Array get() = arrayOf(UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_CONFIG_BLUETOOTH) From 8397f1056ba8d948f9b383c7bf1c6675e0725740 Mon Sep 17 00:00:00 2001 From: Sunny Shao Date: Thu, 6 Feb 2025 13:31:21 +0000 Subject: [PATCH 15/19] [Catalyst] Introduce the Tags for Get/Set APIs NO_IFTTT=Catalyst only Test: atest ExternalSettingsProviderTest Bug: 394002861 Flag: EXEMPT bugfix Change-Id: I630a9f5ffbaaeed16e13674efa507f3a7b681839 --- .../settings/contract/SettingsContract.kt | 39 +++++++++++++++++++ .../AmbientDisplayAlwaysOnPreference.kt | 3 +- .../settings/slices/CustomSliceRegistry.java | 8 ++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/com/android/settings/contract/SettingsContract.kt b/src/com/android/settings/contract/SettingsContract.kt index d3798daa582..99db4973b71 100644 --- a/src/com/android/settings/contract/SettingsContract.kt +++ b/src/com/android/settings/contract/SettingsContract.kt @@ -47,3 +47,42 @@ const val KEY_ADAPTIVE_BRIGHTNESS = "auto_brightness" /** Contract key for the "Screen attention" setting. */ const val KEY_SCREEN_ATTENTION = "screen_attention" + +/** Contract key for the "Use adaptive connectivity" setting. */ +const val KEY_ADAPTIVE_CONNECTIVITY = "adaptive_connectivity" + +/** Contract key for the "WiFi hotspot" setting. */ +const val KEY_WIFI_HOTSPOT = "enable_wifi_ap" + +/** Contract key for the "Battery Gauge Slider" setting. */ +const val KEY_BATTERY_LEVEL = "battery_level" + +/** Contract key for the "Battery Percentage" setting. */ +const val KEY_BATTERY_PERCENTAGE = "battery_percentage" + +/** Contract key for the "Brightness level" setting. */ +const val KEY_BRIGHTNESS_LEVEL = "brightness_level" + +/** Contract key for the "Smooth display" setting. */ +const val KEY_SMOOTH_DISPLAY = "smooth_display" + +/** Contract key for the "Dark theme" setting. */ +const val KEY_DARK_THEME = "dark_theme" + +/** Contract key for the "Always show time and info" setting. */ +const val KEY_AMBIENT_DISPLAY_ALWAYS_ON = "ambient_display_always_on" + +/** Contract key for the "Use vibration & haptics" setting. */ +const val KEY_VIBRATION_HAPTICS = "vibration_haptics" + +/** Contract key for the "Media volume" setting. */ +const val KEY_MEDIA_VOLUME = "media_volume" + +/** Contract key for the "Call volume" setting. */ +const val KEY_CALL_VOLUME = "call_volume" + +/** Contract key for the "Ring volume" setting. */ +const val KEY_RING_VOLUME = "separate_ring_volume" + +/** Contract key for the "Remove animation" setting. */ +const val KEY_REMOVE_ANIMATION = "remove_animation" diff --git a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt index 3cc608bec91..453593fd5c0 100644 --- a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt +++ b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreference.kt @@ -24,6 +24,7 @@ import android.os.UserManager import android.provider.Settings.Secure.DOZE_ALWAYS_ON import com.android.settings.PreferenceRestrictionMixin import com.android.settings.R +import com.android.settings.contract.KEY_AMBIENT_DISPLAY_ALWAYS_ON import com.android.settings.display.AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime import com.android.settingslib.datastore.AbstractKeyedDataObservable import com.android.settingslib.datastore.HandlerExecutor @@ -123,7 +124,7 @@ class AmbientDisplayAlwaysOnPreference : } companion object { - const val KEY = "ambient_display_always_on" + const val KEY = KEY_AMBIENT_DISPLAY_ALWAYS_ON private const val PROP_AWARE_AVAILABLE = "ro.vendor.aware_available" } } diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index 41f189276dd..38ca5cde95e 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -171,7 +171,7 @@ public class CustomSliceRegistry { .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) - .appendPath("call_volume") + .appendPath(SettingsContractKt.KEY_CALL_VOLUME) .build(); /** * Full {@link Uri} for the Media Volume Slice. @@ -180,7 +180,7 @@ public class CustomSliceRegistry { .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) - .appendPath("media_volume") + .appendPath(SettingsContractKt.KEY_MEDIA_VOLUME) .build(); /** @@ -190,7 +190,7 @@ public class CustomSliceRegistry { .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) - .appendPath("separate_ring_volume") + .appendPath(SettingsContractKt.KEY_RING_VOLUME) .build(); /** @@ -268,7 +268,7 @@ public class CustomSliceRegistry { .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) - .appendPath("dark_theme") + .appendPath(SettingsContractKt.KEY_DARK_THEME) .build(); /** From eeec7d0d66d3c31308cc8de1f8538decf3e093b6 Mon Sep 17 00:00:00 2001 From: Matthew DeVore Date: Wed, 5 Feb 2025 21:17:45 +0000 Subject: [PATCH 16/19] Run body of applyTopology for all non-noop drags In onBlockTouchUp, if DisplayTopology.rearrange happened to revert the change made by the drag so that it matched the before-drag layout, the blocks would not be moved, so the block would be in the dragged position but not the normalized position. This will happen when rearrange has a bug or is otherwise optimizing the layout, which is dependent on the implementation of rearrange. The test field mTimesReceivedSameTopology has been replaced with one that represents an observable positive operation: mTimesRefreshedBlocks, the validation of which has been added to some existing tests. Flag: com.android.settings.flags.display_topology_pane_in_display_list Test: move display so that rearrange reverts the change, then exit and re-enter the external display fragment, and verify it matches the state when left Test: DisplayTopologyPreferenceTest Bug: b/394361999 Bug: b/394355269 Change-Id: Ic3028747d283db77f144831352b7687fe2706391 --- .../display/DisplayTopology.kt | 27 +++++++++++-- .../display/DisplayTopologyPreferenceTest.kt | 40 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt index c30d98a0c2b..a3c710c59db 100644 --- a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt +++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt @@ -334,12 +334,15 @@ class DisplayTopologyPreference(context : Context) * @param displayHeight height of display being dragged in actual (not View) coordinates * @param dragOffsetX difference between event rawX coordinate and X of the display in the pane * @param dragOffsetY difference between event rawY coordinate and Y of the display in the pane + * @param didMove true if we have detected the user intentionally wanted to drag rather than + * just click */ private data class BlockDrag( val stationaryDisps : List>, val display: DisplayBlock, val displayId: Int, val displayWidth: Float, val displayHeight: Float, - val dragOffsetX: Float, val dragOffsetY: Float) + val dragOffsetX: Float, val dragOffsetY: Float, + var didMove: Boolean = false) private var mTopologyInfo : TopologyInfo? = null private var mDrag : BlockDrag? = null @@ -369,7 +372,7 @@ class DisplayTopologyPreference(context : Context) applyTopology(topology) } - @VisibleForTesting var mTimesReceivedSameTopology = 0 + @VisibleForTesting var mTimesRefreshedBlocks = 0 private fun applyTopology(topology: DisplayTopology) { mTopologyHint.text = context.getString(R.string.external_display_topology_hint) @@ -386,7 +389,6 @@ class DisplayTopologyPreference(context : Context) oldBounds.zip(newBounds).all { (old, new) -> old.first == new.first && sameDisplayPosition(old.second, new.second) }) { - mTimesReceivedSameTopology++ return } @@ -438,6 +440,7 @@ class DisplayTopologyPreference(context : Context) } } mPaneContent.removeViews(newBounds.size, recycleableBlocks.size) + mTimesRefreshedBlocks++ mTopologyInfo = TopologyInfo(topology, scaling, newBounds) @@ -481,6 +484,7 @@ class DisplayTopologyPreference(context : Context) val snapRect = clampPosition(drag.stationaryDisps.map { it.second }, dispDragRect) drag.display.place(topology.scaling.displayToPaneCoor(snapRect.left, snapRect.top)) + drag.didMove = true return true } @@ -491,6 +495,15 @@ class DisplayTopologyPreference(context : Context) mPaneContent.requestDisallowInterceptTouchEvent(false) drag.display.setHighlighted(false) + mDrag = null + if (!drag.didMove) { + // If no move event occurred, ignore the drag completely. + // TODO(b/352648432): Responding to a single move event no matter how small may be too + // sensitive. It is easy to slide by a small amount just by force of pressing down the + // mouse button. Keep an eye on this. + return true + } + val newCoor = topology.scaling.paneToDisplayCoor( drag.display.x, drag.display.y) val newTopology = topology.topology.copy() @@ -499,9 +512,15 @@ class DisplayTopologyPreference(context : Context) val arr = hashMapOf(*newPositions.toTypedArray()) newTopology.rearrange(arr) + + // Setting mTopologyInfo to null forces applyTopology to skip the no-op drag check. This is + // necessary because we don't know if newTopology.rearrange has mutated the topology away + // from what the user has dragged into position. + mTopologyInfo = null + applyTopology(newTopology) + injector.displayTopology = newTopology - refreshPane() return true } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt index 5dcb26514ae..8b7f64ccd55 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt +++ b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt @@ -186,6 +186,8 @@ class DisplayTopologyPreferenceTest { fun dragDisplayDownward() { val (leftBlock, _) = setupTwoDisplays() + preference.mTimesRefreshedBlocks = 0 + val downEvent = MotionEventBuilder.newBuilder() .setPointer(0f, 0f) .setAction(MotionEvent.ACTION_DOWN) @@ -208,12 +210,16 @@ class DisplayTopologyPreferenceTest { val child = rootChildren[0] assertThat(child.position).isEqualTo(POSITION_LEFT) assertThat(child.offset).isWithin(1f).of(82f) + + assertThat(preference.mTimesRefreshedBlocks).isEqualTo(1) } @Test fun dragRootDisplayToNewSide() { val (leftBlock, rightBlock) = setupTwoDisplays() + preference.mTimesRefreshedBlocks = 0 + val downEvent = MotionEventBuilder.newBuilder() .setAction(MotionEvent.ACTION_DOWN) .setPointer(0f, 0f) @@ -251,6 +257,32 @@ class DisplayTopologyPreferenceTest { assertThat(paneChildren[0].x) .isWithin(1f) .of(paneChildren[1].x) + + assertThat(preference.mTimesRefreshedBlocks).isEqualTo(1) + } + + @Test + fun noRefreshForUnmovingDrag() { + val (leftBlock, rightBlock) = setupTwoDisplays() + + preference.mTimesRefreshedBlocks = 0 + + val downEvent = MotionEventBuilder.newBuilder() + .setAction(MotionEvent.ACTION_DOWN) + .setPointer(0f, 0f) + .build() + + val upEvent = MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_UP).build() + + rightBlock.dispatchTouchEvent(downEvent) + rightBlock.dispatchTouchEvent(upEvent) + + // After drag, the original views should still be present. + val paneChildren = getPaneChildren() + assertThat(paneChildren.indexOf(leftBlock)).isNotEqualTo(-1) + assertThat(paneChildren.indexOf(rightBlock)).isNotEqualTo(-1) + + assertThat(preference.mTimesRefreshedBlocks).isEqualTo(0) } @Test @@ -269,13 +301,15 @@ class DisplayTopologyPreferenceTest { @Test fun applyNewTopologyViaListenerUpdate() { setupTwoDisplays() + + preference.mTimesRefreshedBlocks = 0 val newTopology = injector.topology!!.copy() newTopology.addDisplay(/* displayId= */ 8008, /* width= */ 300f, /* height= */ 320f) injector.topology = newTopology injector.topologyListener!!.accept(newTopology) - assertThat(preference.mTimesReceivedSameTopology).isEqualTo(0) + assertThat(preference.mTimesRefreshedBlocks).isEqualTo(1) val paneChildren = getPaneChildren() assertThat(paneChildren).hasSize(3) @@ -293,11 +327,11 @@ class DisplayTopologyPreferenceTest { injector.topology = twoDisplayTopology(POSITION_TOP, /* offset= */ 12.0f) preparePane() - assertThat(preference.mTimesReceivedSameTopology).isEqualTo(0) + preference.mTimesRefreshedBlocks = 0 injector.topology = twoDisplayTopology(POSITION_TOP, /* offset= */ 12.1f) injector.topologyListener!!.accept(injector.topology!!) - assertThat(preference.mTimesReceivedSameTopology).isEqualTo(1) + assertThat(preference.mTimesRefreshedBlocks).isEqualTo(0) } @Test From 7630d396a64d9297029d1ccd8185751fbd12ed91 Mon Sep 17 00:00:00 2001 From: Yuhan Yang Date: Tue, 4 Feb 2025 07:33:49 +0000 Subject: [PATCH 17/19] Add Autoclick cursor area size settings dialog Replace Autoclick sliderbar with an alert dialog and radio group to match the new UI requirement. Screenshot: - Click area column: go/screenshot-5AfdF7FGghsdiXk - Alert dialog: go/screenshot-4dgtojcc6lvjtyy Bug: 390460480 Test: ToggleAutoclickCustomSeekbarControllerTest Flag: com.android.server.accessibility.enable_autoclick_indicator Change-Id: Ic79f8833e69dde8d8eb3e945e5acd551d2ee9ff5 --- .../dialog_autoclick_cursor_area_size.xml | 78 ++++++++++ res/values/strings.xml | 27 +++- res/values/styles.xml | 8 + res/xml/accessibility_autoclick_settings.xml | 9 +- ...ggleAutoclickCursorAreaSizeController.java | 102 ++++++++----- ...AutoclickCursorAreaSizeControllerTest.java | 137 +++++++----------- 6 files changed, 226 insertions(+), 135 deletions(-) create mode 100644 res/layout/dialog_autoclick_cursor_area_size.xml diff --git a/res/layout/dialog_autoclick_cursor_area_size.xml b/res/layout/dialog_autoclick_cursor_area_size.xml new file mode 100644 index 00000000000..8f517bae29f --- /dev/null +++ b/res/layout/dialog_autoclick_cursor_area_size.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 483eb130658..d8a71b1455d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5621,12 +5621,27 @@ Longer Auto click time - - - Cursor area size - - - Adjust the autoclick ring indicator area size + + + Click area + + + The area where the cursor can move freely without canceling the click once the countdown starts + + + Extra large + + + Large + + + Default + + + Small + + + Extra small Ignore minor cursor movement diff --git a/res/values/styles.xml b/res/values/styles.xml index 29b4aa65739..3bc436829ce 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -710,6 +710,14 @@ 14sp + +