diff --git a/Android.bp b/Android.bp index 8353634e312..48e0d2d2298 100644 --- a/Android.bp +++ b/Android.bp @@ -120,6 +120,10 @@ android_library { "telephony-common", "ims-common", ], + + lint: { + extra_check_modules: ["SettingsLibLintChecker"], + }, } platform_compat_config { diff --git a/res/layout/accessibility_text_reading_preview.xml b/res/layout/accessibility_text_reading_preview.xml index 95ea2e42349..830d9e66778 100644 --- a/res/layout/accessibility_text_reading_preview.xml +++ b/res/layout/accessibility_text_reading_preview.xml @@ -42,7 +42,8 @@ android:id="@+id/preview_pager" android:layout_width="match_parent" android:layout_height="217dp" - android:contentDescription="@string/preview_pager_content_description" /> + android:contentDescription="@string/preview_pager_content_description" + android:nestedScrollingEnabled="true" /> Sort by app name - Last time used + Last used + + never Usage time @@ -5864,12 +5866,16 @@ %1$s - %2$s %1$s %2$s + + %1$s %2$s Battery usage chart Daily battery usage chart Hourly battery usage chart + + Battery level percentage from %1$s to %2$s Battery usage since last full charge diff --git a/src/com/android/settings/accessibility/TextReadingPreviewPager.java b/src/com/android/settings/accessibility/TextReadingPreviewPager.java index 5d431593d06..252d1213465 100644 --- a/src/com/android/settings/accessibility/TextReadingPreviewPager.java +++ b/src/com/android/settings/accessibility/TextReadingPreviewPager.java @@ -18,8 +18,7 @@ package com.android.settings.accessibility; import android.content.Context; import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.ViewGroup; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -29,18 +28,31 @@ import androidx.viewpager.widget.ViewPager; * The view pager is used for displaying screen preview with different size configuration changes. */ public class TextReadingPreviewPager extends ViewPager { + public TextReadingPreviewPager(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - final ViewGroup parent = (ViewGroup) getParent(); - if (parent != null) { - // Avoid conflicting with the nested scroll view. - parent.requestDisallowInterceptTouchEvent(true); - } + public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { + return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0; + } - return super.onInterceptTouchEvent(ev); + @Override + public void onNestedScrollAccepted(View child, View target, int axes) { + super.onNestedScrollAccepted(child, target, axes); + // Allow the nested scrollview inside of the view pager to be scrollable. + getParent().requestDisallowInterceptTouchEvent(true); + } + + @Override + public void onNestedScroll( + View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { + if (dyUnconsumed != 0) { + // Assume dyUnconsumed != 0 means the target has been scrolled to the end vertically. + // We could let the parent to consume the rest of the dyUnconsumed + getParent().requestDisallowInterceptTouchEvent(false); + } + super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); } } diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java index d17fa24fede..c798dff4be7 100644 --- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java +++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollEnrollingView.java @@ -138,7 +138,8 @@ public class UdfpsEnrollEnrollingView extends GlifLayout { displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), scaleFactor, - displayInfo.rotation); + displayInfo.rotation, + udfpsProps.sensorType); mUdfpsEnrollView.setOverlayParams(params); mUdfpsEnrollView.setEnrollHelper(udfpsEnrollHelper); diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java index 2d392ffbe6d..4a2a243d2d8 100644 --- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java +++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollView.java @@ -19,6 +19,7 @@ package com.android.settings.biometrics.fingerprint; import android.content.Context; import android.graphics.Rect; import android.graphics.RectF; +import android.hardware.fingerprint.FingerprintSensorProperties; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; @@ -202,7 +203,9 @@ public class UdfpsEnrollView extends FrameLayout implements UdfpsEnrollHelper.Li } private void onFingerDown() { - mFingerprintDrawable.setShouldSkipDraw(true); + if (mOverlayParams.getSensorType() == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { + mFingerprintDrawable.setShouldSkipDraw(true); + } mFingerprintDrawable.invalidateSelf(); } diff --git a/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java b/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java index 831e83b5ed5..c99cb2ddd57 100644 --- a/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java +++ b/src/com/android/settings/biometrics2/ui/widget/UdfpsEnrollView.java @@ -260,8 +260,8 @@ public class UdfpsEnrollView extends FrameLayout { displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), scaleFactor, - displayInfo.rotation); - + displayInfo.rotation, + mSensorProperties.sensorType); post(() -> { mProgressBarRadius = diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 8ae8edd4be0..51dce26fdf4 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -35,6 +35,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.SettingsActivity; +import com.android.settings.Utils; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.AbstractPreferenceController; @@ -427,13 +428,35 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll R.string.battery_usage_day_and_hour, selectedDayText, selectedHourText); } + @VisibleForTesting + String getBatteryLevelPercentageInfo() { + if (mDailyViewModel == null || mHourlyViewModels == null) { + // No data + return ""; + } + + if (mDailyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL + || mHourlyChartIndex == BatteryChartViewModel.SELECTED_INDEX_ALL) { + return mDailyViewModel.getSlotBatteryLevelText(mDailyChartIndex); + } + + return mHourlyViewModels.get(mDailyChartIndex).getSlotBatteryLevelText(mHourlyChartIndex); + } + private String getAccessibilityAnnounceMessage() { final String slotInformation = getSlotInformation(); - return slotInformation == null - ? mPrefContext.getString( - R.string.battery_usage_breakdown_title_since_last_full_charge) - : mPrefContext.getString( - R.string.battery_usage_breakdown_title_for_slot, slotInformation); + final String slotInformationMessage = + slotInformation == null + ? mPrefContext.getString( + R.string.battery_usage_breakdown_title_since_last_full_charge) + : mPrefContext.getString( + R.string.battery_usage_breakdown_title_for_slot, slotInformation); + final String batteryLevelPercentageMessage = getBatteryLevelPercentageInfo(); + + return mPrefContext.getString( + R.string.battery_usage_time_info_and_battery_level, + slotInformationMessage, + batteryLevelPercentageMessage); } private void animateBatteryChartViewGroup() { @@ -573,7 +596,29 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll return null; } - private final class DailyChartLabelTextGenerator + private abstract class BaseLabelTextGenerator + implements BatteryChartViewModel.LabelTextGenerator { + @Override + public String generateSlotBatteryLevelText(List levels, int index) { + final int fromBatteryLevelIndex = + index == BatteryChartViewModel.SELECTED_INDEX_ALL ? 0 : index; + final int toBatteryLevelIndex = + index == BatteryChartViewModel.SELECTED_INDEX_ALL + ? levels.size() - 1 + : index + 1; + return mPrefContext.getString( + R.string.battery_level_percentage, + generateBatteryLevelText(levels.get(fromBatteryLevelIndex)), + generateBatteryLevelText(levels.get(toBatteryLevelIndex))); + } + + @VisibleForTesting + private static String generateBatteryLevelText(Integer level) { + return Utils.formatPercentage(level); + } + } + + private final class DailyChartLabelTextGenerator extends BaseLabelTextGenerator implements BatteryChartViewModel.LabelTextGenerator { @Override public String generateText(List timestamps, int index) { @@ -588,7 +633,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } } - private final class HourlyChartLabelTextGenerator + private final class HourlyChartLabelTextGenerator extends BaseLabelTextGenerator implements BatteryChartViewModel.LabelTextGenerator { private static final int FULL_CHARGE_BATTERY_LEVEL = 100; diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java index 6ff52a2bc1d..111a5a1c677 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartView.java @@ -784,10 +784,16 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick } final AccessibilityNodeInfo childInfo = new AccessibilityNodeInfo(BatteryChartView.this, index); + final String slotTimeInfo = mViewModel.getFullText(index); + final String batteryLevelInfo = mViewModel.getSlotBatteryLevelText(index); onInitializeAccessibilityNodeInfo(childInfo); childInfo.setClickable(isValidToDraw(mViewModel, index)); - childInfo.setText(mViewModel.getFullText(index)); - childInfo.setContentDescription(mViewModel.getFullText(index)); + childInfo.setText(slotTimeInfo); + childInfo.setContentDescription( + mContext.getString( + R.string.battery_usage_time_info_and_battery_level, + slotTimeInfo, + batteryLevelInfo)); final Rect bounds = new Rect(); getBoundsOnScreen(bounds, true); diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java index 6ec01a4c344..86890d5926e 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java @@ -40,11 +40,14 @@ class BatteryChartViewModel { } interface LabelTextGenerator { - /** Generate the label text. The text may be abbreviated to save space. */ + /** Generates the label text. The text may be abbreviated to save space. */ String generateText(List timestamps, int index); - /** Generate the full text for accessibility. */ + /** Generates the full text for accessibility. */ String generateFullText(List timestamps, int index); + + /** Generates the battery level text of a slot for accessibility.*/ + String generateSlotBatteryLevelText(List levels, int index); } private final List mLevels; @@ -53,6 +56,7 @@ class BatteryChartViewModel { private final LabelTextGenerator mLabelTextGenerator; private final String[] mTexts; private final String[] mFullTexts; + private final String[] mBatteryLevelTexts; private int mSelectedIndex = SELECTED_INDEX_ALL; private int mHighlightSlotIndex = SELECTED_INDEX_INVALID; @@ -75,6 +79,8 @@ class BatteryChartViewModel { mLabelTextGenerator = labelTextGenerator; mTexts = new String[size()]; mFullTexts = new String[size()]; + // Last one for SELECTED_INDEX_ALL + mBatteryLevelTexts = new String[size() + 1]; } public int size() { @@ -99,6 +105,15 @@ class BatteryChartViewModel { return mFullTexts[index]; } + public String getSlotBatteryLevelText(int index) { + final int textIndex = index != SELECTED_INDEX_ALL ? index : size(); + if (mBatteryLevelTexts[textIndex] == null) { + mBatteryLevelTexts[textIndex] = + mLabelTextGenerator.generateSlotBatteryLevelText(mLevels, index); + } + return mBatteryLevelTexts[textIndex]; + } + public AxisLabelPosition axisLabelPosition() { return mAxisLabelPosition; } diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java index 2a299c55ac2..4a760add79e 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java @@ -417,7 +417,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity { // the profile user using verifyTiedProfileChallenge. Biometrics can still be used if // the user is stopped with delayed locking (i.e., with storage unlocked), so the user // state (whether the user is in the RUNNING_UNLOCKED state) should not be relied upon. - return !StorageManager.isUserKeyUnlocked(userId); + return !StorageManager.isCeStorageUnlocked(userId); } return !mUserManager.isUserUnlocked(userId); } diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialUtils.java b/src/com/android/settings/password/ConfirmDeviceCredentialUtils.java index 4778c03f9f8..32e7489c6a6 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialUtils.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialUtils.java @@ -23,6 +23,8 @@ import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.UserInfo; +import android.content.pm.UserProperties; import android.os.RemoteException; import android.os.UserManager; import android.view.View; @@ -68,16 +70,44 @@ public class ConfirmDeviceCredentialUtils { DevicePolicyManager dpm, int userId, boolean isStrongAuth) { if (isStrongAuth) { utils.reportSuccessfulPasswordAttempt(userId); + if (isBiometricUnlockEnabledForPrivateSpace()) { + final UserInfo userInfo = userManager.getUserInfo(userId); + if (userInfo != null) { + if (isProfileThatAlwaysRequiresAuthToDisableQuietMode(userManager, userInfo) + || userInfo.isManagedProfile()) { + // Keyguard is responsible to disable StrongAuth for primary user. Disable + // StrongAuth for profile challenges only here. + utils.userPresent(userId); + } + } + } } else { dpm.reportSuccessfulBiometricAttempt(userId); } - if (userManager.isManagedProfile(userId)) { - // Keyguard is responsible to disable StrongAuth for primary user. Disable StrongAuth - // for work challenge only here. - utils.userPresent(userId); + if (!isBiometricUnlockEnabledForPrivateSpace()) { + if (userManager.isManagedProfile(userId)) { + // Disable StrongAuth for work challenge only here. + utils.userPresent(userId); + } } } + /** + * Returns true if the userInfo passed as the parameter corresponds to a profile that always + * requires auth to disable quiet mode and false otherwise + */ + private static boolean isProfileThatAlwaysRequiresAuthToDisableQuietMode( + UserManager userManager, @NonNull UserInfo userInfo) { + final UserProperties userProperties = + userManager.getUserProperties(userInfo.getUserHandle()); + return userProperties.isAuthAlwaysRequiredToDisableQuietMode() && userInfo.isProfile(); + } + + private static boolean isBiometricUnlockEnabledForPrivateSpace() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace(); + } + /** * Request hiding soft-keyboard before animating away credential UI, in case IME * insets animation get delayed by dismissing animation. diff --git a/src/com/android/settings/spa/development/UsageStatsListModel.kt b/src/com/android/settings/spa/development/UsageStatsListModel.kt index d27796d14c8..bb20da6b304 100644 --- a/src/com/android/settings/spa/development/UsageStatsListModel.kt +++ b/src/com/android/settings/spa/development/UsageStatsListModel.kt @@ -29,6 +29,7 @@ import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord import java.text.DateFormat +import java.time.Duration import java.util.concurrent.TimeUnit import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -77,15 +78,25 @@ class UsageStatsListModel(private val context: Context) : AppListModel String)? { val usageStats = record.usageStats ?: return null - val lastTimeUsed = DateUtils.formatSameDayTime( - usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM - ) - val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed" + val lastTimeUsedLine = + "${context.getString(R.string.last_time_used_label)}: ${usageStats.getLastUsedString()}" val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000) val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime" return { "$lastTimeUsedLine\n$usageTimeLine" } } + private fun UsageStats.getLastUsedString() = when { + lastTimeUsed < Duration.ofDays(1) + .toMillis() -> context.getString(R.string.last_time_used_never) + + else -> DateUtils.formatSameDayTime( + lastTimeUsed, + now, + DateFormat.MEDIUM, + DateFormat.MEDIUM + ) + } + private fun getUsageStats(): Map { val startTime = now - TimeUnit.DAYS.toMillis(5) diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java index 120274d4736..faea3d8ecb1 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsCompanionAppsControllerTest.java @@ -112,6 +112,7 @@ public class BluetoothDetailsCompanionAppsControllerTest extends /* selfManaged */ false, /* notifyOnDeviceNearby */ true, /* revoked */ false, + /* pending */ false, /* timeApprovedMs */ System.currentTimeMillis(), /* lastTimeConnected */ Long.MAX_VALUE, /* systemDataSyncFlags */ -1); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java index bbb022edb3b..6fb021cbdae 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java @@ -384,6 +384,8 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 100% to 66%"); } @Test @@ -393,6 +395,8 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo(null); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 100% to 66%"); } @Test @@ -402,6 +406,8 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mHourlyChartIndex = SELECTED_INDEX_ALL; assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("Sunday"); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 83% to 59%"); } @Test @@ -412,6 +418,8 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.getSlotInformation()) .isEqualTo("10 AM - 12 PM"); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 97% to 95%"); } @Test @@ -422,6 +430,8 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.getSlotInformation()) .isEqualTo("Sunday 4 PM - 6 PM"); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 67% to 65%"); } @Test @@ -432,6 +442,8 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.getSlotInformation()) .isEqualTo("7:01 AM - 8 AM"); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 100% to 99%"); } @Test @@ -441,6 +453,8 @@ public final class BatteryChartPreferenceControllerTest { mBatteryChartPreferenceController.mHourlyChartIndex = 3; assertThat(mBatteryChartPreferenceController.getSlotInformation()).isEqualTo("12 PM - now"); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 95% to 66%"); } @Test @@ -451,6 +465,8 @@ public final class BatteryChartPreferenceControllerTest { assertThat(mBatteryChartPreferenceController.getSlotInformation()) .isEqualTo("7:01 AM - now"); + assertThat(mBatteryChartPreferenceController.getBatteryLevelPercentageInfo()) + .isEqualTo("Battery level percentage from 100% to 66%"); } @Test