Snap for 9654363 from 3a445a3ea1 to udc-release
Change-Id: Id00abc2eb74f7a188733b3b78219a564ed890083
This commit is contained in:
104
res/layout/udfps_enroll_enrolling_v2.xml
Normal file
104
res/layout/udfps_enroll_enrolling_v2.xml
Normal file
@@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2023 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/suw_lift"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sud_layout_icon"
|
||||
style="@style/SudGlifIcon"
|
||||
android:layout_marginStart="48dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="68dp"
|
||||
android:scaleType="fitStart"
|
||||
android:src="@drawable/ic_lock" />
|
||||
|
||||
<TextView
|
||||
style="@style/SudGlifHeaderTitle"
|
||||
android:id="@+id/suc_layout_title"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="138dp"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginEnd="24dp"/>
|
||||
|
||||
<TextView
|
||||
style="@style/SudDescription.Glif"
|
||||
android:id="@+id/sud_layout_subtitle"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="206dp"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginEnd="24dp"/>
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:id="@+id/illustration_lottie"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="294dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="17dp"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true"
|
||||
app:lottie_speed=".85" />
|
||||
|
||||
<com.android.settings.biometrics2.ui.widget.UdfpsEnrollView
|
||||
android:id="@+id/udfps_animation_view"
|
||||
android:layout_width="218.42dp"
|
||||
android:layout_height="216dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="553dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/udfps_enroll_animation_fp_progress_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- Fingerprint -->
|
||||
<ImageView
|
||||
android:id="@+id/udfps_enroll_animation_fp_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</com.android.settings.biometrics2.ui.widget.UdfpsEnrollView>
|
||||
|
||||
<Button
|
||||
style="@style/SudGlifButton.Secondary"
|
||||
android:id="@+id/skip_btn"
|
||||
android:text="@string/security_settings_fingerprint_enroll_enrolling_skip"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginBottom="26dp"
|
||||
android:layout_marginStart="66dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -5350,6 +5350,8 @@
|
||||
<string name="battery_usage_spinner_breakdown_by_apps">Breakdown by apps</string>
|
||||
<!-- [CHAR_LIMIT=NONE] The spinner item text in the battery usage breakdown. -->
|
||||
<string name="battery_usage_spinner_breakdown_by_system">Breakdown by system</string>
|
||||
<!-- [CHAR_LIMIT=NONE] Less than some percentage, e.g. < 1% -->
|
||||
<string name="battery_usage_less_than_percent">< <xliff:g id="percentage">%1$s</xliff:g></string>
|
||||
<!-- Process Stats strings -->
|
||||
<skip />
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
package com.android.settings.accessibility;
|
||||
|
||||
import android.content.Context;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
@@ -94,19 +93,11 @@ public class MagnificationGesturesPreferenceController extends TogglePreferenceC
|
||||
}
|
||||
|
||||
static void populateMagnificationGesturesPreferenceExtras(Bundle extras, Context context) {
|
||||
extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
|
||||
// TODO(b/270481978): It seems not necessary to put EXTRA_TITLE_RES.
|
||||
extras.putInt(AccessibilitySettings.EXTRA_TITLE_RES,
|
||||
R.string.accessibility_screen_magnification_gestures_title);
|
||||
|
||||
String intro = context.getString(R.string.accessibility_screen_magnification_intro_text);
|
||||
extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro);
|
||||
|
||||
String summary = context.getString(R.string.accessibility_screen_magnification_summary);
|
||||
final Object[] numberArguments = {1, 2, 3, 4, 5};
|
||||
summary = MessageFormat.format(summary, numberArguments);
|
||||
extras.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary);
|
||||
|
||||
// TODO(b/270481978): It seems not necessary to put EXTRA_VIDEO_RAW_RESOURCE_ID.
|
||||
extras.putInt(AccessibilitySettings.EXTRA_VIDEO_RAW_RESOURCE_ID,
|
||||
R.raw.accessibility_screen_magnification);
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
|
||||
private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
|
||||
private boolean mSavedAccessibilityFloatingMenuEnabled;
|
||||
private ImageView mImageGetterCacheView;
|
||||
private final Html.ImageGetter mImageGetter = (String str) -> {
|
||||
protected final Html.ImageGetter mImageGetter = (String str) -> {
|
||||
if (str != null && str.startsWith(IMG_PREFIX)) {
|
||||
final String fileName = str.substring(IMG_PREFIX.length());
|
||||
return getDrawableFromUri(Uri.parse(
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.icu.text.CaseMap;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.DeviceConfig;
|
||||
@@ -220,6 +221,34 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
addJoystickSetting(generalCategory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProcessArguments(Bundle arguments) {
|
||||
Context context = getContext();
|
||||
|
||||
// This Fragment may get arguments from MagnificationGesturesPreferenceController or
|
||||
// MagnificationNavbarPreferenceController and it's necessary to check if a key exists
|
||||
// before putting a new value into arguments.
|
||||
|
||||
if (!arguments.containsKey(AccessibilitySettings.EXTRA_PREFERENCE_KEY)) {
|
||||
arguments.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY,
|
||||
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
|
||||
}
|
||||
|
||||
if (!arguments.containsKey(AccessibilitySettings.EXTRA_INTRO)) {
|
||||
arguments.putCharSequence(AccessibilitySettings.EXTRA_INTRO,
|
||||
context.getString(R.string.accessibility_screen_magnification_intro_text));
|
||||
}
|
||||
|
||||
if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)) {
|
||||
String summary = MessageFormat.format(
|
||||
context.getString(R.string.accessibility_screen_magnification_summary),
|
||||
new Object[]{1, 2, 3, 4, 5});
|
||||
arguments.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary);
|
||||
}
|
||||
|
||||
super.onProcessArguments(arguments);
|
||||
}
|
||||
|
||||
private void addAlwaysOnSetting(PreferenceCategory generalCategory) {
|
||||
if (!DeviceConfig.getBoolean(
|
||||
DeviceConfig.NAMESPACE_WINDOW_MANAGER,
|
||||
|
||||
@@ -124,7 +124,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController
|
||||
mParent.getMetricsCategory(),
|
||||
mBatteryDiffEntry,
|
||||
Utils.formatPercentage(
|
||||
mBatteryDiffEntry.getPercentOfTotal(), /* round */ true),
|
||||
mBatteryDiffEntry.getPercentage(), /* round */ true),
|
||||
/*slotInformation=*/ null, /*showTimeInformation=*/ false);
|
||||
return true;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController
|
||||
void updateBatteryWithDiffEntry() {
|
||||
if (mBatteryDiffEntry != null && mBatteryDiffEntry.mConsumePower > 0) {
|
||||
mBatteryPercent = Utils.formatPercentage(
|
||||
mBatteryDiffEntry.getPercentOfTotal(), /* round */ true);
|
||||
mBatteryDiffEntry.getPercentage(), /* round */ true);
|
||||
mPreference.setSummary(mContext.getString(
|
||||
R.string.battery_summary, mBatteryPercent));
|
||||
} else {
|
||||
|
||||
@@ -22,24 +22,19 @@ import android.annotation.RawRes;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable2;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -52,25 +47,16 @@ import androidx.transition.TransitionSet;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.biometrics.BiometricUtils;
|
||||
import com.android.settings.biometrics.fingerprint.UdfpsEnrollHelper;
|
||||
import com.android.settings.biometrics.fingerprint.UdfpsEnrollView;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
|
||||
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.DeviceRotationViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollEnrollingViewModel;
|
||||
import com.android.settings.biometrics2.ui.viewmodel.FingerprintEnrollProgressViewModel;
|
||||
import com.android.settings.biometrics2.ui.widget.UdfpsEnrollView;
|
||||
import com.android.settingslib.display.DisplayDensityUtils;
|
||||
import com.android.settingslib.udfps.UdfpsOverlayParams;
|
||||
import com.android.settingslib.udfps.UdfpsUtils;
|
||||
|
||||
import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.airbnb.lottie.LottieCompositionFactory;
|
||||
import com.google.android.setupcompat.template.FooterActionButton;
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Fragment is used to handle enrolling process for udfps
|
||||
@@ -92,9 +78,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
private DeviceRotationViewModel mRotationViewModel;
|
||||
private FingerprintEnrollProgressViewModel mProgressViewModel;
|
||||
|
||||
private Interpolator mFastOutSlowInInterpolator;
|
||||
private Interpolator mLinearOutSlowInInterpolator;
|
||||
private Interpolator mFastOutLinearInInterpolator;
|
||||
private boolean mAnimationCancelled;
|
||||
|
||||
private LottieAnimationView mIllustrationLottie;
|
||||
@@ -104,9 +87,13 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
private boolean mHaveShownUdfpsCenterLottie;
|
||||
private boolean mHaveShownUdfpsGuideLottie;
|
||||
|
||||
private GlifLayout mView;
|
||||
private RelativeLayout mView;
|
||||
private ImageView mIcon;
|
||||
private TextView mErrorText;
|
||||
private FooterBarMixin mFooterBarMixin;
|
||||
private TextView mTitleText;
|
||||
private TextView mSubTitleText;
|
||||
private Button mSkipBtn;
|
||||
private UdfpsEnrollView mUdfpsEnrollView;
|
||||
|
||||
private boolean mShouldShowLottie;
|
||||
private boolean mIsAccessibilityEnabled;
|
||||
@@ -145,13 +132,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
|
||||
private int mIconTouchCount;
|
||||
|
||||
private UdfpsUtils mUdfpsUtils;
|
||||
private float mScaleFactor = 1.0f;
|
||||
//TODO UdfpsEnrollHelper should not be a Fragment, we should tell enrollview & progress
|
||||
// drawable enough information EnrollView & ProgressDrawable should draw themselves without
|
||||
// UdfpsEnrollHelper
|
||||
private UdfpsEnrollHelper mUdfpsEnrollHelper;
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
@@ -200,10 +180,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
super.onCreate(savedInstanceState);
|
||||
mEnrollingViewModel.restoreSavedState(savedInstanceState);
|
||||
mIsAccessibilityEnabled = mEnrollingViewModel.isAccessibilityEnabled();
|
||||
mUdfpsUtils = new UdfpsUtils();
|
||||
mUdfpsEnrollHelper = new UdfpsEnrollHelper(getActivity(), getActivity().getSystemService(
|
||||
FingerprintManager.class
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -216,95 +192,85 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
mView = initUdfpsLayout(inflater, container);
|
||||
|
||||
return mView;
|
||||
}
|
||||
|
||||
private GlifLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
||||
final GlifLayout containView = (GlifLayout) inflater.inflate(
|
||||
R.layout.udfps_enroll_enrolling, container, false);
|
||||
final UdfpsEnrollView udfpsEnrollView = addUdfpsEnrollView(inflater,
|
||||
mEnrollingViewModel.getFirstFingerprintSensorPropertiesInternal());
|
||||
final int rotation = mRotationViewModel.getLiveData().getValue();
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale(
|
||||
Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL);
|
||||
final LinearLayout layoutContainer = containView.findViewById(
|
||||
R.id.layout_container);
|
||||
layoutContainer.setPaddingRelative(
|
||||
(int) getResources().getDimension(R.dimen.rotation_90_enroll_padding_start),
|
||||
0,
|
||||
isLayoutRtl ? 0 : (int) getResources().getDimension(
|
||||
R.dimen.rotation_90_enroll_padding_end),
|
||||
0);
|
||||
|
||||
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
lp.setMarginEnd((int) getResources().getDimension(
|
||||
R.dimen.rotation_90_enroll_margin_end));
|
||||
layoutContainer.setLayoutParams(lp);
|
||||
containView.addView(udfpsEnrollView);
|
||||
containView.setClipChildren(false);
|
||||
containView.setClipToPadding(false);
|
||||
containView.setLayoutParams(lp);
|
||||
setOnHoverListener(true, containView, udfpsEnrollView);
|
||||
} else if (rotation == Surface.ROTATION_270) {
|
||||
containView.addView(udfpsEnrollView);
|
||||
containView.setClipChildren(false);
|
||||
containView.setClipToPadding(false);
|
||||
setOnHoverListener(true, containView, udfpsEnrollView);
|
||||
} else {
|
||||
final FrameLayout portraitLayoutContainer = containView.findViewById(
|
||||
R.id.layout_container);
|
||||
portraitLayoutContainer.addView(udfpsEnrollView);
|
||||
ViewGroup parent = ((ViewGroup) portraitLayoutContainer.getParent());
|
||||
parent.setClipChildren(false);
|
||||
parent.setClipToPadding(false);
|
||||
setOnHoverListener(false, containView, udfpsEnrollView);
|
||||
}
|
||||
private RelativeLayout initUdfpsLayout(LayoutInflater inflater, ViewGroup container) {
|
||||
final RelativeLayout containView = (RelativeLayout) inflater.inflate(
|
||||
R.layout.udfps_enroll_enrolling_v2, container, false);
|
||||
|
||||
final Activity activity = getActivity();
|
||||
new GlifLayoutHelper(activity, containView).setDescriptionText(
|
||||
getString(R.string.security_settings_udfps_enroll_start_message));
|
||||
|
||||
mIcon = containView.findViewById(R.id.sud_layout_icon);
|
||||
mTitleText = containView.findViewById(R.id.suc_layout_title);
|
||||
mSubTitleText = containView.findViewById(R.id.sud_layout_subtitle);
|
||||
mErrorText = containView.findViewById(R.id.error_text);
|
||||
mSkipBtn = containView.findViewById(R.id.skip_btn);
|
||||
mSkipBtn.setOnClickListener(mOnSkipClickListener);
|
||||
mUdfpsEnrollView = containView.findViewById(R.id.udfps_animation_view);
|
||||
mUdfpsEnrollView.setSensorProperties(
|
||||
mEnrollingViewModel.getFirstFingerprintSensorPropertiesInternal());
|
||||
mShouldShowLottie = shouldShowLottie();
|
||||
boolean isLandscape = BiometricUtils.isReverseLandscape(activity)
|
||||
|| BiometricUtils.isLandscape(activity);
|
||||
updateOrientation(containView, (isLandscape
|
||||
? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT));
|
||||
|
||||
mErrorText = containView.findViewById(R.id.error_text);
|
||||
mFooterBarMixin = containView.getMixin(FooterBarMixin.class);
|
||||
mFooterBarMixin.setSecondaryButton(
|
||||
new FooterButton.Builder(activity)
|
||||
.setText(R.string.security_settings_fingerprint_enroll_enrolling_skip)
|
||||
.setListener(mOnSkipClickListener)
|
||||
.setButtonType(FooterButton.ButtonType.SKIP)
|
||||
.setTheme(R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
);
|
||||
|
||||
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
|
||||
activity, android.R.interpolator.fast_out_slow_in);
|
||||
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
|
||||
activity, android.R.interpolator.linear_out_slow_in);
|
||||
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
|
||||
activity, android.R.interpolator.fast_out_linear_in);
|
||||
final int rotation = mRotationViewModel.getLiveData().getValue();
|
||||
if (rotation == Surface.ROTATION_270) {
|
||||
RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2);
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
iconLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
iconLP.topMargin = (int) convertDpToPixel(76.64f, activity);
|
||||
iconLP.leftMargin = (int) convertDpToPixel(151.54f, activity);
|
||||
mIcon.setLayoutParams(iconLP);
|
||||
|
||||
final LinearLayout buttonContainer = mFooterBarMixin.getButtonContainer();
|
||||
View spaceView = null;
|
||||
for (int i = 0; i < buttonContainer.getChildCount(); i++) {
|
||||
if (!(buttonContainer.getChildAt(i) instanceof FooterActionButton)) {
|
||||
spaceView = buttonContainer.getChildAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (spaceView != null) {
|
||||
spaceView.setVisibility(View.GONE);
|
||||
buttonContainer.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
titleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
titleLP.topMargin = (int) convertDpToPixel(138f, activity);
|
||||
titleLP.leftMargin = (int) convertDpToPixel(144f, activity);
|
||||
mTitleText.setLayoutParams(titleLP);
|
||||
|
||||
RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(-1, -2);
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
subtitleLP.addRule(RelativeLayout.END_OF, R.id.udfps_animation_view);
|
||||
subtitleLP.topMargin = (int) convertDpToPixel(198f, activity);
|
||||
subtitleLP.leftMargin = (int) convertDpToPixel(144f, activity);
|
||||
mSubTitleText.setLayoutParams(subtitleLP);
|
||||
} else if (rotation == Surface.ROTATION_90) {
|
||||
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
|
||||
RelativeLayout.LayoutParams iconLP = new RelativeLayout.LayoutParams(-2, -2);
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
iconLP.addRule(RelativeLayout.ALIGN_PARENT_START);
|
||||
iconLP.topMargin = (int) convertDpToPixel(76.64f, activity);
|
||||
iconLP.leftMargin = (int) convertDpToPixel(71.99f, activity);
|
||||
mIcon.setLayoutParams(iconLP);
|
||||
|
||||
RelativeLayout.LayoutParams titleLP = new RelativeLayout.LayoutParams(
|
||||
metrics.widthPixels / 2, -2);
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
titleLP.addRule(RelativeLayout.ALIGN_PARENT_START, R.id.udfps_animation_view);
|
||||
titleLP.topMargin = (int) convertDpToPixel(138f, activity);
|
||||
titleLP.leftMargin = (int) convertDpToPixel(66f, activity);
|
||||
mTitleText.setLayoutParams(titleLP);
|
||||
|
||||
RelativeLayout.LayoutParams subtitleLP = new RelativeLayout.LayoutParams(
|
||||
metrics.widthPixels / 2, -2);
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
subtitleLP.addRule(RelativeLayout.ALIGN_PARENT_START);
|
||||
subtitleLP.topMargin = (int) convertDpToPixel(198f, activity);
|
||||
subtitleLP.leftMargin = (int) convertDpToPixel(66f, activity);
|
||||
mSubTitleText.setLayoutParams(subtitleLP);
|
||||
}
|
||||
|
||||
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
|
||||
RelativeLayout.LayoutParams skipBtnLP =
|
||||
(RelativeLayout.LayoutParams) mIcon.getLayoutParams();
|
||||
skipBtnLP.topMargin = (int) convertDpToPixel(26f, activity);
|
||||
skipBtnLP.leftMargin = (int) convertDpToPixel(54f, activity);
|
||||
mSkipBtn.requestLayout();
|
||||
}
|
||||
return containView;
|
||||
}
|
||||
|
||||
@@ -358,8 +324,9 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
|
||||
final int progress = getProgress(enrollmentProgress);
|
||||
|
||||
mUdfpsEnrollHelper.onEnrollmentProgress(enrollmentProgress.getSteps(),
|
||||
enrollmentProgress.getRemaining());
|
||||
|
||||
mUdfpsEnrollView.onEnrollmentProgress(enrollmentProgress.getRemaining(),
|
||||
enrollmentProgress.getSteps());
|
||||
|
||||
if (animate) {
|
||||
animateProgress(progress);
|
||||
@@ -382,38 +349,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private UdfpsEnrollView addUdfpsEnrollView(LayoutInflater inflater,
|
||||
FingerprintSensorPropertiesInternal udfpsProps) {
|
||||
|
||||
UdfpsEnrollView enrollView = (UdfpsEnrollView) inflater.inflate(R.layout.udfps_enroll_view,
|
||||
null, false);
|
||||
DisplayInfo displayInfo = new DisplayInfo();
|
||||
getActivity().getDisplay().getDisplayInfo(displayInfo);
|
||||
mScaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
|
||||
Rect udfpsBounds = udfpsProps.getLocation().getRect();
|
||||
udfpsBounds.scale(mScaleFactor);
|
||||
|
||||
final Rect overlayBounds = new Rect(
|
||||
0, /* left */
|
||||
displayInfo.getNaturalHeight() / 2, /* top */
|
||||
displayInfo.getNaturalWidth(), /* right */
|
||||
displayInfo.getNaturalHeight() /* botom */);
|
||||
|
||||
UdfpsOverlayParams params = new UdfpsOverlayParams(
|
||||
udfpsBounds,
|
||||
overlayBounds,
|
||||
displayInfo.getNaturalWidth(),
|
||||
displayInfo.getNaturalHeight(),
|
||||
mScaleFactor,
|
||||
displayInfo.rotation);
|
||||
|
||||
enrollView.setOverlayParams(params);
|
||||
|
||||
enrollView.setEnrollHelper(mUdfpsEnrollHelper);
|
||||
|
||||
return enrollView;
|
||||
}
|
||||
|
||||
private void animateProgress(int progress) {
|
||||
// UDFPS animations are owned by SystemUI
|
||||
if (progress >= PROGRESS_BAR_MAX) {
|
||||
@@ -423,17 +358,11 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void updateTitleAndDescription() {
|
||||
|
||||
final Activity activity = getActivity();
|
||||
final GlifLayoutHelper glifLayoutHelper = new GlifLayoutHelper(activity, mView);
|
||||
|
||||
switch (getCurrentStage()) {
|
||||
case STAGE_CENTER:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_start_message));
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||
} else if (!mHaveShownUdfpsCenterLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsCenterLottie = true;
|
||||
// Note: Update string reference when differentiate in between udfps & sfps
|
||||
@@ -445,11 +374,10 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
break;
|
||||
|
||||
case STAGE_GUIDED:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_repeat_title);
|
||||
if (mIsAccessibilityEnabled || mIllustrationLottie == null) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_repeat_a11y_message));
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_udfps_enroll_repeat_a11y_message);
|
||||
} else if (!mHaveShownUdfpsGuideLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsGuideLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -460,8 +388,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
break;
|
||||
case STAGE_FINGERTIP:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_udfps_enroll_fingertip_title);
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_fingertip_title);
|
||||
if (!mHaveShownUdfpsTipLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsTipLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -471,8 +398,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
break;
|
||||
case STAGE_LEFT_EDGE:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_udfps_enroll_left_edge_title);
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_left_edge_title);
|
||||
if (!mHaveShownUdfpsLeftEdgeLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsLeftEdgeLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -481,17 +407,15 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
configureEnrollmentStage("", R.raw.udfps_left_edge_hint_lottie);
|
||||
} else if (mIllustrationLottie == null) {
|
||||
if (isStageHalfCompleted()) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message));
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||
} else {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_edge_message));
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STAGE_RIGHT_EDGE:
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_udfps_enroll_right_edge_title);
|
||||
mTitleText.setText(R.string.security_settings_udfps_enroll_right_edge_title);
|
||||
if (!mHaveShownUdfpsRightEdgeLottie && mIllustrationLottie != null) {
|
||||
mHaveShownUdfpsRightEdgeLottie = true;
|
||||
mIllustrationLottie.setContentDescription(
|
||||
@@ -501,31 +425,22 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
|
||||
} else if (mIllustrationLottie == null) {
|
||||
if (isStageHalfCompleted()) {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message));
|
||||
mSubTitleText.setText(
|
||||
R.string.security_settings_fingerprint_enroll_repeat_message);
|
||||
} else {
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_edge_message));
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_edge_message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case STAGE_UNKNOWN:
|
||||
default:
|
||||
// setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||
// Don't use BiometricEnrollBase#setHeaderText, since that invokes setTitle,
|
||||
// which gets announced for a11y upon entering the page. For UDFPS, we want to
|
||||
// announce a different string for a11y upon entering the page.
|
||||
glifLayoutHelper.setHeaderText(
|
||||
R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||
glifLayoutHelper.setDescriptionText(getString(
|
||||
R.string.security_settings_udfps_enroll_start_message));
|
||||
mTitleText.setText(R.string.security_settings_fingerprint_enroll_udfps_title);
|
||||
mSubTitleText.setText(R.string.security_settings_udfps_enroll_start_message);
|
||||
final CharSequence description = getString(
|
||||
R.string.security_settings_udfps_enroll_a11y);
|
||||
mView.getHeaderTextView().setContentDescription(description);
|
||||
activity.setTitle(description);
|
||||
getActivity().setTitle(description);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,7 +453,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
return defaultDensity == currentDensity;
|
||||
}
|
||||
|
||||
private void updateOrientation(@NonNull GlifLayout glifLayout, int orientation) {
|
||||
private void updateOrientation(@NonNull RelativeLayout content, int orientation) {
|
||||
switch (orientation) {
|
||||
case Configuration.ORIENTATION_LANDSCAPE: {
|
||||
mIllustrationLottie = null;
|
||||
@@ -546,7 +461,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
case Configuration.ORIENTATION_PORTRAIT: {
|
||||
if (mShouldShowLottie) {
|
||||
mIllustrationLottie = glifLayout.findViewById(R.id.illustration_lottie);
|
||||
mIllustrationLottie = content.findViewById(R.id.illustration_lottie);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -625,7 +540,7 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void configureEnrollmentStage(CharSequence description, @RawRes int lottie) {
|
||||
new GlifLayoutHelper(getActivity(), mView).setDescriptionText(description);
|
||||
mSubTitleText.setText(description);
|
||||
LottieCompositionFactory.fromRawRes(getActivity(), lottie)
|
||||
.addListener((c) -> {
|
||||
mIllustrationLottie.setComposition(c);
|
||||
@@ -634,31 +549,6 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void setOnHoverListener(boolean isLandscape, GlifLayout enrollLayout,
|
||||
UdfpsEnrollView udfpsEnrollView) {
|
||||
if (!mIsAccessibilityEnabled) return;
|
||||
|
||||
final Context context = getActivity();
|
||||
final View.OnHoverListener onHoverListener = (v, event) -> {
|
||||
// Map the touch to portrait mode if the device is in
|
||||
// landscape mode.
|
||||
final Point scaledTouch =
|
||||
mUdfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0),
|
||||
event, udfpsEnrollView.getOverlayParams());
|
||||
|
||||
final String theStr = mUdfpsUtils.onTouchOutsideOfSensorArea(
|
||||
mEnrollingViewModel.isTouchExplorationEnabled(), context,
|
||||
scaledTouch.x, scaledTouch.y, udfpsEnrollView.getOverlayParams());
|
||||
if (theStr != null) {
|
||||
v.announceForAccessibility(theStr);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
enrollLayout.findManagedViewById(isLandscape ? R.id.sud_landscape_content_area
|
||||
: R.id.sud_layout_content).setOnHoverListener(onHoverListener);
|
||||
}
|
||||
|
||||
private void onEnrollmentProgressChange(@NonNull EnrollmentProgress progress) {
|
||||
updateProgress(true /* animate */, progress);
|
||||
|
||||
@@ -678,34 +568,43 @@ public class FingerprintEnrollEnrollingUdfpsFragment extends Fragment {
|
||||
private void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
|
||||
if (!TextUtils.isEmpty(helpString)) {
|
||||
showError(helpString);
|
||||
mUdfpsEnrollHelper.onEnrollmentHelp();
|
||||
mUdfpsEnrollView.onEnrollmentHelp();
|
||||
}
|
||||
}
|
||||
|
||||
private void onAcquired(boolean isAcquiredGood) {
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onAcquired(isAcquiredGood);
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onAcquired(isAcquiredGood);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPointerDown(int sensorId) {
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onPointerDown(sensorId);
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onPointerDown(sensorId);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPointerUp(int sensorId) {
|
||||
if (mUdfpsEnrollHelper != null) {
|
||||
mUdfpsEnrollHelper.onPointerUp(sensorId);
|
||||
if (mUdfpsEnrollView != null) {
|
||||
mUdfpsEnrollView.onPointerUp(sensorId);
|
||||
}
|
||||
}
|
||||
|
||||
private void showError(CharSequence error) {
|
||||
mView.setHeaderText(error);
|
||||
mView.getHeaderTextView().setContentDescription(error);
|
||||
new GlifLayoutHelper(getActivity(), mView).setDescriptionText("");
|
||||
mTitleText.setText(error);
|
||||
mTitleText.setContentDescription(error);
|
||||
mSubTitleText.setContentDescription("");
|
||||
}
|
||||
|
||||
private float convertDpToPixel(float dp, Context context) {
|
||||
float px = dp * getDensity(context);
|
||||
return px;
|
||||
}
|
||||
|
||||
private float getDensity(Context context) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return metrics.density;
|
||||
}
|
||||
|
||||
private final Runnable mShowDialogRunnable = new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics2.ui.widget;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.PathShape;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.PathParser;
|
||||
import android.util.TypedValue;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UDFPS fingerprint drawable that is shown when enrolling
|
||||
*/
|
||||
public class UdfpsEnrollDrawable extends Drawable {
|
||||
private static final String TAG = "UdfpsAnimationEnroll";
|
||||
|
||||
private static final long TARGET_ANIM_DURATION_LONG = 800L;
|
||||
private static final long TARGET_ANIM_DURATION_SHORT = 600L;
|
||||
// 1 + SCALE_MAX is the maximum that the moving target will animate to
|
||||
private static final float SCALE_MAX = 0.25f;
|
||||
private static final float DEFAULT_STROKE_WIDTH = 3f;
|
||||
private static final float SCALE = 0.5f;
|
||||
private static final String SCALE_OVERRIDE =
|
||||
"com.android.systemui.biometrics.UdfpsEnrollHelper.scale";
|
||||
private static final String NEW_COORDS_OVERRIDE =
|
||||
"com.android.systemui.biometrics.UdfpsNewCoords";
|
||||
|
||||
@NonNull
|
||||
private final Drawable mMovingTargetFpIcon;
|
||||
@NonNull
|
||||
private final Paint mSensorOutlinePaint;
|
||||
@NonNull
|
||||
private final Paint mBlueFill;
|
||||
@NonNull
|
||||
private final ShapeDrawable mFingerprintDrawable;
|
||||
private int mAlpha;
|
||||
private boolean mSkipDraw = false;
|
||||
|
||||
@Nullable
|
||||
private RectF mSensorRect;
|
||||
|
||||
// Moving target animator set
|
||||
@Nullable
|
||||
AnimatorSet mTargetAnimatorSet;
|
||||
// Moving target location
|
||||
float mCurrentX;
|
||||
float mCurrentY;
|
||||
// Moving target size
|
||||
float mCurrentScale = 1.f;
|
||||
|
||||
@NonNull
|
||||
private final Animator.AnimatorListener mTargetAnimListener;
|
||||
|
||||
private boolean mShouldShowTipHint = false;
|
||||
private boolean mShouldShowEdgeHint = false;
|
||||
|
||||
private int mEnrollIcon;
|
||||
private int mMovingTargetFill;
|
||||
|
||||
private int mTotalSteps = -1;
|
||||
private int mRemainingSteps = -1;
|
||||
private int mLocationsEnrolled = 0;
|
||||
private int mCenterTouchCount = 0;
|
||||
|
||||
private FingerprintManager mFingerprintManager;
|
||||
|
||||
private boolean mAccessibilityEnabled;
|
||||
private Context mContext;
|
||||
private final List<PointF> mGuidedEnrollmentPoints;
|
||||
|
||||
UdfpsEnrollDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
mFingerprintDrawable = defaultFactory(context);
|
||||
|
||||
loadResources(context, attrs);
|
||||
mSensorOutlinePaint = new Paint(0 /* flags */);
|
||||
mSensorOutlinePaint.setAntiAlias(true);
|
||||
mSensorOutlinePaint.setColor(mMovingTargetFill);
|
||||
mSensorOutlinePaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mBlueFill = new Paint(0 /* flags */);
|
||||
mBlueFill.setAntiAlias(true);
|
||||
mBlueFill.setColor(mMovingTargetFill);
|
||||
mBlueFill.setStyle(Paint.Style.FILL);
|
||||
|
||||
mMovingTargetFpIcon = context.getResources()
|
||||
.getDrawable(R.drawable.ic_enrollment_fingerprint, null);
|
||||
mMovingTargetFpIcon.setTint(mEnrollIcon);
|
||||
mMovingTargetFpIcon.mutate();
|
||||
|
||||
mFingerprintDrawable.setTint(mEnrollIcon);
|
||||
|
||||
setAlpha(255);
|
||||
mTargetAnimListener = new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
updateTipHintVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
}
|
||||
};
|
||||
mContext = context;
|
||||
mFingerprintManager = context.getSystemService(FingerprintManager.class);
|
||||
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||
mAccessibilityEnabled = am.isEnabled();
|
||||
mGuidedEnrollmentPoints = new ArrayList<>();
|
||||
initEnrollPoint(context);
|
||||
}
|
||||
|
||||
/** The [sensorRect] coordinates for the sensor area. */
|
||||
void onSensorRectUpdated(@NonNull RectF sensorRect) {
|
||||
int margin = ((int) sensorRect.height()) / 8;
|
||||
Rect bounds = new Rect((int) (sensorRect.left) + margin, (int) (sensorRect.top) + margin,
|
||||
(int) (sensorRect.right) - margin, (int) (sensorRect.bottom) - margin);
|
||||
updateFingerprintIconBounds(bounds);
|
||||
mSensorRect = sensorRect;
|
||||
}
|
||||
|
||||
void setShouldSkipDraw(boolean skipDraw) {
|
||||
if (mSkipDraw == skipDraw) {
|
||||
return;
|
||||
}
|
||||
mSkipDraw = skipDraw;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
void updateFingerprintIconBounds(@NonNull Rect bounds) {
|
||||
mFingerprintDrawable.setBounds(bounds);
|
||||
invalidateSelf();
|
||||
mMovingTargetFpIcon.setBounds(bounds);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
void onEnrollmentProgress(final int remaining, final int totalSteps) {
|
||||
if (mTotalSteps == -1) {
|
||||
mTotalSteps = totalSteps;
|
||||
}
|
||||
|
||||
if (remaining != mRemainingSteps) {
|
||||
mLocationsEnrolled++;
|
||||
if (isCenterEnrollmentStage()) {
|
||||
mCenterTouchCount++;
|
||||
}
|
||||
}
|
||||
mRemainingSteps = remaining;
|
||||
|
||||
if (!isCenterEnrollmentStage()) {
|
||||
if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
|
||||
mTargetAnimatorSet.end();
|
||||
}
|
||||
|
||||
final PointF point = getNextGuidedEnrollmentPoint();
|
||||
if (mCurrentX != point.x || mCurrentY != point.y) {
|
||||
final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
|
||||
x.addUpdateListener(animation -> {
|
||||
mCurrentX = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
});
|
||||
|
||||
final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
|
||||
y.addUpdateListener(animation -> {
|
||||
mCurrentY = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
});
|
||||
|
||||
final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
|
||||
final long duration = isMovingToCenter
|
||||
? TARGET_ANIM_DURATION_SHORT
|
||||
: TARGET_ANIM_DURATION_LONG;
|
||||
|
||||
final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
|
||||
scale.setDuration(duration);
|
||||
scale.addUpdateListener(animation -> {
|
||||
// Grow then shrink
|
||||
mCurrentScale = 1
|
||||
+ SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
|
||||
invalidateSelf();
|
||||
});
|
||||
|
||||
mTargetAnimatorSet = new AnimatorSet();
|
||||
|
||||
mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
|
||||
mTargetAnimatorSet.setDuration(duration);
|
||||
mTargetAnimatorSet.addListener(mTargetAnimListener);
|
||||
mTargetAnimatorSet.playTogether(x, y, scale);
|
||||
mTargetAnimatorSet.start();
|
||||
} else {
|
||||
updateTipHintVisibility();
|
||||
}
|
||||
} else {
|
||||
updateTipHintVisibility();
|
||||
}
|
||||
|
||||
updateEdgeHintVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (mSkipDraw) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw moving target
|
||||
if (!isCenterEnrollmentStage()) {
|
||||
canvas.save();
|
||||
canvas.translate(mCurrentX, mCurrentY);
|
||||
|
||||
if (mSensorRect != null) {
|
||||
canvas.scale(mCurrentScale, mCurrentScale,
|
||||
mSensorRect.centerX(), mSensorRect.centerY());
|
||||
canvas.drawOval(mSensorRect, mBlueFill);
|
||||
}
|
||||
|
||||
mMovingTargetFpIcon.draw(canvas);
|
||||
canvas.restore();
|
||||
} else {
|
||||
if (mSensorRect != null) {
|
||||
canvas.drawOval(mSensorRect, mSensorOutlinePaint);
|
||||
}
|
||||
mFingerprintDrawable.draw(canvas);
|
||||
mFingerprintDrawable.setAlpha(getAlpha());
|
||||
mSensorOutlinePaint.setAlpha(getAlpha());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mAlpha = alpha;
|
||||
mFingerprintDrawable.setAlpha(alpha);
|
||||
mSensorOutlinePaint.setAlpha(alpha);
|
||||
mBlueFill.setAlpha(alpha);
|
||||
mMovingTargetFpIcon.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void updateTipHintVisibility() {
|
||||
final boolean shouldShow = isTipEnrollmentStage();
|
||||
// With the new update, we will git rid of most of this code, and instead
|
||||
// we will change the fingerprint icon.
|
||||
if (mShouldShowTipHint == shouldShow) {
|
||||
return;
|
||||
}
|
||||
mShouldShowTipHint = shouldShow;
|
||||
}
|
||||
|
||||
private void updateEdgeHintVisibility() {
|
||||
final boolean shouldShow = isEdgeEnrollmentStage();
|
||||
if (mShouldShowEdgeHint == shouldShow) {
|
||||
return;
|
||||
}
|
||||
mShouldShowEdgeHint = shouldShow;
|
||||
}
|
||||
|
||||
private ShapeDrawable defaultFactory(Context context) {
|
||||
String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
|
||||
ShapeDrawable drawable = new ShapeDrawable(
|
||||
new PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)
|
||||
);
|
||||
drawable.mutate();
|
||||
drawable.getPaint().setStyle(Paint.Style.STROKE);
|
||||
drawable.getPaint().setStrokeCap(Paint.Cap.ROUND);
|
||||
drawable.getPaint().setStrokeWidth(DEFAULT_STROKE_WIDTH);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
||||
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
||||
R.style.BiometricsEnrollStyle);
|
||||
mEnrollIcon = ta.getColor(R.styleable.BiometricsEnrollView_biometricsEnrollIcon, 0);
|
||||
mMovingTargetFill = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
private boolean isCenterEnrollmentStage() {
|
||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return true;
|
||||
}
|
||||
return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
|
||||
}
|
||||
|
||||
private int getStageThresholdSteps(int totalSteps, int stageIndex) {
|
||||
return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
|
||||
}
|
||||
|
||||
private PointF getNextGuidedEnrollmentPoint() {
|
||||
if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
|
||||
return new PointF(0f, 0f);
|
||||
}
|
||||
|
||||
float scale = SCALE;
|
||||
if (Build.IS_ENG || Build.IS_USERDEBUG) {
|
||||
scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
|
||||
SCALE_OVERRIDE, SCALE,
|
||||
UserHandle.USER_CURRENT);
|
||||
}
|
||||
final int index = mLocationsEnrolled - mCenterTouchCount;
|
||||
final PointF originalPoint = mGuidedEnrollmentPoints
|
||||
.get(index % mGuidedEnrollmentPoints.size());
|
||||
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
|
||||
}
|
||||
|
||||
private boolean isGuidedEnrollmentStage() {
|
||||
if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return false;
|
||||
}
|
||||
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||
return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
|
||||
&& progressSteps < getStageThresholdSteps(mTotalSteps, 1);
|
||||
}
|
||||
|
||||
private boolean isTipEnrollmentStage() {
|
||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return false;
|
||||
}
|
||||
final int progressSteps = mTotalSteps - mRemainingSteps;
|
||||
return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
|
||||
&& progressSteps < getStageThresholdSteps(mTotalSteps, 2);
|
||||
}
|
||||
|
||||
private boolean isEdgeEnrollmentStage() {
|
||||
if (mTotalSteps == -1 || mRemainingSteps == -1) {
|
||||
return false;
|
||||
}
|
||||
return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
|
||||
}
|
||||
|
||||
private void initEnrollPoint(Context context) {
|
||||
// Number of pixels per mm
|
||||
float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
|
||||
context.getResources().getDisplayMetrics());
|
||||
boolean useNewCoords = Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
NEW_COORDS_OVERRIDE, 0,
|
||||
UserHandle.USER_CURRENT) != 0;
|
||||
if (useNewCoords && (Build.IS_ENG || Build.IS_USERDEBUG)) {
|
||||
Log.v(TAG, "Using new coordinates");
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, -1.02f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.15f * px, 1.02f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(0.29f * px, 0.00f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, -2.35f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, -3.96f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, -4.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, -3.29f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, -1.23f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-2.48f * px, 1.23f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.69f * px, 3.29f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.37f * px, 4.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(1.07f * px, 3.96f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.17f * px, 2.35f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.58f * px, 0.00f * px));
|
||||
} else {
|
||||
Log.v(TAG, "Using old coordinates");
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.00f * px, 0.00f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(0.87f * px, -2.70f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, -1.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.80f * px, 1.31f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(0.88f * px, 2.70f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(3.94f * px, -1.06f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.90f * px, -4.14f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-0.52f * px, -5.95f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-3.33f * px, -3.33f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-3.99f * px, -0.35f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-3.62f * px, 2.54f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(-1.49f * px, 5.57f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(2.29f * px, 4.92f * px));
|
||||
mGuidedEnrollmentPoints.add(new PointF(3.82f * px, 1.78f * px));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics2.ui.widget;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Process;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* UDFPS enrollment progress bar.
|
||||
*/
|
||||
public class UdfpsEnrollProgressBarDrawable extends Drawable {
|
||||
private static final String TAG = "UdfpsProgressBar";
|
||||
|
||||
private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
|
||||
private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
|
||||
private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
|
||||
private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
|
||||
private static final float STROKE_WIDTH_DP = 12f;
|
||||
private static final Interpolator DEACCEL = new DecelerateInterpolator();
|
||||
|
||||
private static final VibrationEffect VIBRATE_EFFECT_ERROR =
|
||||
VibrationEffect.createWaveform(new long[]{0, 5, 55, 60}, -1);
|
||||
private static final VibrationAttributes FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ACCESSIBILITY);
|
||||
|
||||
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
|
||||
|
||||
private static final VibrationEffect SUCCESS_VIBRATION_EFFECT =
|
||||
VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||
|
||||
private final float mStrokeWidthPx;
|
||||
@ColorInt
|
||||
private final int mProgressColor;
|
||||
@ColorInt
|
||||
private final int mHelpColor;
|
||||
@ColorInt
|
||||
private final int mOnFirstBucketFailedColor;
|
||||
@NonNull
|
||||
private final Drawable mCheckmarkDrawable;
|
||||
@NonNull
|
||||
private final Interpolator mCheckmarkInterpolator;
|
||||
@NonNull
|
||||
private final Paint mBackgroundPaint;
|
||||
@VisibleForTesting
|
||||
@NonNull
|
||||
final Paint mFillPaint;
|
||||
@NonNull
|
||||
private final Vibrator mVibrator;
|
||||
@NonNull
|
||||
private final boolean mIsAccessibilityEnabled;
|
||||
@NonNull
|
||||
private final Context mContext;
|
||||
|
||||
private boolean mAfterFirstTouch;
|
||||
|
||||
private int mRemainingSteps = 0;
|
||||
private int mTotalSteps = 0;
|
||||
private float mProgress = 0f;
|
||||
@Nullable
|
||||
private ValueAnimator mProgressAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
|
||||
|
||||
private boolean mShowingHelp = false;
|
||||
@Nullable
|
||||
private ValueAnimator mFillColorAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
|
||||
|
||||
@Nullable
|
||||
private ValueAnimator mBackgroundColorAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
|
||||
|
||||
private boolean mComplete = false;
|
||||
private float mCheckmarkScale = 0f;
|
||||
@Nullable
|
||||
private ValueAnimator mCheckmarkAnimator;
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mCheckmarkUpdateListener;
|
||||
|
||||
private int mMovingTargetFill;
|
||||
private int mMovingTargetFillError;
|
||||
private int mEnrollProgress;
|
||||
private int mEnrollProgressHelp;
|
||||
private int mEnrollProgressHelpWithTalkback;
|
||||
|
||||
public UdfpsEnrollProgressBarDrawable(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
mContext = context;
|
||||
|
||||
loadResources(context, attrs);
|
||||
float density = context.getResources().getDisplayMetrics().densityDpi;
|
||||
mStrokeWidthPx = STROKE_WIDTH_DP * (density / DisplayMetrics.DENSITY_DEFAULT);
|
||||
mProgressColor = mEnrollProgress;
|
||||
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
|
||||
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
|
||||
mOnFirstBucketFailedColor = mMovingTargetFillError;
|
||||
if (!mIsAccessibilityEnabled) {
|
||||
mHelpColor = mEnrollProgressHelp;
|
||||
} else {
|
||||
mHelpColor = mEnrollProgressHelpWithTalkback;
|
||||
}
|
||||
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
|
||||
mCheckmarkDrawable.mutate();
|
||||
mCheckmarkInterpolator = new OvershootInterpolator();
|
||||
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mBackgroundPaint.setColor(mMovingTargetFill);
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
||||
mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
// Progress fill should *not* use the extracted system color.
|
||||
mFillPaint = new Paint();
|
||||
mFillPaint.setStrokeWidth(mStrokeWidthPx);
|
||||
mFillPaint.setColor(mProgressColor);
|
||||
mFillPaint.setAntiAlias(true);
|
||||
mFillPaint.setStyle(Paint.Style.STROKE);
|
||||
mFillPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
mVibrator = mContext.getSystemService(Vibrator.class);
|
||||
|
||||
mProgressUpdateListener = animation -> {
|
||||
mProgress = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mFillColorUpdateListener = animation -> {
|
||||
mFillPaint.setColor((int) animation.getAnimatedValue());
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mCheckmarkUpdateListener = animation -> {
|
||||
mCheckmarkScale = (float) animation.getAnimatedValue();
|
||||
invalidateSelf();
|
||||
};
|
||||
|
||||
mBackgroundColorUpdateListener = animation -> {
|
||||
mBackgroundPaint.setColor((int) animation.getAnimatedValue());
|
||||
invalidateSelf();
|
||||
};
|
||||
}
|
||||
|
||||
void onEnrollmentProgress(final int remaining, final int totalSteps) {
|
||||
android.util.Log.e(TAG, "remaining =" + remaining);
|
||||
android.util.Log.e(TAG, "totalSteps =" + totalSteps);
|
||||
mAfterFirstTouch = true;
|
||||
updateState(remaining, totalSteps, false /* showingHelp */);
|
||||
}
|
||||
|
||||
void onEnrollmentHelp(int remaining, int totalSteps) {
|
||||
updateState(remaining, totalSteps, true /* showingHelp */);
|
||||
}
|
||||
|
||||
void onLastStepAcquired() {
|
||||
updateState(0, mTotalSteps, false /* showingHelp */);
|
||||
}
|
||||
|
||||
private void updateState(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||
updateProgress(remainingSteps, totalSteps, showingHelp);
|
||||
updateFillColor(showingHelp);
|
||||
}
|
||||
|
||||
private void updateProgress(int remainingSteps, int totalSteps, boolean showingHelp) {
|
||||
if (mRemainingSteps == remainingSteps && mTotalSteps == totalSteps) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mShowingHelp) {
|
||||
if (mVibrator != null && mIsAccessibilityEnabled) {
|
||||
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
||||
VIBRATE_EFFECT_ERROR, getClass().getSimpleName() + "::onEnrollmentHelp",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
||||
}
|
||||
} else {
|
||||
// If the first touch is an error, remainingSteps will be -1 and the callback
|
||||
// doesn't come from onEnrollmentHelp. If we are in the accessibility flow,
|
||||
// we still would like to vibrate.
|
||||
if (mVibrator != null) {
|
||||
if (remainingSteps == -1 && mIsAccessibilityEnabled) {
|
||||
mVibrator.vibrate(Process.myUid(), mContext.getOpPackageName(),
|
||||
VIBRATE_EFFECT_ERROR,
|
||||
getClass().getSimpleName() + "::onFirstTouchError",
|
||||
FINGERPRINT_ENROLLING_SONFICATION_ATTRIBUTES);
|
||||
} else if (remainingSteps != -1 && !mIsAccessibilityEnabled) {
|
||||
mVibrator.vibrate(Process.myUid(),
|
||||
mContext.getOpPackageName(),
|
||||
SUCCESS_VIBRATION_EFFECT,
|
||||
getClass().getSimpleName() + "::OnEnrollmentProgress",
|
||||
HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mShowingHelp = showingHelp;
|
||||
mRemainingSteps = remainingSteps;
|
||||
mTotalSteps = totalSteps;
|
||||
|
||||
final int progressSteps = Math.max(0, totalSteps - remainingSteps);
|
||||
|
||||
// If needed, add 1 to progress and total steps to account for initial touch.
|
||||
final int adjustedSteps = mAfterFirstTouch ? progressSteps + 1 : progressSteps;
|
||||
final int adjustedTotal = mAfterFirstTouch ? mTotalSteps + 1 : mTotalSteps;
|
||||
|
||||
final float targetProgress = Math.min(1f, (float) adjustedSteps / (float) adjustedTotal);
|
||||
|
||||
if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
|
||||
mProgressAnimator.cancel();
|
||||
}
|
||||
|
||||
mProgressAnimator = ValueAnimator.ofFloat(mProgress, targetProgress);
|
||||
mProgressAnimator.setDuration(PROGRESS_ANIMATION_DURATION_MS);
|
||||
mProgressAnimator.addUpdateListener(mProgressUpdateListener);
|
||||
mProgressAnimator.start();
|
||||
|
||||
if (remainingSteps == 0) {
|
||||
startCompletionAnimation();
|
||||
} else if (remainingSteps > 0) {
|
||||
rollBackCompletionAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private void animateBackgroundColor() {
|
||||
if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
|
||||
mBackgroundColorAnimator.end();
|
||||
}
|
||||
mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
|
||||
mOnFirstBucketFailedColor);
|
||||
mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||
mBackgroundColorAnimator.setRepeatCount(1);
|
||||
mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||
mBackgroundColorAnimator.setInterpolator(DEACCEL);
|
||||
mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
|
||||
mBackgroundColorAnimator.start();
|
||||
}
|
||||
|
||||
private void updateFillColor(boolean showingHelp) {
|
||||
if (!mAfterFirstTouch && showingHelp) {
|
||||
// If we are on the first touch, animate the background color
|
||||
// instead of the progress color.
|
||||
animateBackgroundColor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
|
||||
mFillColorAnimator.end();
|
||||
}
|
||||
|
||||
@ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
|
||||
mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
|
||||
mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
|
||||
mFillColorAnimator.setRepeatCount(1);
|
||||
mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||
mFillColorAnimator.setInterpolator(DEACCEL);
|
||||
mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
|
||||
mFillColorAnimator.start();
|
||||
}
|
||||
|
||||
private void startCompletionAnimation() {
|
||||
if (mComplete) {
|
||||
return;
|
||||
}
|
||||
mComplete = true;
|
||||
|
||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||
mCheckmarkAnimator.cancel();
|
||||
}
|
||||
|
||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 1f);
|
||||
mCheckmarkAnimator.setStartDelay(CHECKMARK_ANIMATION_DELAY_MS);
|
||||
mCheckmarkAnimator.setDuration(CHECKMARK_ANIMATION_DURATION_MS);
|
||||
mCheckmarkAnimator.setInterpolator(mCheckmarkInterpolator);
|
||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||
mCheckmarkAnimator.start();
|
||||
}
|
||||
|
||||
private void rollBackCompletionAnimation() {
|
||||
if (!mComplete) {
|
||||
return;
|
||||
}
|
||||
mComplete = false;
|
||||
|
||||
// Adjust duration based on how much of the completion animation has played.
|
||||
final float animatedFraction = mCheckmarkAnimator != null
|
||||
? mCheckmarkAnimator.getAnimatedFraction()
|
||||
: 0f;
|
||||
final long durationMs = Math.round(CHECKMARK_ANIMATION_DELAY_MS * animatedFraction);
|
||||
|
||||
if (mCheckmarkAnimator != null && mCheckmarkAnimator.isRunning()) {
|
||||
mCheckmarkAnimator.cancel();
|
||||
}
|
||||
|
||||
mCheckmarkAnimator = ValueAnimator.ofFloat(mCheckmarkScale, 0f);
|
||||
mCheckmarkAnimator.setDuration(durationMs);
|
||||
mCheckmarkAnimator.addUpdateListener(mCheckmarkUpdateListener);
|
||||
mCheckmarkAnimator.start();
|
||||
}
|
||||
|
||||
private void loadResources(Context context, @Nullable AttributeSet attrs) {
|
||||
final TypedArray ta = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.BiometricsEnrollView, R.attr.biometricsEnrollStyle,
|
||||
R.style.BiometricsEnrollStyle);
|
||||
mMovingTargetFill = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFill, 0);
|
||||
mMovingTargetFillError = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsMovingTargetFillError, 0);
|
||||
mEnrollProgress = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgress, 0);
|
||||
mEnrollProgressHelp = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelp, 0);
|
||||
mEnrollProgressHelpWithTalkback = ta.getColor(
|
||||
R.styleable.BiometricsEnrollView_biometricsEnrollProgressHelpWithTalkback, 0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
canvas.save();
|
||||
|
||||
// Progress starts from the top, instead of the right
|
||||
canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
|
||||
|
||||
final float halfPaddingPx = mStrokeWidthPx / 2f;
|
||||
|
||||
if (mProgress < 1f) {
|
||||
// Draw the background color of the progress circle.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
getBounds().right - halfPaddingPx,
|
||||
getBounds().bottom - halfPaddingPx,
|
||||
0f /* startAngle */,
|
||||
360f /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
mBackgroundPaint);
|
||||
}
|
||||
|
||||
if (mProgress > 0f) {
|
||||
// Draw the filled portion of the progress circle.
|
||||
canvas.drawArc(
|
||||
halfPaddingPx,
|
||||
halfPaddingPx,
|
||||
getBounds().right - halfPaddingPx,
|
||||
getBounds().bottom - halfPaddingPx,
|
||||
0f /* startAngle */,
|
||||
360f * mProgress /* sweepAngle */,
|
||||
false /* useCenter */,
|
||||
mFillPaint);
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
|
||||
if (mCheckmarkScale > 0f) {
|
||||
final float offsetScale = (float) Math.sqrt(2) / 2f;
|
||||
final float centerXOffset = (getBounds().width() - mStrokeWidthPx) / 2f * offsetScale;
|
||||
final float centerYOffset = (getBounds().height() - mStrokeWidthPx) / 2f * offsetScale;
|
||||
final float centerX = getBounds().centerX() + centerXOffset;
|
||||
final float centerY = getBounds().centerY() + centerYOffset;
|
||||
|
||||
final float boundsXOffset =
|
||||
mCheckmarkDrawable.getIntrinsicWidth() / 2f * mCheckmarkScale;
|
||||
final float boundsYOffset =
|
||||
mCheckmarkDrawable.getIntrinsicHeight() / 2f * mCheckmarkScale;
|
||||
|
||||
final int left = Math.round(centerX - boundsXOffset);
|
||||
final int top = Math.round(centerY - boundsYOffset);
|
||||
final int right = Math.round(centerX + boundsXOffset);
|
||||
final int bottom = Math.round(centerY + boundsYOffset);
|
||||
mCheckmarkDrawable.setBounds(left, top, right, bottom);
|
||||
mCheckmarkDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.biometrics2.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.RotationUtils;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.Surface;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.udfps.UdfpsOverlayParams;
|
||||
import com.android.settingslib.udfps.UdfpsUtils;
|
||||
|
||||
/**
|
||||
* View corresponding with udfps_enroll_view.xml
|
||||
*/
|
||||
public class UdfpsEnrollView extends FrameLayout {
|
||||
private static final String TAG = "UdfpsEnrollView";
|
||||
@NonNull
|
||||
private final UdfpsEnrollDrawable mFingerprintDrawable;
|
||||
@NonNull
|
||||
private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
|
||||
@NonNull
|
||||
private ImageView mFingerprintProgressView;
|
||||
private UdfpsUtils mUdfpsUtils;
|
||||
|
||||
private int mProgressBarRadius;
|
||||
|
||||
private Rect mSensorRect;
|
||||
private UdfpsOverlayParams mOverlayParams;
|
||||
private FingerprintSensorPropertiesInternal mSensorProperties;
|
||||
|
||||
private int mTotalSteps = -1;
|
||||
private int mRemainingSteps = -1;
|
||||
private int mLocationsEnrolled = 0;
|
||||
private int mCenterTouchCount = 0;
|
||||
|
||||
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
|
||||
mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, attrs);
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mUdfpsUtils = new UdfpsUtils();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
ImageView fingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
|
||||
fingerprintView.setImageDrawable(mFingerprintDrawable);
|
||||
mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
|
||||
mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive enroll progress information from FingerprintEnrollEnrollingUdfpsFragment
|
||||
*/
|
||||
public void onEnrollmentProgress(int remaining, int totalSteps) {
|
||||
if (mTotalSteps == -1) {
|
||||
mTotalSteps = totalSteps;
|
||||
}
|
||||
mRemainingSteps = remaining;
|
||||
mHandler.post(() -> {
|
||||
mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||
mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive enroll help information from FingerprintEnrollEnrollingUdfpsFragment
|
||||
*/
|
||||
public void onEnrollmentHelp() {
|
||||
mHandler.post(
|
||||
() -> mFingerprintProgressDrawable.onEnrollmentHelp(mRemainingSteps, mTotalSteps));
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive onAcquired from FingerprintEnrollEnrollingUdfpsFragment
|
||||
*/
|
||||
public void onAcquired(boolean isAcquiredGood) {
|
||||
final boolean animateIfLastStepGood =
|
||||
isAcquiredGood && (mRemainingSteps <= 2 && mRemainingSteps >= 0);
|
||||
mHandler.post(() -> {
|
||||
onFingerUp();
|
||||
if (animateIfLastStepGood) mFingerprintProgressDrawable.onLastStepAcquired();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive onPointerDown from FingerprintEnrollEnrollingUdfpsFragment
|
||||
*/
|
||||
public void onPointerDown(int sensorId) {
|
||||
onFingerDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive onPointerUp from FingerprintEnrollEnrollingUdfpsFragment
|
||||
*/
|
||||
public void onPointerUp(int sensorId) {
|
||||
onFingerUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* setup SensorProperties
|
||||
*/
|
||||
public void setSensorProperties(FingerprintSensorPropertiesInternal properties) {
|
||||
mSensorProperties = properties;
|
||||
((ViewGroup) getParent()).getViewTreeObserver().addOnDrawListener(
|
||||
new ViewTreeObserver.OnDrawListener() {
|
||||
@Override
|
||||
public void onDraw() {
|
||||
updateOverlayParams();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onSensorRectUpdated() {
|
||||
updateDimensions();
|
||||
|
||||
// Updates sensor rect in relation to the overlay view
|
||||
mSensorRect.set(getPaddingX(), getPaddingY(),
|
||||
(mOverlayParams.getSensorBounds().width() + getPaddingX()),
|
||||
(mOverlayParams.getSensorBounds().height() + getPaddingY()));
|
||||
mFingerprintDrawable.onSensorRectUpdated(new RectF(mSensorRect));
|
||||
}
|
||||
|
||||
private void updateDimensions() {
|
||||
// Original sensorBounds assume portrait mode.
|
||||
final Rect rotatedBounds = new Rect(mOverlayParams.getSensorBounds());
|
||||
int rotation = mOverlayParams.getRotation();
|
||||
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
|
||||
RotationUtils.rotateBounds(
|
||||
rotatedBounds,
|
||||
mOverlayParams.getNaturalDisplayWidth(),
|
||||
mOverlayParams.getNaturalDisplayHeight(),
|
||||
rotation
|
||||
);
|
||||
}
|
||||
|
||||
RelativeLayout parent = ((RelativeLayout) getParent());
|
||||
final int[] coords = parent.getLocationOnScreen();
|
||||
final int parentLeft = coords[0];
|
||||
final int parentTop = coords[1];
|
||||
final int parentRight = parentLeft + parent.getWidth();
|
||||
final int parentBottom = parentTop + parent.getHeight();
|
||||
|
||||
|
||||
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getWidth(),
|
||||
getHeight());
|
||||
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||
params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||
params.topMargin = rotatedBounds.top - parentTop - getPaddingY();
|
||||
} else {
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
|
||||
params.rightMargin = parentRight - rotatedBounds.right - getPaddingX();
|
||||
params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||
} else {
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
|
||||
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
|
||||
params.bottomMargin = parentBottom - rotatedBounds.bottom - getPaddingY();
|
||||
params.leftMargin = rotatedBounds.left - parentLeft - getPaddingX();
|
||||
}
|
||||
}
|
||||
params.height = rotatedBounds.height() + 2 * getPaddingX();
|
||||
params.width = rotatedBounds.width() + 2 * getPaddingY();
|
||||
setLayoutParams(params);
|
||||
}
|
||||
|
||||
private void onFingerDown() {
|
||||
mFingerprintDrawable.setShouldSkipDraw(true);
|
||||
mFingerprintDrawable.invalidateSelf();
|
||||
}
|
||||
|
||||
private void onFingerUp() {
|
||||
mFingerprintDrawable.setShouldSkipDraw(false);
|
||||
mFingerprintDrawable.invalidateSelf();
|
||||
}
|
||||
|
||||
private int getPaddingX() {
|
||||
return mProgressBarRadius;
|
||||
}
|
||||
|
||||
private int getPaddingY() {
|
||||
return mProgressBarRadius;
|
||||
}
|
||||
|
||||
private void updateOverlayParams() {
|
||||
|
||||
if (mSensorProperties == null) {
|
||||
android.util.Log.e(TAG, "There is no sensor info!");
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayInfo displayInfo = new DisplayInfo();
|
||||
getDisplay().getDisplayInfo(displayInfo);
|
||||
Rect udfpsBounds = mSensorProperties.getLocation().getRect();
|
||||
float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
|
||||
udfpsBounds.scale(scaleFactor);
|
||||
|
||||
final Rect overlayBounds = new Rect(
|
||||
0, /* left */
|
||||
displayInfo.getNaturalHeight() / 2, /* top */
|
||||
displayInfo.getNaturalWidth(), /* right */
|
||||
displayInfo.getNaturalHeight() /* botom */);
|
||||
|
||||
mOverlayParams = new UdfpsOverlayParams(
|
||||
udfpsBounds,
|
||||
overlayBounds,
|
||||
displayInfo.getNaturalWidth(),
|
||||
displayInfo.getNaturalHeight(),
|
||||
scaleFactor,
|
||||
displayInfo.rotation);
|
||||
|
||||
|
||||
post(() -> {
|
||||
mProgressBarRadius =
|
||||
(int) (mOverlayParams.getScaleFactor() * getContext().getResources().getInteger(
|
||||
R.integer.config_udfpsEnrollProgressBar));
|
||||
mSensorRect = new Rect(mOverlayParams.getSensorBounds());
|
||||
|
||||
onSensorRectUpdated();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,11 +78,7 @@ public class DataSaverSummary extends SettingsPreferenceFragment
|
||||
|
||||
addPreferencesFromResource(R.xml.data_saver);
|
||||
mUnrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS);
|
||||
mApplicationsState = ApplicationsState.getInstance(
|
||||
(Application) getContext().getApplicationContext());
|
||||
mDataSaverBackend = new DataSaverBackend(getContext());
|
||||
mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);
|
||||
mSession = mApplicationsState.newSession(this, getSettingsLifecycle());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,6 +32,8 @@ import java.util.Set;
|
||||
|
||||
/** Wraps the battery usage diff data for each entry used for battery usage app list. */
|
||||
public class BatteryDiffData {
|
||||
static final double SMALL_PERCENTAGE_THRESHOLD = 1f;
|
||||
|
||||
private final List<BatteryDiffEntry> mAppEntries;
|
||||
private final List<BatteryDiffEntry> mSystemEntries;
|
||||
|
||||
@@ -52,8 +54,8 @@ public class BatteryDiffData {
|
||||
combineBatteryDiffEntry(context, featureProvider, systemAppsSet);
|
||||
}
|
||||
|
||||
setTotalConsumePower();
|
||||
sortEntries();
|
||||
processAndSortEntries(mAppEntries);
|
||||
processAndSortEntries(mSystemEntries);
|
||||
}
|
||||
|
||||
public List<BatteryDiffEntry> getAppDiffEntryList() {
|
||||
@@ -77,18 +79,6 @@ public class BatteryDiffData {
|
||||
combineSystemItemsIntoOthers(context, featureProvider, mSystemEntries);
|
||||
}
|
||||
|
||||
/** Sets total consume power for app and system entries separately. */
|
||||
private void setTotalConsumePower() {
|
||||
setTotalConsumePowerForAllEntries(mAppEntries);
|
||||
setTotalConsumePowerForAllEntries(mSystemEntries);
|
||||
}
|
||||
|
||||
/** Sorts entries based on consumed percentage. */
|
||||
private void sortEntries() {
|
||||
Collections.sort(mAppEntries, BatteryDiffEntry.COMPARATOR);
|
||||
Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
|
||||
}
|
||||
|
||||
private static void purgeBatteryDiffData(
|
||||
final PowerUsageFeatureProvider featureProvider,
|
||||
final List<BatteryDiffEntry> entries) {
|
||||
@@ -177,18 +167,6 @@ public class BatteryDiffData {
|
||||
}
|
||||
}
|
||||
|
||||
// Sets total consume power for each entry.
|
||||
private static void setTotalConsumePowerForAllEntries(
|
||||
final List<BatteryDiffEntry> batteryDiffEntries) {
|
||||
double totalConsumePower = 0.0;
|
||||
for (BatteryDiffEntry batteryDiffEntry : batteryDiffEntries) {
|
||||
totalConsumePower += batteryDiffEntry.mConsumePower;
|
||||
}
|
||||
for (BatteryDiffEntry batteryDiffEntry : batteryDiffEntries) {
|
||||
batteryDiffEntry.setTotalConsumePower(totalConsumePower);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean needsCombineInSystemApp(final BatteryDiffEntry batteryDiffEntry,
|
||||
final List<String> systemAppsAllowlist, final Set<String> systemAppsSet) {
|
||||
@@ -207,4 +185,51 @@ public class BatteryDiffData {
|
||||
|
||||
return systemAppsSet != null && systemAppsSet.contains(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets total consume power, and adjusts the percentages to ensure the total round percentage
|
||||
* could be 100%, and then sorts entries based on the sorting key.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static void processAndSortEntries(final List<BatteryDiffEntry> batteryDiffEntries) {
|
||||
if (batteryDiffEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sets total consume power.
|
||||
double totalConsumePower = 0.0;
|
||||
for (BatteryDiffEntry batteryDiffEntry : batteryDiffEntries) {
|
||||
totalConsumePower += batteryDiffEntry.mConsumePower;
|
||||
}
|
||||
for (BatteryDiffEntry batteryDiffEntry : batteryDiffEntries) {
|
||||
batteryDiffEntry.setTotalConsumePower(totalConsumePower);
|
||||
}
|
||||
|
||||
// Adjusts percentages to show.
|
||||
// The lower bound is treating all the small percentages as 0.
|
||||
// The upper bound is treating all the small percentages as 1.
|
||||
int totalLowerBound = 0;
|
||||
int totalUpperBound = 0;
|
||||
for (BatteryDiffEntry entry : batteryDiffEntries) {
|
||||
if (entry.getPercentage() < SMALL_PERCENTAGE_THRESHOLD) {
|
||||
totalUpperBound += 1;
|
||||
} else {
|
||||
int roundPercentage = Math.round((float) entry.getPercentage());
|
||||
totalLowerBound += roundPercentage;
|
||||
totalUpperBound += roundPercentage;
|
||||
}
|
||||
}
|
||||
if (totalLowerBound > 100 || totalUpperBound < 100) {
|
||||
Collections.sort(batteryDiffEntries, BatteryDiffEntry.COMPARATOR);
|
||||
for (int i = 0; i < totalLowerBound - 100 && i < batteryDiffEntries.size(); i++) {
|
||||
batteryDiffEntries.get(i).setAdjustPercentageOffset(-1);
|
||||
}
|
||||
for (int i = 0; i < 100 - totalUpperBound && i < batteryDiffEntries.size(); i++) {
|
||||
batteryDiffEntries.get(i).setAdjustPercentageOffset(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts entries.
|
||||
Collections.sort(batteryDiffEntries, BatteryDiffEntry.COMPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class BatteryDiffEntry {
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||
public static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
|
||||
|
||||
/** A comparator for {@link BatteryDiffEntry} based on consumed percentage. */
|
||||
/** A comparator for {@link BatteryDiffEntry} based on the sorting key. */
|
||||
public static final Comparator<BatteryDiffEntry> COMPARATOR =
|
||||
(a, b) -> Double.compare(b.getSortingKey(), a.getSortingKey());
|
||||
|
||||
@@ -65,7 +65,8 @@ public class BatteryDiffEntry {
|
||||
protected Context mContext;
|
||||
|
||||
private double mTotalConsumePower;
|
||||
private double mPercentOfTotal;
|
||||
private double mPercentage;
|
||||
private int mAdjustPercentageOffset;
|
||||
private UserManager mUserManager;
|
||||
private String mDefaultPackageName = null;
|
||||
|
||||
@@ -107,8 +108,9 @@ public class BatteryDiffEntry {
|
||||
/** Sets the total consumed power in a specific time slot. */
|
||||
public void setTotalConsumePower(double totalConsumePower) {
|
||||
mTotalConsumePower = totalConsumePower;
|
||||
mPercentOfTotal = totalConsumePower == 0
|
||||
mPercentage = totalConsumePower == 0
|
||||
? 0 : (mConsumePower / mTotalConsumePower) * 100.0;
|
||||
mAdjustPercentageOffset = 0;
|
||||
}
|
||||
|
||||
/** Gets the total consumed power in a specific time slot. */
|
||||
@@ -117,13 +119,23 @@ public class BatteryDiffEntry {
|
||||
}
|
||||
|
||||
/** Gets the percentage of total consumed power. */
|
||||
public double getPercentOfTotal() {
|
||||
return mPercentOfTotal;
|
||||
public double getPercentage() {
|
||||
return mPercentage;
|
||||
}
|
||||
|
||||
/** Gets the percentage offset to adjust. */
|
||||
public double getAdjustPercentageOffset() {
|
||||
return mAdjustPercentageOffset;
|
||||
}
|
||||
|
||||
/** Sets the percentage offset to adjust. */
|
||||
public void setAdjustPercentageOffset(int offset) {
|
||||
mAdjustPercentageOffset = offset;
|
||||
}
|
||||
|
||||
/** Gets the key for sorting */
|
||||
public double getSortingKey() {
|
||||
return getPercentOfTotal();
|
||||
return getPercentage() + getAdjustPercentageOffset();
|
||||
}
|
||||
|
||||
/** Clones a new instance. */
|
||||
@@ -369,7 +381,7 @@ public class BatteryDiffEntry {
|
||||
.append(String.format("\n\tname=%s restrictable=%b",
|
||||
mAppLabel, mValidForRestriction))
|
||||
.append(String.format("\n\tconsume=%.2f%% %f/%f",
|
||||
mPercentOfTotal, mConsumePower, mTotalConsumePower))
|
||||
mPercentage, mConsumePower, mTotalConsumePower))
|
||||
.append(String.format("\n\tconsume power= foreground:%f foregroundService:%f",
|
||||
mForegroundUsageConsumePower, mForegroundServiceUsageConsumePower))
|
||||
.append(String.format("\n\tconsume power= background:%f cached:%f",
|
||||
|
||||
@@ -35,6 +35,7 @@ import androidx.preference.PreferenceScreen;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
import com.android.settings.core.InstrumentedPreferenceFragment;
|
||||
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
|
||||
@@ -86,6 +87,8 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
FooterPreference mFooterPreference;
|
||||
@VisibleForTesting
|
||||
BatteryDiffData mBatteryDiffData;
|
||||
@VisibleForTesting
|
||||
String mPercentLessThanThresholdText;
|
||||
|
||||
public BatteryUsageBreakdownController(
|
||||
Context context, Lifecycle lifecycle, SettingsActivity activity,
|
||||
@@ -147,11 +150,11 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
: SettingsEnums.ACTION_BATTERY_USAGE_SYSTEM_ITEM,
|
||||
/* pageId */ SettingsEnums.OPEN_BATTERY_USAGE,
|
||||
TextUtils.isEmpty(packageName) ? PACKAGE_NAME_NONE : packageName,
|
||||
(int) Math.round(diffEntry.getPercentOfTotal()));
|
||||
(int) Math.round(diffEntry.getPercentage()));
|
||||
Log.d(TAG, String.format("handleClick() label=%s key=%s package=%s",
|
||||
diffEntry.getAppLabel(), histEntry.getKey(), histEntry.mPackageName));
|
||||
AdvancedPowerUsageDetail.startBatteryDetailPage(
|
||||
mActivity, mFragment, diffEntry, powerPref.getPercent(), mSlotTimestamp);
|
||||
mActivity, mFragment, diffEntry, powerPref.getPercentage(), mSlotTimestamp);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -163,6 +166,9 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
mSpinnerPreference = screen.findPreference(SPINNER_PREFERENCE_KEY);
|
||||
mAppListPreferenceGroup = screen.findPreference(APP_LIST_PREFERENCE_KEY);
|
||||
mFooterPreference = screen.findPreference(FOOTER_PREFERENCE_KEY);
|
||||
mPercentLessThanThresholdText = mPrefContext.getString(
|
||||
R.string.battery_usage_less_than_percent,
|
||||
Utils.formatPercentage(BatteryDiffData.SMALL_PERCENTAGE_THRESHOLD, false));
|
||||
|
||||
mAppListPreferenceGroup.setOrderingAsAdded(false);
|
||||
mSpinnerPreference.initializeSpinner(
|
||||
@@ -279,11 +285,11 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
pref.setIcon(appIcon);
|
||||
pref.setTitle(appLabel);
|
||||
pref.setOrder(prefIndex);
|
||||
pref.setPercent(entry.getPercentOfTotal());
|
||||
pref.setSingleLineTitle(true);
|
||||
// Sets the BatteryDiffEntry to preference for launching detailed page.
|
||||
pref.setBatteryDiffEntry(entry);
|
||||
pref.setSelectable(entry.validForRestriction());
|
||||
setPreferencePercentage(pref, entry);
|
||||
setPreferenceSummary(pref, entry);
|
||||
if (!isAdded) {
|
||||
mAppListPreferenceGroup.addPreference(pref);
|
||||
@@ -307,6 +313,17 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
|
||||
mAppListPreferenceGroup.removeAll();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setPreferencePercentage(
|
||||
PowerGaugePreference preference, BatteryDiffEntry entry) {
|
||||
preference.setPercentage(
|
||||
entry.getPercentage() < BatteryDiffData.SMALL_PERCENTAGE_THRESHOLD
|
||||
? mPercentLessThanThresholdText
|
||||
: Utils.formatPercentage(
|
||||
entry.getPercentage() + entry.getAdjustPercentageOffset(),
|
||||
/* round= */ true));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setPreferenceSummary(
|
||||
PowerGaugePreference preference, BatteryDiffEntry entry) {
|
||||
|
||||
@@ -38,7 +38,6 @@ import com.android.settingslib.widget.AppPreference;
|
||||
*/
|
||||
public class PowerGaugePreference extends AppPreference {
|
||||
|
||||
private static final double PERCENTAGE_TO_SHOW_THRESHOLD = 1f;
|
||||
// Please see go/battery-usage-app-list-alpha
|
||||
private static final float SELECTABLE_ALPHA = 1f;
|
||||
private static final float UNSELECTABLE_ALPHA_LIGHT_MODE = 0.65f;
|
||||
@@ -81,29 +80,17 @@ public class PowerGaugePreference extends AppPreference {
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
/** Sets the percent of total. */
|
||||
public void setPercent(double percentOfTotal) {
|
||||
mProgress = percentOfTotal < PERCENTAGE_TO_SHOW_THRESHOLD
|
||||
? "-" : Utils.formatPercentage(percentOfTotal, true);
|
||||
/** Sets the percentage to show. */
|
||||
public void setPercentage(CharSequence percentage) {
|
||||
mProgress = percentage;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
/** Gets the percent of total. */
|
||||
public String getPercent() {
|
||||
/** Gets the percentage to show. */
|
||||
public String getPercentage() {
|
||||
return mProgress.toString();
|
||||
}
|
||||
|
||||
/** Sets the subtitle. */
|
||||
public void setSubtitle(CharSequence subtitle) {
|
||||
mProgress = subtitle;
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
/** Gets the subtitle. */
|
||||
public CharSequence getSubtitle() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
/** Sets whether to show anomaly icon */
|
||||
public void shouldShowAnomalyIcon(boolean showAnomalyIcon) {
|
||||
mShowAnomalyIcon = showAnomalyIcon;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.settings.regionalpreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemProperties;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.android.settings.core.BasePreferenceController;
|
||||
@@ -27,6 +28,8 @@ import java.util.Locale;
|
||||
* A controller for the entry of Calendar types' page
|
||||
*/
|
||||
public class CalendarTypeController extends BasePreferenceController {
|
||||
private static final String CALENDAR_FEATURE_PROPERTY =
|
||||
"i18n-feature-locale-preference-calendar";
|
||||
private static final String TAG = CalendarTypeController.class.getSimpleName();
|
||||
public CalendarTypeController(Context context, String preferenceKey) {
|
||||
super(context, preferenceKey);
|
||||
@@ -45,7 +48,8 @@ public class CalendarTypeController extends BasePreferenceController {
|
||||
*/
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
return SystemProperties.getBoolean(CALENDAR_FEATURE_PROPERTY, false)
|
||||
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -108,7 +108,7 @@ private class AppBatteryPresenter(private val context: Context, private val app:
|
||||
private fun BatteryDiffEntry?.getSummary(): String =
|
||||
this?.takeIf { mConsumePower > 0 }?.let {
|
||||
context.getString(
|
||||
R.string.battery_summary, Utils.formatPercentage(percentOfTotal, true)
|
||||
R.string.battery_summary, Utils.formatPercentage(percentage, true)
|
||||
)
|
||||
} ?: context.getString(R.string.no_battery_summary)
|
||||
|
||||
@@ -127,7 +127,7 @@ private class AppBatteryPresenter(private val context: Context, private val app:
|
||||
context,
|
||||
AppInfoSettingsProvider.METRICS_CATEGORY,
|
||||
this,
|
||||
Utils.formatPercentage(percentOfTotal, true),
|
||||
Utils.formatPercentage(percentage, true),
|
||||
null,
|
||||
false,
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@@ -405,6 +406,17 @@ public class ToggleScreenMagnificationPreferenceFragmentTest {
|
||||
assertThat(mFragment.getHelpResource()).isEqualTo(R.string.help_url_magnification);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onProcessArguments_defaultArgumentUnavailable_shouldSetDefaultArguments() {
|
||||
Bundle arguments = new Bundle();
|
||||
|
||||
mFragment.onProcessArguments(arguments);
|
||||
|
||||
assertTrue(arguments.containsKey(AccessibilitySettings.EXTRA_PREFERENCE_KEY));
|
||||
assertTrue(arguments.containsKey(AccessibilitySettings.EXTRA_INTRO));
|
||||
assertTrue(arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION));
|
||||
}
|
||||
|
||||
private void putStringIntoSettings(String key, String componentName) {
|
||||
Settings.Secure.putString(mContext.getContentResolver(), key, componentName);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ public class AppBatteryPreferenceControllerTest {
|
||||
mController.displayPreference(mScreen);
|
||||
mBatteryDiffEntry.mConsumePower = 1;
|
||||
mController.mBatteryDiffEntry = mBatteryDiffEntry;
|
||||
when(mBatteryDiffEntry.getPercentOfTotal()).thenReturn(60.0);
|
||||
when(mBatteryDiffEntry.getPercentage()).thenReturn(60.0);
|
||||
|
||||
mController.updateBatteryWithDiffEntry();
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -58,26 +59,10 @@ public class BatteryDiffDataTest {
|
||||
|
||||
@Test
|
||||
public void needsCombineInSystemApp_isHidden_returnTrue() {
|
||||
final int currentUserId = mContext.getUserId();
|
||||
final BatteryHistEntry hiddenHistEntry = createBatteryHistEntry(
|
||||
ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0,
|
||||
/*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0,
|
||||
/*backgroundUsageConsumePower=*/ 0, /*cachedUsageConsumePower=*/ 0,
|
||||
/*uid=*/ 0L, currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
|
||||
/*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L, true);
|
||||
final BatteryDiffEntry hiddenDiffEntry = new BatteryDiffEntry(
|
||||
mContext,
|
||||
/*foregroundUsageTimeInMs=*/ 0,
|
||||
/*backgroundUsageTimeInMs=*/ 0,
|
||||
/*screenOnTimeInMs=*/ 0,
|
||||
/*consumePower=*/ 0,
|
||||
/*foregroundUsageConsumePower=*/ 0,
|
||||
/*foregroundServiceUsageConsumePower=*/ 0,
|
||||
/*backgroundUsageConsumePower=*/ 0,
|
||||
/*cachedUsageConsumePower=*/ 0,
|
||||
hiddenHistEntry);
|
||||
final BatteryDiffEntry hiddenDiffEntry =
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 0, /*isHidden=*/ true);
|
||||
|
||||
boolean needsCombineInSystemApp = BatteryDiffData.needsCombineInSystemApp(
|
||||
final boolean needsCombineInSystemApp = BatteryDiffData.needsCombineInSystemApp(
|
||||
hiddenDiffEntry, List.of(), Set.of());
|
||||
|
||||
assertThat(needsCombineInSystemApp).isTrue();
|
||||
@@ -85,29 +70,13 @@ public class BatteryDiffDataTest {
|
||||
|
||||
@Test
|
||||
public void needsCombineInSystemApp_isSystemApp_returnTrue() {
|
||||
final int currentUserId = mContext.getUserId();
|
||||
final BatteryHistEntry batteryHistEntry = createBatteryHistEntry(
|
||||
ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0,
|
||||
/*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0,
|
||||
/*backgroundUsageConsumePower=*/ 0, /*cachedUsageConsumePower=*/ 0,
|
||||
/*uid=*/ 0L, currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
|
||||
/*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L, false);
|
||||
final BatteryDiffEntry batteryDiffEntry = new BatteryDiffEntry(
|
||||
mContext,
|
||||
/*foregroundUsageTimeInMs=*/ 0,
|
||||
/*backgroundUsageTimeInMs=*/ 0,
|
||||
/*screenOnTimeInMs=*/ 0,
|
||||
/*consumePower=*/ 0,
|
||||
/*foregroundUsageConsumePower=*/ 0,
|
||||
/*foregroundServiceUsageConsumePower=*/ 0,
|
||||
/*backgroundUsageConsumePower=*/ 0,
|
||||
/*cachedUsageConsumePower=*/ 0,
|
||||
batteryHistEntry);
|
||||
final BatteryDiffEntry batteryDiffEntry =
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 0, /*isHidden=*/ false);
|
||||
doReturn(mAppEntry).when(mApplicationsState).getEntry(anyString(), anyInt());
|
||||
mAppEntry.info = mApplicationInfo;
|
||||
mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
|
||||
boolean needsCombineInSystemApp = BatteryDiffData.needsCombineInSystemApp(
|
||||
final boolean needsCombineInSystemApp = BatteryDiffData.needsCombineInSystemApp(
|
||||
batteryDiffEntry, List.of(), Set.of(ConvertUtils.FAKE_PACKAGE_NAME));
|
||||
|
||||
assertThat(needsCombineInSystemApp).isTrue();
|
||||
@@ -115,32 +84,78 @@ public class BatteryDiffDataTest {
|
||||
|
||||
@Test
|
||||
public void needsCombineInSystemApp_notSystemApp_returnFalse() {
|
||||
final int currentUserId = mContext.getUserId();
|
||||
final BatteryDiffEntry batteryDiffEntry =
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 0, /*isHidden=*/ false);
|
||||
doReturn(mAppEntry).when(mApplicationsState).getEntry(anyString(), anyInt());
|
||||
mAppEntry.info = mApplicationInfo;
|
||||
mApplicationInfo.flags = 0;
|
||||
|
||||
final boolean needsCombineInSystemApp = BatteryDiffData.needsCombineInSystemApp(
|
||||
batteryDiffEntry, List.of(), Set.of());
|
||||
|
||||
assertThat(needsCombineInSystemApp).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processPercentsAndSort_sumLessThan100_adjustTo100() {
|
||||
List<BatteryDiffEntry> batteryDiffEntries = new ArrayList<>();
|
||||
batteryDiffEntries.add(
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 33.33, /*isHidden=*/ false));
|
||||
batteryDiffEntries.add(
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 33.34, /*isHidden=*/ false));
|
||||
batteryDiffEntries.add(
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 33.33, /*isHidden=*/ false));
|
||||
|
||||
BatteryDiffData.processAndSortEntries(batteryDiffEntries);
|
||||
|
||||
assertThat(batteryDiffEntries.get(0).getPercentage()).isEqualTo(33.34);
|
||||
assertThat(batteryDiffEntries.get(0).getAdjustPercentageOffset()).isEqualTo(1);
|
||||
assertThat(batteryDiffEntries.get(1).getPercentage()).isEqualTo(33.33);
|
||||
assertThat(batteryDiffEntries.get(1).getAdjustPercentageOffset()).isEqualTo(0);
|
||||
assertThat(batteryDiffEntries.get(2).getPercentage()).isEqualTo(33.33);
|
||||
assertThat(batteryDiffEntries.get(2).getAdjustPercentageOffset()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processPercentsAndSort_sumGreaterThan100_adjustTo100() {
|
||||
List<BatteryDiffEntry> batteryDiffEntries = new ArrayList<>();
|
||||
batteryDiffEntries.add(
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 48.5, /*isHidden=*/ false));
|
||||
batteryDiffEntries.add(
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 3, /*isHidden=*/ false));
|
||||
batteryDiffEntries.add(
|
||||
createBatteryDiffEntry(mContext, /*consumePower=*/ 48.5, /*isHidden=*/ false));
|
||||
|
||||
BatteryDiffData.processAndSortEntries(batteryDiffEntries);
|
||||
|
||||
assertThat(batteryDiffEntries.get(0).getPercentage()).isEqualTo(48.5);
|
||||
assertThat(batteryDiffEntries.get(0).getAdjustPercentageOffset()).isEqualTo(0);
|
||||
assertThat(batteryDiffEntries.get(1).getPercentage()).isEqualTo(48.5);
|
||||
assertThat(batteryDiffEntries.get(1).getAdjustPercentageOffset()).isEqualTo(-1);
|
||||
assertThat(batteryDiffEntries.get(2).getPercentage()).isEqualTo(3);
|
||||
assertThat(batteryDiffEntries.get(2).getAdjustPercentageOffset()).isEqualTo(0);
|
||||
}
|
||||
|
||||
private static BatteryDiffEntry createBatteryDiffEntry(
|
||||
Context context, double consumePower, boolean isHidden) {
|
||||
final int currentUserId = context.getUserId();
|
||||
final BatteryHistEntry batteryHistEntry = createBatteryHistEntry(
|
||||
ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", /*consumePower=*/ 0,
|
||||
ConvertUtils.FAKE_PACKAGE_NAME, "fake_label", consumePower,
|
||||
/*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0,
|
||||
/*backgroundUsageConsumePower=*/ 0, /*cachedUsageConsumePower=*/ 0,
|
||||
/*uid=*/ 0L, currentUserId, ConvertUtils.CONSUMER_TYPE_UID_BATTERY,
|
||||
/*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L, false);
|
||||
final BatteryDiffEntry batteryDiffEntry = new BatteryDiffEntry(
|
||||
mContext,
|
||||
/*foregroundUsageTimeInMs=*/ 0L, /*backgroundUsageTimeInMs=*/ 0L, isHidden);
|
||||
return new BatteryDiffEntry(
|
||||
context,
|
||||
/*foregroundUsageTimeInMs=*/ 0,
|
||||
/*backgroundUsageTimeInMs=*/ 0,
|
||||
/*screenOnTimeInMs=*/ 0,
|
||||
/*consumePower=*/ 0,
|
||||
/*consumePower=*/ consumePower,
|
||||
/*foregroundUsageConsumePower=*/ 0,
|
||||
/*foregroundServiceUsageConsumePower=*/ 0,
|
||||
/*backgroundUsageConsumePower=*/ 0,
|
||||
/*cachedUsageConsumePower=*/ 0,
|
||||
batteryHistEntry);
|
||||
doReturn(mAppEntry).when(mApplicationsState).getEntry(anyString(), anyInt());
|
||||
mAppEntry.info = mApplicationInfo;
|
||||
mApplicationInfo.flags = 0;
|
||||
|
||||
boolean needsCombineInSystemApp = BatteryDiffData.needsCombineInSystemApp(
|
||||
batteryDiffEntry, List.of(), Set.of());
|
||||
|
||||
assertThat(needsCombineInSystemApp).isFalse();
|
||||
}
|
||||
|
||||
private static BatteryHistEntry createBatteryHistEntry(
|
||||
|
||||
@@ -106,7 +106,7 @@ public final class BatteryDiffEntryTest {
|
||||
/*batteryHistEntry=*/ null);
|
||||
entry.setTotalConsumePower(100.0);
|
||||
|
||||
assertThat(entry.getPercentOfTotal()).isEqualTo(22.0);
|
||||
assertThat(entry.getPercentage()).isEqualTo(22.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -125,21 +125,28 @@ public final class BatteryDiffEntryTest {
|
||||
/*batteryHistEntry=*/ null);
|
||||
entry.setTotalConsumePower(0);
|
||||
|
||||
assertThat(entry.getPercentOfTotal()).isEqualTo(0);
|
||||
assertThat(entry.getPercentage()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComparator_sortCollectionsInDescOrder() {
|
||||
final List<BatteryDiffEntry> entryList = new ArrayList<>();
|
||||
// Generates fake testing data.
|
||||
BatteryDiffEntry systemAppsBatteryDiffEntry =
|
||||
new BatteryDiffEntry.SystemAppsBatteryDiffEntry(mContext);
|
||||
systemAppsBatteryDiffEntry.mConsumePower = 16;
|
||||
systemAppsBatteryDiffEntry.setTotalConsumePower(100);
|
||||
entryList.add(systemAppsBatteryDiffEntry);
|
||||
entryList.add(createBatteryDiffEntry(30, mBatteryHistEntry));
|
||||
entryList.add(createBatteryDiffEntry(20, mBatteryHistEntry));
|
||||
entryList.add(createBatteryDiffEntry(10, mBatteryHistEntry));
|
||||
|
||||
Collections.sort(entryList, BatteryDiffEntry.COMPARATOR);
|
||||
|
||||
assertThat(entryList.get(0).getPercentOfTotal()).isEqualTo(30);
|
||||
assertThat(entryList.get(1).getPercentOfTotal()).isEqualTo(20);
|
||||
assertThat(entryList.get(2).getPercentOfTotal()).isEqualTo(10);
|
||||
assertThat(entryList.get(0).getPercentage()).isEqualTo(30);
|
||||
assertThat(entryList.get(1).getPercentage()).isEqualTo(20);
|
||||
assertThat(entryList.get(2).getPercentage()).isEqualTo(10);
|
||||
assertThat(entryList.get(3).getPercentage()).isEqualTo(16);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -234,6 +234,40 @@ public final class BatteryUsageBreakdownControllerTest {
|
||||
/* percentage of total */ 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferencePercent_lessThanThreshold_expectedFormat() {
|
||||
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
|
||||
final BatteryDiffEntry batteryDiffEntry = createBatteryDiffEntry(
|
||||
/*isSystem=*/ true,
|
||||
/*screenOnTimeInMs=*/ 0,
|
||||
/*foregroundUsageTimeInMs=*/ 0,
|
||||
/*backgroundUsageTimeInMs=*/ 0);
|
||||
batteryDiffEntry.mConsumePower = 0.8;
|
||||
batteryDiffEntry.setTotalConsumePower(100);
|
||||
mBatteryUsageBreakdownController.mPercentLessThanThresholdText = "< 1%";
|
||||
|
||||
mBatteryUsageBreakdownController.setPreferencePercentage(pref, batteryDiffEntry);
|
||||
|
||||
assertThat(pref.getPercentage()).isEqualTo("< 1%");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferencePercent_greaterThanThreshold_expectedFormat() {
|
||||
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
|
||||
final BatteryDiffEntry batteryDiffEntry = createBatteryDiffEntry(
|
||||
/*isSystem=*/ true,
|
||||
/*screenOnTimeInMs=*/ 0,
|
||||
/*foregroundUsageTimeInMs=*/ 0,
|
||||
/*backgroundUsageTimeInMs=*/ 0);
|
||||
batteryDiffEntry.mConsumePower = 16;
|
||||
batteryDiffEntry.setTotalConsumePower(100);
|
||||
mBatteryUsageBreakdownController.mPercentLessThanThresholdText = "< 1%";
|
||||
|
||||
mBatteryUsageBreakdownController.setPreferencePercentage(pref, batteryDiffEntry);
|
||||
|
||||
assertThat(pref.getPercentage()).isEqualTo("16%");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPreferenceSummary_systemEntryTotalUsageTimeIsZero_emptySummary() {
|
||||
final PowerGaugePreference pref = new PowerGaugePreference(mContext);
|
||||
|
||||
@@ -1993,7 +1993,7 @@ public final class DataProcessorTest {
|
||||
assertThat(entry.mBatteryHistEntry.mUserId).isEqualTo(userId);
|
||||
assertThat(entry.mBatteryHistEntry.mUid).isEqualTo(uid);
|
||||
assertThat(entry.mBatteryHistEntry.mConsumerType).isEqualTo(consumerType);
|
||||
assertThat(entry.getPercentOfTotal()).isEqualTo(consumePercentage);
|
||||
assertThat(entry.getPercentage()).isEqualTo(consumePercentage);
|
||||
assertThat(entry.mForegroundUsageConsumePower).isEqualTo(foregroundUsageConsumePower);
|
||||
assertThat(entry.mForegroundServiceUsageConsumePower)
|
||||
.isEqualTo(foregroundServiceUsageConsumePower);
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.Shadows;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -56,8 +55,8 @@ public final class DatabaseUtilsTest {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Mock
|
||||
private PackageManager mPackageManager;
|
||||
@Mock private PackageManager mPackageManager;
|
||||
@Mock private UserManager mUserManager;
|
||||
@Mock private ContentResolver mMockContentResolver;
|
||||
@Mock private ContentResolver mMockContentResolver2;
|
||||
@Mock private BatteryUsageStats mBatteryUsageStats;
|
||||
@@ -383,7 +382,11 @@ public final class DatabaseUtilsTest {
|
||||
doReturn("com.fake.package").when(mContext).getPackageName();
|
||||
doReturn(mMockContext).when(mContext).createPackageContextAsUser(
|
||||
"com.fake.package", /*flags=*/ 0, UserHandle.OWNER);
|
||||
BatteryTestUtils.setWorkProfile(mContext);
|
||||
doReturn(UserHandle.CURRENT).when(mContext).getUser();
|
||||
doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
|
||||
doReturn(true).when(mUserManager).isManagedProfile();
|
||||
doReturn(UserHandle.SYSTEM).when(mUserManager).getProfileParent(UserHandle.CURRENT);
|
||||
|
||||
DatabaseUtils.sFakeBatteryStateSupplier = () -> getMatrixCursor();
|
||||
|
||||
final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
|
||||
|
||||
@@ -65,15 +65,6 @@ public class PowerGaugePreferenceTest {
|
||||
assertThat(mPowerGaugePreference.getLayoutResource()).isEqualTo(R.layout.preference_app);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBindViewHolder_bindSubtitle() {
|
||||
mPowerGaugePreference.setSubtitle(SUBTITLE);
|
||||
mPowerGaugePreference.onBindViewHolder(mPreferenceViewHolder);
|
||||
|
||||
TextView widgetSummary = (TextView) mPreferenceViewHolder.findViewById(R.id.widget_summary);
|
||||
assertThat(widgetSummary.getText()).isEqualTo(SUBTITLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnBindViewHolder_showAnomaly_bindAnomalyIcon() {
|
||||
mPowerGaugePreference.shouldShowAnomalyIcon(true);
|
||||
@@ -104,18 +95,4 @@ public class PowerGaugePreferenceTest {
|
||||
assertThat(mPreferenceViewHolder.findViewById(android.R.id.title).getContentDescription())
|
||||
.isEqualTo(CONTENT_DESCRIPTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPercent_greaterThanThreshold_showNumber() {
|
||||
mPowerGaugePreference.setPercent(99.5);
|
||||
|
||||
assertThat(mPowerGaugePreference.getPercent()).isEqualTo("100%");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPercent_lessThanThreshold_showBar() {
|
||||
mPowerGaugePreference.setPercent(0.95);
|
||||
|
||||
assertThat(mPowerGaugePreference.getPercent()).isEqualTo("-");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,6 @@ import com.android.settings.fuelgauge.AdvancedPowerUsageDetail
|
||||
import com.android.settings.fuelgauge.batteryusage.BatteryChartPreferenceController
|
||||
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry
|
||||
import com.android.settings.testutils.mockAsUser
|
||||
import com.android.settingslib.spaprivileged.framework.common.asUser
|
||||
import com.android.settingslib.spaprivileged.model.app.userHandle
|
||||
import com.android.settingslib.spaprivileged.model.app.userId
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
@@ -142,7 +140,7 @@ class AppBatteryPreferenceTest {
|
||||
val batteryDiffEntry = mock(BatteryDiffEntry::class.java).apply {
|
||||
mConsumePower = 12.3
|
||||
}
|
||||
whenever(batteryDiffEntry.percentOfTotal).thenReturn(45.6)
|
||||
whenever(batteryDiffEntry.percentage).thenReturn(45.6)
|
||||
mockBatteryDiffEntry(batteryDiffEntry)
|
||||
|
||||
setContent()
|
||||
@@ -153,7 +151,7 @@ class AppBatteryPreferenceTest {
|
||||
@Test
|
||||
fun whenClick_openDetailsPage() {
|
||||
val batteryDiffEntry = mock(BatteryDiffEntry::class.java)
|
||||
whenever(batteryDiffEntry.percentOfTotal).thenReturn(10.0)
|
||||
whenever(batteryDiffEntry.percentage).thenReturn(10.0)
|
||||
mockBatteryDiffEntry(batteryDiffEntry)
|
||||
|
||||
setContent()
|
||||
|
||||
Reference in New Issue
Block a user