From d668a888f048164f3a573313b35170621fb7b4b4 Mon Sep 17 00:00:00 2001 From: Himanshu Rawat Date: Tue, 19 Jul 2022 06:08:44 +0000 Subject: [PATCH 01/10] Don't cancel passkey entry pairing autonomously Bluetooth pairing is cancelled if the pairing popup is closed without explicitly accepting the pairing. However there is no way to explicitly accept the pairing when the local device displays for the passkey entry or pin key entry pairing method. As a result all passkey entry and pin key entry pairings, where the local device is displaying the value, are cancelled after the pairing is successful. If the BT stack has not completed the SDP search when the pairing is cancelled after successful pairing, it may result in removal of the bond information. Bug: 237757124 Test: Pair with BT HID keyboard; adb logcat | grep "BTPairingController: Pairing dialog canceled" Change-Id: Ifdb98c16084dd811eed68469e7df5d1913c6ace8 (cherry picked from commit aa1950fd801afc9502cbd482981d34079065e743) Merged-In: Ifdb98c16084dd811eed68469e7df5d1913c6ace8 --- .../bluetooth/BluetoothPairingDialogFragment.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java index 650267a4757..8dd00b389ed 100644 --- a/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothPairingDialogFragment.java @@ -86,8 +86,12 @@ public class BluetoothPairingDialogFragment extends InstrumentedDialogFragment i @Override public void onDestroy() { super.onDestroy(); - if (!mPositiveClicked) { - mPairingController.onCancel(); + if (mPairingController.getDialogType() + != BluetoothPairingController.DISPLAY_PASSKEY_DIALOG) { + /* Cancel pairing unless explicitly accepted by user */ + if (!mPositiveClicked) { + mPairingController.onCancel(); + } } } From 7e530dda39da543e3443c97e0cc0ff32b9e50478 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Fri, 29 Jul 2022 07:03:38 +0000 Subject: [PATCH 02/10] [Settings] Data warning & limit UI should be configuable when running out of limit Should access API which is irrelated to status of data limit. Changing from API isDataEnabledForApn(ApnSetting.TYPE_DEFAULT) into isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER). Bug: 223361190 Change-Id: Iada89de38e9db3706a944c7a5b5f8a3e87d415e5 Test: Manual --- src/com/android/settings/datausage/BillingCyclePreference.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/datausage/BillingCyclePreference.java b/src/com/android/settings/datausage/BillingCyclePreference.java index 116ed89b3c7..1bd2be8f078 100644 --- a/src/com/android/settings/datausage/BillingCyclePreference.java +++ b/src/com/android/settings/datausage/BillingCyclePreference.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.net.NetworkTemplate; import android.os.Bundle; import android.os.RemoteException; +import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.util.AttributeSet; @@ -78,7 +79,7 @@ public class BillingCyclePreference extends Preference try { setEnabled(mServices.mNetworkService.isBandwidthControlEnabled() && mServices.mTelephonyManager.createForSubscriptionId(mSubId) - .isDataEnabledForApn(ApnSetting.TYPE_DEFAULT) + .isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER) && mServices.mUserManager.isAdminUser()); } catch (RemoteException e) { setEnabled(false); From 935231bb97492951a5badb2acd31e042d76a6430 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sat, 6 Aug 2022 23:45:43 -0700 Subject: [PATCH 03/10] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ib47d9c013d2a1623f79b1438331154bdb25fc56b --- res/values-or/arrays.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-or/arrays.xml b/res/values-or/arrays.xml index 2bf5736b838..3418fa18cf8 100644 --- a/res/values-or/arrays.xml +++ b/res/values-or/arrays.xml @@ -478,7 +478,7 @@ "ସେବା (ଚାଲୁଛି)" "ସେବା (ରିଷ୍ଟାର୍ଟ ହେଉଛି)" "ପ୍ରାପକ" - "ହୋମ୍‌" + "ହୋମ" "ଶେଷ ଗତିବିଧି" "କ୍ୟାଶ୍ ହୋଇଥିବା (କାର୍ଯ୍ୟକଳାପ)" "କ୍ୟାଶ୍ ହୋଇଥିବା (କାର୍ଯ୍ୟକଳାପ କ୍ଲାଏଣ୍ଟ)" From 0476afd5029355b13c8fb635e655f1bd5b1b08ea Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sat, 6 Aug 2022 23:49:57 -0700 Subject: [PATCH 04/10] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I033876f448bc48999e8f23ecd84878e4579af8bd --- res/values-hi/strings.xml | 2 +- res/values-nb/strings.xml | 6 +++--- res/values-nl/strings.xml | 2 +- res/values-pt-rPT/strings.xml | 2 +- res/values-te/strings.xml | 12 ++++++------ res/values-uk/strings.xml | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index daa26b1e276..073845a9caa 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -523,7 +523,7 @@ "फ़िंगरप्रिंट जोड़ा गया" "फ़िंगरप्रिंट अनलॉक की सुविधा का इस्तेमाल, आपके टैबलेट को अनलॉक करने या ऐप्लिकेशन में इस बात की पुष्टि करने के लिए किया जाता है कि यह आप ही हैं. जितना ज़्यादा इसका इस्तेमाल किया जाता है, यह सुविधा उतनी ही बेहतर होती जाती है" "फ़िंगरप्रिंट अनलॉक की सुविधा का इस्तेमाल, आपके डिवाइस को अनलॉक करने और किसी ऐप्लिकेशन में आपकी पुष्टि के लिए जितना ज़्यादा किया जाता है, यह उतनी ही बेहतर होती जाती है" - "फ़िंगरप्रिंट अनलॉक की सुविधा का इस्तेमाल, आपके फ़ोन को अनलॉक करने और किसी ऐप्लिकेशन में आपकी पुष्टि के लिए जितना ज़्यादा किया जाता है, यह उतनी ही बेहतर होती जाती है" + "फ़िंगरप्रिंट अनलॉक की सुविधा का इस्तेमाल, आपके फ़ोन को अनलॉक करने या ऐप्लिकेशन में इस बात की पुष्टि करने के लिए किया जाता है कि यह आप ही हैं. जितना ज़्यादा इसका इस्तेमाल किया जाता है, यह सुविधा उतनी ही बेहतर होती जाती है" "इसे बाद में करें" "हटाएं, फिर दोबारा छुएं" "पहले अपनी उंगली के एक किनारे को सेंसर पर कुछ देर के लिए रखें और फिर दूसरी तरफ़ से भी ऐसा करें" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 6385144422e..fb62002d241 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -521,9 +521,9 @@ "Dette bidrar til å registrere mer av fingeravtrykket" "Registrerer fingeravtrykket: %d prosent" "Fingeravtrykket er lagt til" - "Opplåsing med fingeravtrykk fungerer bedre jo mer du bruker det til å låse opp enheten og bekrefte identiteten din i apper" + "Opplåsing med fingeravtrykk fungerer bedre jo mer du bruker det til å låse opp nettbrettet og bekrefte identiteten din i apper" "Opplåsing med fingeravtrykk fungerer bedre jo mer du bruker det til å låse opp enheten og bekrefte identiteten din i apper" - "Opplåsing med fingeravtrykk fungerer bedre jo mer du bruker det til å låse opp enheten og bekrefte identiteten din i apper" + "Opplåsing med fingeravtrykk fungerer bedre jo mer du bruker det til å låse opp telefonen og bekrefte identiteten din i apper" "Gjør det senere" "Løft fingeren og trykk igjen" "Plasser siden av fingeravtrykket på sensoren og hold. Bytt deretter til den andre siden." @@ -2548,7 +2548,7 @@ "Gjør skjermen mørkere enn den laveste lysstyrken på telefonen" "Gjør skjermen mørkere enn den laveste lysstyrken på nettbrettet" "Gjør skjermen mørkere så det blir mer komfortabelt å lese" - "Ekstra dimmet kan være nyttig når <ol> <li> standard laveste lysstyrke på telefonen fremdeles er for lys</li> <li> du bruker telefonen i mørke omgivelser, for eksempel om natten eller i et mørkt rom før leggetid</li> </ol>" + "Ekstra dimmet kan være nyttig når <ol> <li>&nbsp;standard laveste lysstyrke på telefonen fremdeles er for lys</li> <li>&nbsp;du bruker telefonen i mørke omgivelser, for eksempel om natten eller i et mørkt rom før leggetid</li> </ol>" "Ekstra dimmet kan være nyttig når <ol> <li> standard laveste lysstyrke på nettbrettet fremdeles er for lys</li> <li> du bruker nettbrettet i mørke omgivelser, for eksempel om natten eller i et mørkt rom før leggetid</li> </ol>" "Intensitet" "Mørkere" diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 45c0e5ea0bf..509866ab149 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -523,7 +523,7 @@ "Vingerafdruk toegevoegd" "Ontgrendelen met vingerafdruk wordt steeds beter naarmate je de functie vaker gebruikt om je tablet te ontgrendelen of in apps te bevestigen dat jij het bent" "Ontgrendelen met vingerafdruk wordt steeds beter naarmate je de functie vaker gebruikt om je apparaat te ontgrendelen of in apps te bevestigen dat jij het bent" - "Ontgrendelen met vingerafdruk wordt steeds beter naarmate je de functie vaker gebruikt om je telefoon te ontgrendelen of in apps te bevestigen dat jij het bent" + "Ontgrendelen met vingerafdruk wordt steeds beter naarmate je de functie vaker gebruikt om je telefoon te ontgrendelen of in apps te bevestigen dat jij het bent." "Later doen" "Raak de sensor opnieuw aan" "Leg de zijkant van je vingerafdruk op de sensor en houd vast. Doe dan hetzelfde aan de andere kant." diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 98cd7cb2cf8..ceb6f93be5b 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -1957,7 +1957,7 @@ "O PIN foi definido" "O padrão foi definido" "Defina uma palavra-passe para o Desbloqueio facial" - "Defina padrão para Desbloqueio facial" + "Defina padrão para᠆Desbloqueio facial" "Defina PIN para usar Desbloqueio facial" "Defina palavra-passe para usar rosto ou impressão digital" "Defina um padrão para usar o rosto ou a impressão digital" diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index d172f5f9a4a..f01f3e37c60 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -2723,7 +2723,7 @@ "Wi‑Fi ఆన్‌లో ఉండే సమయం" "Wi‑Fi ఆన్‌లో ఉండే సమయం" "బ్యాటరీ వినియోగం" - "చరిత్ర వివరాలు" + "హిస్టరీ వివరాలు" "బ్యాటరీ వినియోగం" "గత 24 గంటలలో వినియోగాన్ని చూడండి" "గత పూర్తి ఛార్జ్ నుండి వినియోగాన్ని చూడండి" @@ -3613,7 +3613,7 @@ "బ్యాటరీ సేవర్, పవర్ సేవర్, సేవర్" "అనుకూల ప్రాధాన్యతలు , అనుకూల బ్యాటరీ" "అక్షరక్రమం, నిఘంటువు, స్పెల్ చెక్, స్వీయ-దిద్దుబాటు" - "గుర్తింపు, ఇన్‌పుట్, ప్రసంగం, మాట్లాడటం, భాష, హ్యాండ్స్-ఫ్రీ, హ్యాండ్ ఫ్రీ, గుర్తింపు, అభ్యంతరకరం, పదం, ఆడియో, చరిత్ర, బ్లూటూత్ హెడ్‌సెట్" + "గుర్తింపు, ఇన్‌పుట్, ప్రసంగం, మాట్లాడటం, భాష, హ్యాండ్స్-ఫ్రీ, హ్యాండ్ ఫ్రీ, గుర్తింపు, అభ్యంతరకరం, పదం, ఆడియో, హిస్టరీ, బ్లూటూత్ హెడ్‌సెట్" "రేటు, భాష, ఆటోమేటిక్, చదివి వినిపించు, చదివి వినిపించడం, tts, యాక్సెసిబిలిటీ, స్క్రీన్ రీడర్, అంధులు" "గడియారం, మిలిటరీ" "రీసెట్ చేయి, పునరుద్ధరించు, ఫ్యాక్టరీ" @@ -3876,7 +3876,7 @@ "నోటిఫికేషన్‌ను తెరువు" "నోటిఫికేషన్‌లను తాత్కాలికంగా ఆపివేయడాన్ని అనుమతించండి" "సాధారణ నోటిఫికేషన్‌ల చిహ్నాలను దాచి పెట్టు" - "సాధారణ నోటిఫికేషన్‌లకు సంబంధించిన చిహ్నాలు స్టేటస్‌ పట్టీలో చూపబడవు" + "సాధారణ నోటిఫికేషన్‌లకు సంబంధించిన చిహ్నాలు స్టేటస్‌ బార్‌లో చూపబడవు" "యాప్ చిహ్నంపై నోటిఫికేషన్ డాట్" "ఇటీవలి సంభాషణల స్ట్రిప్‌ను చూపు" "బబుల్స్" @@ -3905,13 +3905,13 @@ "తీసివేయడానికి ఎడమకు, మెనూను చూపడానికి కుడికి స్వైప్ చేయండి" "అనుకూల నోటిఫికేషన్‌లు" "వీటిలో కూడా ప్రదర్శిస్తుంది" - "స్టేటస్‌ పట్టీ" + "స్టేటస్‌ బార్‌" "లాక్ స్క్రీన్" "అనుకూల నోటిఫికేషన్‌లు అనేవి ఎల్లప్పుడూ నిశబ్దంగానే వస్తాయి మరియు అవి ఎప్పుడూ కిందకు-లాగే షేడ్‌లో కనిపిస్తాయి" "కిందకు-లాగే షేడ్‌లో మాత్రమే ప్రదర్శింపబడుతుంది" "కిందకు-లాగే షేడ్ & లాక్ స్క్రీన్‌లో ప్రదర్శిస్తుంది" "కిందకు-లాగే షేడ్ & స్టేటస్‌ పట్టీ‌లో ప్రదర్శిస్తుంది" - "కిందకు-లాగే షేడ్, స్టేటస్‌ పట్టీ & లాక్ స్క్రీన్‌లో ప్రదర్శింపబడుతుంది" + "కిందకు-లాగే షేడ్, స్టేటస్‌ బార్‌ & లాక్ స్క్రీన్‌లో ప్రదర్శింపబడుతుంది" "స్టేటస్ బార్‌లో నిశ్శబ్ద నోటిఫికేషన్‌లను దాచు" "కాంతి మిణుకుమిణుకు అనేలా ఉంచు" "గోప్యత" @@ -5163,7 +5163,7 @@ "పరికర రూపం" "ఆటోమేటిక్" "నెట్‌వర్క్ పేరు" - "స్టేటస్‌ పట్టీలో నెట్‌వర్క్ పేరును ప్రదర్శిస్తుంది" + "స్టేటస్‌ బార్‌లో నెట్‌వర్క్ పేరును ప్రదర్శిస్తుంది" "నిల్వ నిర్వాహికి: ^1" "ఆఫ్‌లో ఉంది" "ఆన్‌లో ఉంది" diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 916fe97cb27..63747a6b3a2 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -2446,7 +2446,7 @@ "Дає змогу вмикати функцію із заблокованого екрана за допомогою комбінації клавіш. Натисніть і утримуйте обидві клавіші гучності впродовж кількох секунд." "Кнопка спеціальних можливостей" "Кнопка й жест спеціальних можливостей" - "Спеціальними можливостями можна швидко скористатися на будь-якому екрані" + "Вмикайте спеціальні можливості з будь-якого екрана" "Про кнопку спеціальних можливостей" "Про кнопку й жест спеціальних можливостей" "Докладніше про кнопку й жест спеціальних можливостей" From 84b39c3ed0a2f28e1d3bfc63e985201113d05770 Mon Sep 17 00:00:00 2001 From: Milton Wu Date: Thu, 28 Jul 2022 08:15:27 +0000 Subject: [PATCH 05/10] Use setting as base activity for fingerprint Change to use FingerprintSetting as base activity when use launch "Fingerprint Unlock" from Settings -> Security. And then we can prevent that necesssary pop-up activites become full-screen. Bug: 232874879 Test: manual test following cases on fp-only devices, and enable don't keep activity and test them again. 1. fp enrollment on SUW 2. fp add another on SUW 3. add first fp on Security Settings 4. add another fp on Security Settings Test: atest FingerprintStatusUtilsTest BiometricsSafetySourceTest Test: robo test for SetupFingerprintEnrollFindSensorTest SetupFingerprintEnrollFinishTest FingerprintEnrollFindSensorTest FingerprintEnrollEnrollingTest Change-Id: Ib1c2ef9f93fb910eed2930f871c0c69bdb94bcbd --- .../biometrics/BiometricsEnrollEnrolling.java | 16 +---- .../biometrics/face/FaceEnrollEnrolling.java | 21 ++++++ .../FingerprintEnrollEnrolling.java | 17 ++++- .../FingerprintEnrollFindSensor.java | 13 +++- .../fingerprint/FingerprintEnrollFinish.java | 20 ++---- .../fingerprint/FingerprintSettings.java | 54 ++++++++++++-- .../fingerprint/FingerprintStatusUtils.java | 3 +- .../FingerprintEnrollFindSensorTest.java | 72 +++++++++++++++++++ .../FingerprintStatusUtilsTest.java | 4 +- .../BiometricsSafetySourceTest.java | 3 +- 10 files changed, 178 insertions(+), 45 deletions(-) diff --git a/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java b/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java index 3a61d5e2243..2a1d049af1b 100644 --- a/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java @@ -24,8 +24,6 @@ import android.view.View; import com.android.settings.R; import com.android.settings.password.ChooseLockSettingsHelper; -import com.google.android.setupcompat.util.WizardManagerHelper; - /** * Abstract base activity which handles the actual enrolling for biometrics. */ @@ -62,30 +60,18 @@ public abstract class BiometricsEnrollEnrolling extends BiometricEnrollBase @Override protected void onStop() { - super.onStop(); - if (mSidecar != null) { mSidecar.setListener(null); } - if (!isChangingConfigurations()) { if (mSidecar != null) { mSidecar.cancelEnrollment(); getSupportFragmentManager() .beginTransaction().remove(mSidecar).commitAllowingStateLoss(); } - if (!WizardManagerHelper.isAnySetupWizard(getIntent()) - && !BiometricUtils.isAnyMultiBiometricFlow(this)) { - setResult(RESULT_TIMEOUT); - } - finish(); } - } - @Override - protected boolean shouldFinishWhenBackgrounded() { - // Prevent super.onStop() from finishing, since we handle this in our onStop(). - return false; + super.onStop(); } @Override diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java index bf4a1d46187..68fbe3899f1 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java @@ -31,11 +31,13 @@ import com.android.settings.R; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricEnrollSidecar; import com.android.settings.biometrics.BiometricErrorDialog; +import com.android.settings.biometrics.BiometricUtils; import com.android.settings.biometrics.BiometricsEnrollEnrolling; import com.android.settings.slices.CustomSliceRegistry; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; +import com.google.android.setupcompat.util.WizardManagerHelper; import java.util.ArrayList; @@ -112,6 +114,25 @@ public class FaceEnrollEnrolling extends BiometricsEnrollEnrolling { startEnrollment(); } + @Override + protected void onStop() { + if (!isChangingConfigurations()) { + if (!WizardManagerHelper.isAnySetupWizard(getIntent()) + && !BiometricUtils.isAnyMultiBiometricFlow(this)) { + setResult(RESULT_TIMEOUT); + } + finish(); + } + + super.onStop(); + } + + @Override + protected boolean shouldFinishWhenBackgrounded() { + // Prevent super.onStop() from finishing, since we handle this in our onStop(). + return false; + } + @Override public void startEnrollment() { super.startEnrollment(); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 5f9a74f931a..bad1bbdfc1b 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -339,8 +339,23 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { @Override protected void onStop() { - super.onStop(); + if (!isChangingConfigurations()) { + if (!WizardManagerHelper.isAnySetupWizard(getIntent()) + && !BiometricUtils.isAnyMultiBiometricFlow(this) + && !mFromSettingsSummary) { + setResult(RESULT_TIMEOUT); + } + finish(); + } stopIconAnimation(); + + super.onStop(); + } + + @Override + protected boolean shouldFinishWhenBackgrounded() { + // Prevent super.onStop() from finishing, since we handle this in our onStop(). + return false; } @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index 627a5143a38..427b50aa7c4 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -49,8 +49,8 @@ import java.util.List; public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements BiometricEnrollSidecar.Listener { - private static final String TAG = "FingerprintEnrollFindSensor"; + private static final String SAVED_STATE_IS_NEXT_CLICKED = "is_next_clicked"; @Nullable private FingerprintFindSensorAnimation mAnimation; @@ -174,6 +174,15 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements mAnimation = (FingerprintFindSensorAnimation) animationView; } } + if (savedInstanceState != null) { + mNextClicked = savedInstanceState.getBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked); } @Override @@ -240,7 +249,6 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements @Override public void onEnrollmentError(int errMsgId, CharSequence errString) { if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { - mNextClicked = false; proceedToEnrolling(false /* cancelEnrollment */); } else { FingerprintErrorDialog.showErrorDialog(this, errMsgId); @@ -341,6 +349,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements finish(); } else { // We came back from enrolling but it wasn't completed, start again. + mNextClicked = false; startLookingForFingerprint(); } break; diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java index 16773d3cbda..b606b607a5a 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java @@ -32,7 +32,6 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; -import com.android.settings.password.ChooseLockSettingsHelper; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; @@ -114,9 +113,6 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase { setResult(RESULT_FINISHED); if (WizardManagerHelper.isAnySetupWizard(getIntent())) { postEnroll(); - } else if (mFromSettingsSummary) { - // Only launch fingerprint settings if enrollment was triggered through settings summary - launchFingerprintSettings(); } finish(); } @@ -148,21 +144,15 @@ public class FingerprintEnrollFinish extends BiometricEnrollBase { } } - private void launchFingerprintSettings() { - final Intent intent = new Intent(ACTION_FINGERPRINT_SETTINGS); - intent.setPackage(Utils.SETTINGS_PACKAGE_NAME); - intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtra(Intent.EXTRA_USER_ID, mUserId); - intent.putExtra(BiometricEnrollBase.EXTRA_KEY_CHALLENGE, mChallenge); - startActivity(intent); - overridePendingTransition(R.anim.sud_slide_back_in, R.anim.sud_slide_back_out); - } - private void onAddAnotherButtonClick(View view) { startActivityForResult(getFingerprintEnrollingIntent(), BiometricUtils.REQUEST_ADD_ANOTHER); } + @Override + protected boolean shouldFinishWhenBackgrounded() { + return !isFinishing() && super.shouldFinishWhenBackgrounded(); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { updateFingerprintSuggestionEnableState(); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 46ea7f42351..e1acec72e34 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -22,6 +22,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROF import static android.app.admin.DevicePolicyResources.UNDEFINED; import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; +import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_FROM_SETTINGS_SUMMARY; import android.app.Activity; import android.app.Dialog; @@ -62,6 +63,7 @@ import com.android.settings.SubSettings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollBase; import com.android.settings.biometrics.BiometricUtils; +import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.password.ChooseLockGeneric; import com.android.settings.password.ChooseLockSettingsHelper; @@ -69,6 +71,7 @@ import com.android.settingslib.HelpUtils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.transition.SettingsTransitionHelper; import com.android.settingslib.widget.FooterPreference; import com.android.settingslib.widget.TwoTargetPreference; @@ -129,6 +132,8 @@ public class FingerprintSettings extends SubSettings { private static final String KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE = "fingerprint_enable_keyguard_toggle"; private static final String KEY_LAUNCHED_CONFIRM = "launched_confirm"; + private static final String KEY_HAS_FIRST_ENROLLED = "has_first_enrolled"; + private static final String KEY_IS_ENROLLING = "is_enrolled"; private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000; private static final int MSG_FINGER_AUTH_SUCCESS = 1001; @@ -140,6 +145,7 @@ public class FingerprintSettings extends SubSettings { private static final int CHOOSE_LOCK_GENERIC_REQUEST = 102; private static final int ADD_FINGERPRINT_REQUEST = 10; + private static final int AUTO_ADD_FIRST_FINGERPRINT_REQUEST = 11; protected static final boolean DEBUG = false; @@ -149,10 +155,11 @@ public class FingerprintSettings extends SubSettings { private boolean mInFingerprintLockout; private byte[] mToken; private boolean mLaunchedConfirm; + private boolean mHasFirstEnrolled = true; private Drawable mHighlightDrawable; private int mUserId; private final List mFooterColumns = new ArrayList<>(); - private boolean mEnrollClicked; + private boolean mIsEnrolling; private long mChallenge; @@ -341,6 +348,9 @@ public class FingerprintSettings extends SubSettings { } mFingerprintsRenaming = new HashMap(); + mUserId = getActivity().getIntent().getIntExtra( + Intent.EXTRA_USER_ID, UserHandle.myUserId()); + mHasFirstEnrolled = mFingerprintManager.hasEnrolledFingerprints(mUserId); if (savedInstanceState != null) { mFingerprintsRenaming = (HashMap) @@ -349,9 +359,10 @@ public class FingerprintSettings extends SubSettings { ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); mLaunchedConfirm = savedInstanceState.getBoolean( KEY_LAUNCHED_CONFIRM, false); + mIsEnrolling = savedInstanceState.getBoolean(KEY_IS_ENROLLING, mIsEnrolling); + mHasFirstEnrolled = savedInstanceState.getBoolean(KEY_HAS_FIRST_ENROLLED, + mHasFirstEnrolled); } - mUserId = getActivity().getIntent().getIntExtra( - Intent.EXTRA_USER_ID, UserHandle.myUserId()); // Need to authenticate a session token if none if (mToken == null && mLaunchedConfirm == false) { @@ -546,7 +557,7 @@ public class FingerprintSettings extends SubSettings { @Override public void onStop() { super.onStop(); - if (!getActivity().isChangingConfigurations() && !mLaunchedConfirm && !mEnrollClicked) { + if (!getActivity().isChangingConfigurations() && !mLaunchedConfirm && !mIsEnrolling) { getActivity().finish(); } } @@ -557,13 +568,15 @@ public class FingerprintSettings extends SubSettings { mToken); outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm); outState.putSerializable("mFingerprintsRenaming", mFingerprintsRenaming); + outState.putBoolean(KEY_IS_ENROLLING, mIsEnrolling); + outState.putBoolean(KEY_HAS_FIRST_ENROLLED, mHasFirstEnrolled); } @Override public boolean onPreferenceTreeClick(Preference pref) { final String key = pref.getKey(); if (KEY_FINGERPRINT_ADD.equals(key)) { - mEnrollClicked = true; + mIsEnrolling = true; Intent intent = new Intent(); intent.setClassName(SETTINGS_PACKAGE_NAME, FingerprintEnrollEnrolling.class.getName()); @@ -659,6 +672,11 @@ public class FingerprintSettings extends SubSettings { BiometricUtils.removeGatekeeperPasswordHandle(getActivity(), data); updateAddPreference(); + if (!mHasFirstEnrolled && !mIsEnrolling) { + mIsEnrolling = true; + addFirstFingerprint( + BiometricUtils.getGatekeeperPasswordHandle(data)); + } }); } else { Log.d(TAG, "Data null or GK PW missing"); @@ -669,12 +687,19 @@ public class FingerprintSettings extends SubSettings { finish(); } } else if (requestCode == ADD_FINGERPRINT_REQUEST) { - mEnrollClicked = false; + mIsEnrolling = false; if (resultCode == RESULT_TIMEOUT) { Activity activity = getActivity(); activity.setResult(resultCode); activity.finish(); } + } else if (requestCode == AUTO_ADD_FIRST_FINGERPRINT_REQUEST) { + mIsEnrolling = false; + mHasFirstEnrolled = false; + if (resultCode != RESULT_FINISHED) { + Log.d(TAG, "Add first fingerprint fail, result:" + resultCode); + finish(); + } } } @@ -746,6 +771,23 @@ public class FingerprintSettings extends SubSettings { } } + private void addFirstFingerprint(@Nullable Long gkPwHandle) { + Intent intent = new Intent(); + intent.setClassName(SETTINGS_PACKAGE_NAME, + FingerprintEnrollIntroductionInternal.class.getName()); + + intent.putExtra(EXTRA_FROM_SETTINGS_SUMMARY, true); + intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE, + SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); + + intent.putExtra(Intent.EXTRA_USER_ID, mUserId); + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken); + if (gkPwHandle != null) { + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, (long) gkPwHandle); + } + startActivityForResult(intent, AUTO_ADD_FIRST_FINGERPRINT_REQUEST); + } + @VisibleForTesting void deleteFingerPrint(Fingerprint fingerPrint) { mRemovalSidecar.startRemove(fingerPrint, mUserId); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java index 82ceed6c878..71cdcf73b84 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtils.java @@ -78,8 +78,7 @@ public class FingerprintStatusUtils { * Returns the class name of the Settings page corresponding to fingerprint settings. */ public String getSettingsClassName() { - return hasEnrolled() ? FingerprintSettings.class.getName() - : FingerprintEnrollIntroductionInternal.class.getName(); + return FingerprintSettings.class.getName(); } /** diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java index ec17a8602c0..a78dbb1f52a 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensorTest.java @@ -165,4 +165,76 @@ public class FingerprintEnrollFindSensorTest { null); assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(Activity.RESULT_CANCELED); } + + @Test + public void onActivityResult_EnrollRequestResultFinishShallBeSentBack() { + final int defaultActivityResult = Shadows.shadowOf(mActivity).getResultCode(); + + // Start enrolling + EnrollmentCallback enrollmentCallback = verifyAndCaptureEnrollmentCallback(); + enrollmentCallback.onEnrollmentProgress(123); + enrollmentCallback.onEnrollmentError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, "test"); + + // onStop shall not change default activity result + mActivity.onStop(); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(defaultActivityResult); + + // onActivityResult from Enrolling activity shall be sent back + final int testResult = BiometricEnrollBase.RESULT_FINISHED; + mActivity.onActivityResult(BiometricEnrollBase.ENROLL_REQUEST, testResult, null); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(testResult); + assertThat(mActivity.isFinishing()).isEqualTo(true); + + // onStop shall not change last activity result + mActivity.onStop(); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(testResult); + } + + @Test + public void onActivityResult_EnrollRequestResultSkipShallBeSentBack() { + final int defaultActivityResult = Shadows.shadowOf(mActivity).getResultCode(); + + // Start enrolling + EnrollmentCallback enrollmentCallback = verifyAndCaptureEnrollmentCallback(); + enrollmentCallback.onEnrollmentProgress(123); + enrollmentCallback.onEnrollmentError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, "test"); + + // onStop shall not change default activity result + mActivity.onStop(); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(defaultActivityResult); + + // onActivityResult from Enrolling activity shall be sent back + final int testResult = BiometricEnrollBase.RESULT_SKIP; + mActivity.onActivityResult(BiometricEnrollBase.ENROLL_REQUEST, testResult, null); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(testResult); + assertThat(mActivity.isFinishing()).isEqualTo(true); + + // onStop shall not change last activity result + mActivity.onStop(); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(testResult); + } + + @Test + public void onActivityResult_EnrollRequestResultTimeoutShallBeSentBack() { + final int defaultActivityResult = Shadows.shadowOf(mActivity).getResultCode(); + + // Start enrolling + EnrollmentCallback enrollmentCallback = verifyAndCaptureEnrollmentCallback(); + enrollmentCallback.onEnrollmentProgress(123); + enrollmentCallback.onEnrollmentError(FingerprintManager.FINGERPRINT_ERROR_CANCELED, "test"); + + // onStop shall not change default activity result + mActivity.onStop(); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(defaultActivityResult); + + // onActivityResult from Enrolling activity shall be sent back + final int testResult = BiometricEnrollBase.RESULT_TIMEOUT; + mActivity.onActivityResult(BiometricEnrollBase.ENROLL_REQUEST, testResult, null); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(testResult); + assertThat(mActivity.isFinishing()).isEqualTo(true); + + // onStop shall not change last activity result + mActivity.onStop(); + assertThat(Shadows.shadowOf(mActivity).getResultCode()).isEqualTo(testResult); + } } diff --git a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java index 6faf8011278..69e5e2f485b 100644 --- a/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java +++ b/tests/unit/src/com/android/settings/biometrics/fingerprint/FingerprintStatusUtilsTest.java @@ -179,11 +179,11 @@ public class FingerprintStatusUtilsTest { } @Test - public void getSettingsClassName_whenNotEnrolled_returnsFingerprintEnrollInduction() { + public void getSettingsClassName_whenNotEnrolled_returnsFingerprintSettings() { when(mFingerprintManager.hasEnrolledFingerprints(anyInt())).thenReturn(false); assertThat(mFingerprintStatusUtils.getSettingsClassName()) - .isEqualTo(FingerprintEnrollIntroductionInternal.class.getName()); + .isEqualTo(FingerprintSettings.class.getName()); } @Test diff --git a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java index 7ff407db43a..07c1571f987 100644 --- a/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java +++ b/tests/unit/src/com/android/settings/safetycenter/BiometricsSafetySourceTest.java @@ -47,7 +47,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.settings.Settings; import com.android.settings.biometrics.face.FaceEnrollIntroductionInternal; -import com.android.settings.biometrics.fingerprint.FingerprintEnrollIntroductionInternal; import com.android.settings.biometrics.fingerprint.FingerprintSettings; import com.android.settings.testutils.ResourcesUtils; @@ -186,7 +185,7 @@ public class BiometricsSafetySourceTest { assertSafetySourceEnabledDataSetWithSingularSummary( "security_settings_fingerprint_preference_title", "security_settings_fingerprint_preference_summary_none", - FingerprintEnrollIntroductionInternal.class.getName()); + FingerprintSettings.class.getName()); } @Test From 1ca385591a76981752e1a47db92b260e9e5337b1 Mon Sep 17 00:00:00 2001 From: menghanli Date: Tue, 9 Aug 2022 09:52:01 +0800 Subject: [PATCH 06/10] Fix IllegalStateException to get tile content after device rotate Root cause: Race condition to get content after the onDetach() Solution: Add protection to prevent this IllegalStateException Bug: 239578655 Test: make RunSettingsRoboTests ROBOTEST_FILTER=OneHandedSettings Test: Manual testing by rotating 100 times quickly Change-Id: I0a03cf1c01cd34c8ae0b0e70c4ffc41168b18ab8 --- .../settings/gestures/OneHandedSettings.java | 14 ++++++-- .../gestures/OneHandedSettingsTest.java | 34 +++++++++++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java index e1b9b817f95..aa193865886 100644 --- a/src/com/android/settings/gestures/OneHandedSettings.java +++ b/src/com/android/settings/gestures/OneHandedSettings.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.os.Bundle; import android.os.UserHandle; +import android.util.Log; import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.settings.R; @@ -39,6 +40,7 @@ import com.android.settingslib.widget.MainSwitchPreference; */ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { + private static final String TAG = "OneHandedSettings"; private static final String ONE_HANDED_SHORTCUT_KEY = "one_handed_shortcuts_preference"; private static final String ONE_HANDED_ILLUSTRATION_KEY = "one_handed_header"; protected static final String ONE_HANDED_MAIN_SWITCH_KEY = @@ -131,9 +133,15 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { @Override protected CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) { + final Context context = getContext(); + if (context == null) { + Log.w(TAG, "OneHandedSettings not attached to a context."); + return null; + } return type == QuickSettingsTooltipType.GUIDE_TO_EDIT - ? getText(R.string.accessibility_one_handed_mode_qs_tooltip_content) - : getText(R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content); + ? context.getText(R.string.accessibility_one_handed_mode_qs_tooltip_content) + : context.getText( + R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content); } @Override @@ -143,7 +151,7 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { @Override protected String getLogTag() { - return null; + return TAG; } @Override diff --git a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java index 26517089a66..9633b15bec4 100644 --- a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java +++ b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java @@ -18,10 +18,18 @@ package com.android.settings.gestures; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import android.content.Context; import android.os.SystemProperties; import android.provider.SearchIndexableResource; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,16 +39,36 @@ import org.robolectric.util.ReflectionHelpers; import java.util.List; +/** Tests for {@link OneHandedSettings}. */ @RunWith(RobolectricTestRunner.class) public class OneHandedSettingsTest { + private final Context mContext = ApplicationProvider.getApplicationContext(); private OneHandedSettings mSettings; - private Context mContext; @Before public void setUp() { - mSettings = new OneHandedSettings(); - mContext = RuntimeEnvironment.application; + mSettings = spy(new OneHandedSettings()); + } + + @Test + public void getTileTooltipContent_returnsExpectedValues() { + // Simulate to call getTileTooltipContent after onDetach + assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT)) + .isNull(); + // Simulate to call getTileTooltipContent after onAttach + when(mSettings.getContext()).thenReturn(mContext); + assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT)) + .isEqualTo(mContext.getText( + R.string.accessibility_one_handed_mode_qs_tooltip_content)); + assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE)) + .isEqualTo(mContext.getText( + R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content)); + } + + @Test + public void getLogTag_returnsCorrectTag() { + assertThat(mSettings.getLogTag()).isEqualTo("OneHandedSettings"); } @Test From 8d88148036f29366f302161b5881cc243c5a3b57 Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Tue, 9 Aug 2022 13:41:04 +0800 Subject: [PATCH 07/10] Avoid NullPointerException when batteryLevelData is null. Bug: 236101687 Bug: 241738105 Test: make RunSettingsRoboTests Change-Id: Iee04f96083eff7ff5cd61a7c1f2eda20a6dc9149 --- .../settings/fuelgauge/batteryusage/DataProcessor.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index aadac8ed666..1af31aa6497 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -137,10 +137,12 @@ public final class DataProcessor { // Wrap and processed history map into easy-to-use format for UI rendering. final BatteryLevelData batteryLevelData = getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap); - return getBatteryUsageMap( - context, - batteryLevelData.getHourlyBatteryLevelsPerDay(), - processedBatteryHistoryMap); + return batteryLevelData == null + ? null + : getBatteryUsageMap( + context, + batteryLevelData.getHourlyBatteryLevelsPerDay(), + processedBatteryHistoryMap); } /** From 8d0030d874addb7aca11e0f7e7ff41fdff94409a Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Tue, 9 Aug 2022 16:22:33 +0800 Subject: [PATCH 08/10] Fix b/241872474 Battery usage page will crash when selecting the last hour chart bar, going to app detail page, and going back This bug is because we always use mHourlyChartIndex to construct every view model in mHoulyViewModels. However, mHourlyChartIndex could be got from saved instance. So mHourlyChartIndex may be out of bound in some hourly view model which has not many hours data. This fix removes the selectedIndex in BatteryChartViewModel constructor. Suppose the selectedIndex should be set everytime the view model is used. Test: manual Bug: 236101166 Bug: 241872474 Change-Id: I0bb5568ac33fcc23c406fe3af308b8d2706c5542 --- .../BatteryChartPreferenceControllerV2.java | 2 - .../batteryusage/BatteryChartViewModel.java | 34 +++++++-------- ...atteryChartPreferenceControllerV2Test.java | 43 ++++++++----------- .../batteryusage/BatteryChartViewV2Test.java | 12 +++--- 4 files changed, 38 insertions(+), 53 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java index b847f04d048..2bc8c46a992 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java @@ -284,7 +284,6 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro generateTimestampDayOfWeekTexts( mContext, batteryLevelData.getDailyBatteryLevels().getTimestamps(), /* isAbbreviation= */ true), - mDailyChartIndex, BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS); mHourlyViewModels = new ArrayList<>(); for (BatteryLevelData.PeriodBatteryLevelData hourlyBatteryLevelsPerDay : @@ -293,7 +292,6 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro hourlyBatteryLevelsPerDay.getLevels(), generateTimestampHourTexts( mContext, hourlyBatteryLevelsPerDay.getTimestamps()), - mHourlyChartIndex, BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); } refreshUi(); diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java index 493891f45bc..82a41c4a26e 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewModel.java @@ -41,22 +41,18 @@ class BatteryChartViewModel { private final List mLevels; private final List mTexts; private final AxisLabelPosition mAxisLabelPosition; - private int mSelectedIndex; + private int mSelectedIndex = SELECTED_INDEX_ALL; BatteryChartViewModel( - @NonNull List levels, @NonNull List texts, int selectedIndex, + @NonNull List levels, @NonNull List texts, @NonNull AxisLabelPosition axisLabelPosition) { Preconditions.checkArgument( - levels.size() == texts.size() - && levels.size() >= MIN_LEVELS_DATA_SIZE - && selectedIndex >= SELECTED_INDEX_ALL - && selectedIndex < levels.size(), - String.format(Locale.ENGLISH, "Invalid BatteryChartViewModel" - + " levels.size: %d\ntexts.size: %d\nselectedIndex: %d.", - levels.size(), texts.size(), selectedIndex)); + levels.size() == texts.size() && levels.size() >= MIN_LEVELS_DATA_SIZE, + String.format(Locale.ENGLISH, + "Invalid BatteryChartViewModel levels.size: %d, texts.size: %d.", + levels.size(), texts.size())); mLevels = levels; mTexts = texts; - mSelectedIndex = selectedIndex; mAxisLabelPosition = axisLabelPosition; } @@ -72,6 +68,10 @@ class BatteryChartViewModel { return mTexts; } + public AxisLabelPosition axisLabelPosition() { + return mAxisLabelPosition; + } + public int selectedIndex() { return mSelectedIndex; } @@ -80,10 +80,6 @@ class BatteryChartViewModel { mSelectedIndex = index; } - public AxisLabelPosition axisLabelPosition() { - return mAxisLabelPosition; - } - @Override public int hashCode() { return Objects.hash(mLevels, mTexts, mSelectedIndex, mAxisLabelPosition); @@ -99,15 +95,15 @@ class BatteryChartViewModel { final BatteryChartViewModel batteryChartViewModel = (BatteryChartViewModel) other; return Objects.equals(mLevels, batteryChartViewModel.mLevels) && Objects.equals(mTexts, batteryChartViewModel.mTexts) - && mSelectedIndex == batteryChartViewModel.mSelectedIndex - && mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition; + && mAxisLabelPosition == batteryChartViewModel.mAxisLabelPosition + && mSelectedIndex == batteryChartViewModel.mSelectedIndex; } @Override public String toString() { return String.format(Locale.ENGLISH, - "levels: %s\ntexts: %s\nselectedIndex: %d, axisLabelPosition: %s", - Objects.toString(mLevels), Objects.toString(mTexts), mSelectedIndex, - mAxisLabelPosition); + "levels: %s,\ntexts: %s,\naxisLabelPosition: %s, selectedIndex: %d", + Objects.toString(mLevels), Objects.toString(mTexts), mAxisLabelPosition, + mSelectedIndex); } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java index cd985b6ccc5..e738e6534f6 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2Test.java @@ -180,21 +180,21 @@ public final class BatteryChartPreferenceControllerV2Test { verify(mHourlyChartView).setViewModel(new BatteryChartViewModel( List.of(100, 97, 95), List.of("8 am", "10 am", "12 pm"), - BatteryChartViewModel.SELECTED_INDEX_ALL, BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); } @Test public void setBatteryChartViewModel_60Hours() { + BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel( + List.of(100, 83, 59, 41), + List.of("Sat", "Sun", "Mon", "Mon"), + BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS); + mBatteryChartPreferenceController.setBatteryHistoryMap(createBatteryHistoryMap(60)); verify(mDailyChartView, atLeastOnce()).setVisibility(View.VISIBLE); verify(mHourlyChartView, atLeastOnce()).setVisibility(View.GONE); - verify(mDailyChartView).setViewModel(new BatteryChartViewModel( - List.of(100, 83, 59, 41), - List.of("Sat", "Sun", "Mon", "Mon"), - BatteryChartViewModel.SELECTED_INDEX_ALL, - BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS)); + verify(mDailyChartView).setViewModel(expectedDailyViewModel); reset(mDailyChartView); reset(mHourlyChartView); @@ -203,16 +203,13 @@ public final class BatteryChartPreferenceControllerV2Test { mBatteryChartPreferenceController.refreshUi(); verify(mDailyChartView).setVisibility(View.VISIBLE); verify(mHourlyChartView).setVisibility(View.VISIBLE); - verify(mDailyChartView).setViewModel(new BatteryChartViewModel( - List.of(100, 83, 59, 41), - List.of("Sat", "Sun", "Mon", "Mon"), - 0, - BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS)); + + expectedDailyViewModel.setSelectedIndex(0); + verify(mDailyChartView).setViewModel(expectedDailyViewModel); verify(mHourlyChartView).setViewModel(new BatteryChartViewModel( List.of(100, 97, 95, 93, 91, 89, 87, 85, 83), List.of("8 am", "10 am", "12 pm", "2 pm", "4 pm", "6 pm", "8 pm", "10 pm", "12 am"), - BatteryChartViewModel.SELECTED_INDEX_ALL, BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); reset(mDailyChartView); @@ -223,17 +220,15 @@ public final class BatteryChartPreferenceControllerV2Test { mBatteryChartPreferenceController.refreshUi(); verify(mDailyChartView).setVisibility(View.VISIBLE); verify(mHourlyChartView).setVisibility(View.VISIBLE); - verify(mDailyChartView).setViewModel(new BatteryChartViewModel( - List.of(100, 83, 59, 41), - List.of("Sat", "Sun", "Mon", "Mon"), - 1, - BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS)); - verify(mHourlyChartView).setViewModel(new BatteryChartViewModel( + expectedDailyViewModel.setSelectedIndex(1); + verify(mDailyChartView).setViewModel(expectedDailyViewModel); + BatteryChartViewModel expectedHourlyViewModel = new BatteryChartViewModel( List.of(83, 81, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59), List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm", "4 pm", "6 pm", "8 pm", "10 pm", "12 am"), - 6, - BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); + BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS); + expectedHourlyViewModel.setSelectedIndex(6); + verify(mHourlyChartView).setViewModel(expectedHourlyViewModel); reset(mDailyChartView); reset(mHourlyChartView); @@ -244,16 +239,12 @@ public final class BatteryChartPreferenceControllerV2Test { mBatteryChartPreferenceController.refreshUi(); verify(mDailyChartView).setVisibility(View.VISIBLE); verify(mHourlyChartView).setVisibility(View.VISIBLE); - verify(mDailyChartView).setViewModel(new BatteryChartViewModel( - List.of(100, 83, 59, 41), - List.of("Sat", "Sun", "Mon", "Mon"), - 2, - BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS)); + expectedDailyViewModel.setSelectedIndex(2); + verify(mDailyChartView).setViewModel(expectedDailyViewModel); verify(mHourlyChartView).setViewModel(new BatteryChartViewModel( List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41), List.of("12 am", "2 am", "4 am", "6 am", "8 am", "10 am", "12 pm", "2 pm", "4 pm", "6 pm"), - BatteryChartViewModel.SELECTED_INDEX_ALL, BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java index 174733536bb..10e62d69dfb 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2Test.java @@ -102,10 +102,11 @@ public final class BatteryChartViewV2Test { @Test public void onClick_invokesCallback() { final int originalSelectedIndex = 2; - mBatteryChartView.setViewModel( - new BatteryChartViewModel(List.of(90, 80, 70, 60), List.of("", "", "", ""), - originalSelectedIndex, - BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); + BatteryChartViewModel batteryChartViewModel = new BatteryChartViewModel( + List.of(90, 80, 70, 60), List.of("", "", "", ""), + BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS); + batteryChartViewModel.setSelectedIndex(originalSelectedIndex); + mBatteryChartView.setViewModel(batteryChartViewModel); for (int i = 0; i < mBatteryChartView.mTrapezoidSlots.length; i++) { mBatteryChartView.mTrapezoidSlots[i] = new BatteryChartViewV2.TrapezoidSlot(); mBatteryChartView.mTrapezoidSlots[i].mLeft = i; @@ -192,8 +193,7 @@ public final class BatteryChartViewV2Test { levels.add(index + 1); texts.add(""); } - mBatteryChartView.setViewModel(new BatteryChartViewModel( - levels, texts, BatteryChartViewModel.SELECTED_INDEX_ALL, + mBatteryChartView.setViewModel(new BatteryChartViewModel(levels, texts, BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS)); mBatteryChartView.setClickableForce(true); when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) From 5316bb71fe9477ce385c846b92d4df2c7afd7b3b Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Tue, 9 Aug 2022 14:38:45 +0800 Subject: [PATCH 09/10] Fix b/241885070: Unexpected texts moving when going back to battery usage page This bug is because two charts visibility were set to visible everytime when entering the battery usage page. Only after loading the data and refresh the UI, one of them could be hidden. screen record after fix: https://drive.google.com/file/d/1zpynm8UX3XXunlD-WE1eyv6yenrVhAH5/view?usp=sharing&resourcekey=0-y79NYHcm8lgiNRbgy8SEfQ Test: manual Bug: 236101166 Bug: 241885070 Change-Id: I9c1dbf79a95dad2c3dece71af6d52f546d8a5808 --- .../BatteryChartPreferenceControllerV2.java | 12 +++++++++--- .../fuelgauge/batteryusage/BatteryChartViewV2.java | 1 - 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java index b847f04d048..fe10a4742ec 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerV2.java @@ -273,7 +273,7 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro mDailyTimestampFullTexts = null; mDailyViewModel = null; mHourlyViewModels = null; - addFooterPreferenceIfNeeded(false); + refreshUi(); return; } mDailyTimestampFullTexts = generateTimestampDayOfWeekTexts( @@ -338,14 +338,20 @@ public class BatteryChartPreferenceControllerV2 extends AbstractPreferenceContro @VisibleForTesting boolean refreshUi() { - if (mBatteryUsageMap == null || mDailyChartView == null || mHourlyChartView == null) { + if (mDailyChartView == null || mHourlyChartView == null) { + // Chart views are not initialized. return false; } - if (mDailyViewModel == null || mHourlyViewModels == null) { // Fail to get battery level data, show an empty hourly chart view. mDailyChartView.setVisibility(View.GONE); + mHourlyChartView.setVisibility(View.VISIBLE); mHourlyChartView.setViewModel(null); + addFooterPreferenceIfNeeded(false); + return false; + } + if (mBatteryUsageMap == null) { + // Battery usage data is not ready, wait for data ready to refresh UI. return false; } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java index bbe1a8f6dcd..a8954788d72 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java @@ -158,7 +158,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli } else { mTextPaint = null; } - setVisibility(View.VISIBLE); requestLayout(); } From 72b64f251559b87b1c14a06f0657ef6525b115e2 Mon Sep 17 00:00:00 2001 From: Zaiyue Xue Date: Fri, 5 Aug 2022 13:18:28 +0800 Subject: [PATCH 10/10] New way to draw battery chart axis labels Pairly draws axis labels from left and right side to middle. If the pair of labels have any overlap, skips that pair of labels. https://drive.google.com/drive/folders/1tR4xfJsJGakuH5JRdn74kPD5GBH6u6CL?resourcekey=0-Ikp5CV0DpxllWv7n5-UHnw&usp=sharing Test: manual Bug: 236101166 Change-Id: Ib13d4c73b31ad86ac9e318d4315b5a1a0bb25814 --- .../batteryusage/BatteryChartViewV2.java | 206 ++++++++++-------- 1 file changed, 112 insertions(+), 94 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java index bbe1a8f6dcd..49bbc93ee22 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartViewV2.java @@ -46,12 +46,10 @@ import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.Utils; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; /** A widget component to draw chart graph. */ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnClickListener, @@ -62,8 +60,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private static final int DIVIDER_COLOR = Color.parseColor("#CDCCC5"); private static final long UPDATE_STATE_DELAYED_TIME = 500L; - private static final Map MODEL_SIZE_TO_LABEL_INDEXES_MAP = - buildModelSizeToLabelIndexesMap(); /** A callback listener for selected group index is updated. */ public interface OnSelectListener { @@ -79,7 +75,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private float mTrapezoidHOffset; private boolean mIsSlotsClickabled; private String[] mPercentages = getPercentages(); - private Integer[] mLabelsIndexes; @VisibleForTesting int mHoveredIndex = BatteryChartViewModel.SELECTED_INDEX_INVALID; @@ -94,7 +89,8 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private final Rect[] mPercentageBounds = new Rect[]{new Rect(), new Rect(), new Rect()}; // For drawing the axis label information. - private final Rect[] mAxisLabelsBounds = initializeAxisLabelsBounds(); + private final List mAxisLabelsBounds = new ArrayList<>(); + @VisibleForTesting Handler mHandler = new Handler(); @@ -138,7 +134,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli Log.d(TAG, String.format("setViewModel(): size: %d, selectedIndex: %d.", viewModel.size(), viewModel.selectedIndex())); mViewModel = viewModel; - mLabelsIndexes = MODEL_SIZE_TO_LABEL_INDEXES_MAP.get(mViewModel.size()); + initializeAxisLabelsBounds(); initializeTrapezoidSlots(viewModel.size() - 1); setClickable(hasAnyValidTrapezoid(viewModel)); requestLayout(); @@ -167,6 +163,7 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Measures text bounds and updates indent configuration. if (mTextPaint != null) { + mTextPaint.setTextAlign(Paint.Align.LEFT); for (int index = 0; index < mPercentages.length; index++) { mTextPaint.getTextBounds( mPercentages[index], 0, mPercentages[index].length(), @@ -177,13 +174,13 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli mIndent.right = mPercentageBounds[0].width() + mTextPadding; if (mViewModel != null) { - int maxHeight = 0; - for (int index = 0; index < mLabelsIndexes.length; index++) { - final String text = getAxisLabelText(index); - mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds[index]); - maxHeight = Math.max(maxHeight, mAxisLabelsBounds[index].height()); + int maxTop = 0; + for (int index = 0; index < mViewModel.size(); index++) { + final String text = mViewModel.texts().get(index); + mTextPaint.getTextBounds(text, 0, text.length(), mAxisLabelsBounds.get(index)); + maxTop = Math.max(maxTop, -mAxisLabelsBounds.get(index).top); } - mIndent.bottom = maxHeight + round(mTextPadding * 1.5f); + mIndent.bottom = maxTop + round(mTextPadding * 2f); } Log.d(TAG, "setIndent:" + mPercentageBounds[0]); } else { @@ -386,10 +383,10 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli private void drawPercentage(Canvas canvas, int index, float offsetY) { if (mTextPaint != null) { + mTextPaint.setTextAlign(Paint.Align.RIGHT); canvas.drawText( mPercentages[index], - getWidth() - mPercentageBounds[index].width() - - mPercentageBounds[index].left, + getWidth(), offsetY + mPercentageBounds[index].height() * .5f, mTextPaint); } @@ -417,67 +414,112 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli } // Draws the axis label slot information. if (mViewModel != null) { - final float[] xOffsets = new float[mLabelsIndexes.length]; - final float baselineX = mDividerWidth * .5f; - final float offsetX = mDividerWidth + unitWidth; - for (int index = 0; index < mLabelsIndexes.length; index++) { - xOffsets[index] = baselineX + mLabelsIndexes[index] * offsetX; - } + final float baselineY = getHeight() - mTextPadding * 1.5f; + Rect[] axisLabelDisplayAreas; switch (mViewModel.axisLabelPosition()) { case CENTER_OF_TRAPEZOIDS: - drawAxisLabelsCenterOfTrapezoids(canvas, xOffsets, unitWidth); + axisLabelDisplayAreas = getAxisLabelDisplayAreas( + /* size= */ mViewModel.size() - 1, + /* baselineX= */ mDividerWidth + unitWidth * .5f, + /* offsetX= */ mDividerWidth + unitWidth, + baselineY, + /* shiftFirstAndLast= */ false); break; case BETWEEN_TRAPEZOIDS: default: - drawAxisLabelsBetweenTrapezoids(canvas, xOffsets); + axisLabelDisplayAreas = getAxisLabelDisplayAreas( + /* size= */ mViewModel.size(), + /* baselineX= */ mDividerWidth * .5f, + /* offsetX= */ mDividerWidth + unitWidth, + baselineY, + /* shiftFirstAndLast= */ true); break; } + drawAxisLabels(canvas, axisLabelDisplayAreas, baselineY); + } + } + + /** Gets all the axis label texts displaying area positions if they are shown. */ + private Rect[] getAxisLabelDisplayAreas(final int size, final float baselineX, + final float offsetX, final float baselineY, final boolean shiftFirstAndLast) { + final Rect[] result = new Rect[size]; + for (int index = 0; index < result.length; index++) { + final float width = mAxisLabelsBounds.get(index).width(); + float middle = baselineX + index * offsetX; + if (shiftFirstAndLast) { + if (index == 0) { + middle += width * .5f; + } + if (index == size - 1) { + middle -= width * .5f; + } + } + final float left = middle - width * .5f; + final float right = left + width; + final float top = baselineY + mAxisLabelsBounds.get(index).top; + final float bottom = top + mAxisLabelsBounds.get(index).height(); + result[index] = new Rect(round(left), round(top), round(right), round(bottom)); + } + return result; + } + + /** + * Pairly draws axis labels from left and right side to middle. If the pair of labels have + * any overlap, skips that pair of labels. + */ + private void drawAxisLabels(Canvas canvas, final Rect[] displayAreas, final float baselineY) { + int forwardCheckLine = Integer.MIN_VALUE; + int backwardCheckLine = Integer.MAX_VALUE; + Rect middleDisplayArea = null; + for (int forwardIndex = 0, backwordIndex = displayAreas.length - 1; + forwardIndex <= backwordIndex; forwardIndex++, backwordIndex--) { + final Rect forwardDisplayArea = displayAreas[forwardIndex]; + final Rect backwardDisplayArea = displayAreas[backwordIndex]; + if (forwardDisplayArea.left < forwardCheckLine + || backwardDisplayArea.right > backwardCheckLine) { + // Overlapped at left or right, skip the pair of labels + continue; + } + if (middleDisplayArea != null && ( + forwardDisplayArea.right + mTextPadding > middleDisplayArea.left + || backwardDisplayArea.left - mTextPadding < middleDisplayArea.right)) { + // Overlapped with the middle label. + continue; + } + if (forwardIndex != backwordIndex + && forwardDisplayArea.right + mTextPadding > backwardDisplayArea.left) { + // Overlapped in the middle, skip the pair of labels + continue; + } + + drawAxisLabelText(canvas, forwardIndex, forwardDisplayArea, baselineY); + drawAxisLabelText(canvas, backwordIndex, backwardDisplayArea, baselineY); + + forwardCheckLine = forwardDisplayArea.right + mTextPadding; + backwardCheckLine = backwardDisplayArea.left - mTextPadding; + + // If the number of labels is odd, draw the middle label first + if (forwardIndex == 0 && backwordIndex % 2 == 0) { + final int middleIndex = backwordIndex / 2; + middleDisplayArea = displayAreas[middleIndex]; + if (middleDisplayArea.left < forwardCheckLine + || middleDisplayArea.right > backwardCheckLine) { + // Overlapped at left or right, skip the pair of labels + continue; + } + drawAxisLabelText(canvas, middleIndex, middleDisplayArea, baselineY); + } } } - private void drawAxisLabelsBetweenTrapezoids(Canvas canvas, float[] xOffsets) { - // Draws the 1st axis label info. + private void drawAxisLabelText( + Canvas canvas, final int index, final Rect displayArea, final float baselineY) { + mTextPaint.setTextAlign(Paint.Align.CENTER); canvas.drawText( - getAxisLabelText(0), xOffsets[0] - mAxisLabelsBounds[0].left, getAxisLabelY(0), + mViewModel.texts().get(index), + displayArea.centerX(), + baselineY, mTextPaint); - final int latestIndex = mLabelsIndexes.length - 1; - // Draws the last axis label info. - canvas.drawText( - getAxisLabelText(latestIndex), - xOffsets[latestIndex] - - mAxisLabelsBounds[latestIndex].width() - - mAxisLabelsBounds[latestIndex].left, - getAxisLabelY(latestIndex), - mTextPaint); - // Draws the rest of axis label info since it is located in the center. - for (int index = 1; index <= mLabelsIndexes.length - 2; index++) { - canvas.drawText( - getAxisLabelText(index), - xOffsets[index] - - (mAxisLabelsBounds[index].width() - mAxisLabelsBounds[index].left) - * .5f, - getAxisLabelY(index), - mTextPaint); - } - } - - private void drawAxisLabelsCenterOfTrapezoids( - Canvas canvas, float[] xOffsets, float unitWidth) { - for (int index = 0; index < mLabelsIndexes.length - 1; index++) { - canvas.drawText( - getAxisLabelText(index), - xOffsets[index] + (unitWidth - (mAxisLabelsBounds[index].width() - - mAxisLabelsBounds[index].left)) * .5f, - getAxisLabelY(index), - mTextPaint); - } - } - - private int getAxisLabelY(int index) { - return getHeight() - - mAxisLabelsBounds[index].height() - + (mAxisLabelsBounds[index].height() + mAxisLabelsBounds[index].top) - + round(mTextPadding * 1.5f); } private void drawTrapezoids(Canvas canvas) { @@ -556,8 +598,11 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli return BatteryChartViewModel.SELECTED_INDEX_INVALID; } - private String getAxisLabelText(int labelIndex) { - return mViewModel.texts().get(mLabelsIndexes[labelIndex]); + private void initializeAxisLabelsBounds() { + mAxisLabelsBounds.clear(); + for (int i = 0; i < mViewModel.size(); i++) { + mAxisLabelsBounds.add(new Rect()); + } } private static boolean isTrapezoidValid( @@ -613,33 +658,6 @@ public class BatteryChartViewV2 extends AppCompatImageView implements View.OnCli return false; } - private static Map buildModelSizeToLabelIndexesMap() { - final Map result = new HashMap<>(); - result.put(2, new Integer[]{0, 1}); - result.put(3, new Integer[]{0, 1, 2}); - result.put(4, new Integer[]{0, 1, 2, 3}); - result.put(5, new Integer[]{0, 1, 2, 3, 4}); - result.put(6, new Integer[]{0, 1, 2, 3, 4, 5}); - result.put(7, new Integer[]{0, 1, 2, 3, 4, 5, 6}); - result.put(8, new Integer[]{0, 1, 2, 3, 4, 5, 6, 7}); - result.put(9, new Integer[]{0, 2, 4, 6, 8}); - result.put(10, new Integer[]{0, 3, 6, 9}); - result.put(11, new Integer[]{0, 5, 10}); - result.put(12, new Integer[]{0, 4, 7, 11}); - result.put(13, new Integer[]{0, 4, 8, 12}); - return result; - } - - private static Rect[] initializeAxisLabelsBounds() { - final int maxLabelsLength = MODEL_SIZE_TO_LABEL_INDEXES_MAP.values().stream().max( - Comparator.comparingInt(indexes -> indexes.length)).get().length; - final Rect[] bounds = new Rect[maxLabelsLength]; - for (int i = 0; i < maxLabelsLength; i++) { - bounds[i] = new Rect(); - } - return bounds; - } - // A container class for each trapezoid left and right location. @VisibleForTesting static final class TrapezoidSlot {