Merge "[DataUsage] The drop down menu and gear icon not aligned" into tm-dev
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 469f201..867fecb 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1626,4 +1626,24 @@
         <item>300000</item>
     </string-array>
 
+    <!-- Developer settings: ingress rate limit entries. [DO NOT TRANSLATE] -->
+    <string-array name="ingress_rate_limit_entries">
+        <item>@string/ingress_rate_limit_no_limit_entry</item>
+        <item>128kbps</item>
+        <item>256kbps</item>
+        <item>1Mbps</item>
+        <item>5Mbps</item>
+        <item>15Mbps</item>
+    </string-array>
+
+    <!-- Developer settings: ingress rate limit values. [DO NOT TRANSLATE] -->
+    <string-array name="ingress_rate_limit_values">
+        <item>-1</item> <!-- -1 codes for disabled -->
+        <item>16000</item> <!-- 128kbps == 16000B/s -->
+        <item>32000</item> <!-- 256kbps == 32000B/s -->
+        <item>125000</item> <!-- 1Mbps == 125000B/s -->
+        <item>625000</item> <!-- 5Mbps == 625000/s -->
+        <item>1875000</item> <!-- 15Mbps == 1875000/s -->
+    </string-array>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 45fac0a..2c0780f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -14037,4 +14037,13 @@
     <string name="bluetooth_details_head_tracking_title">Make audio more realistic</string>
     <!-- The summary of the head tracking [CHAR LIMIT=none] -->
     <string name="bluetooth_details_head_tracking_summary">Shift positioning of audio so it sounds more natural.</string>
+
+    <!-- Developer Settings: Title for network bandwidth ingress rate limit [CHAR LIMIT=none] -->
+    <string name="ingress_rate_limit_title">Network download rate limit</string>
+    <!-- Developer Settings: Summary for network bandwidth ingress rate limit [CHAR LIMIT=none] -->
+    <string name="ingress_rate_limit_summary">Configure the network bandwidth ingress rate limit which is applied to all networks that provide internet connectivity.</string>
+    <!-- Developer Settings: Dialog for network bandwidth ingress rate limit [CHAR LIMIT=none] -->
+    <string name="ingress_rate_limit_dialog_title">Configure network download rate limit</string>
+    <!-- Developer Settings: Dialog ListPreference option to disable network bandwidth ingress rate limit [CHAR LIMIT=none] -->
+    <string name="ingress_rate_limit_no_limit_entry">No limit</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index c3ed5b2..bd0aea1 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -432,12 +432,12 @@
     </style>
 
     <style name="LockPatternContainerStyle">
-        <item name="android:maxHeight">400dp</item>
-        <item name="android:maxWidth">420dp</item>
+        <item name="android:maxHeight">620dp</item>
+        <item name="android:maxWidth">620dp</item>
         <item name="android:minHeight">0dp</item>
         <item name="android:minWidth">0dp</item>
         <item name="android:paddingBottom">0dp</item>
-        <item name="android:paddingHorizontal">44dp</item>
+        <item name="android:paddingHorizontal">0dp</item>
         <item name="android:paddingTop">0dp</item>
     </style>
 
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 808131c..75395c0 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -297,6 +297,14 @@
             android:title="@string/tethering_hardware_offload"
             android:summary="@string/tethering_hardware_offload_summary" />
 
+        <ListPreference
+            android:key="ingress_rate_limit"
+            android:title="@string/ingress_rate_limit_title"
+            android:summary="@string/ingress_rate_limit_summary"
+            android:dialogTitle="@string/ingress_rate_limit_dialog_title"
+            android:entries="@array/ingress_rate_limit_entries"
+            android:entryValues="@array/ingress_rate_limit_values" />
+
         <com.android.settingslib.RestrictedPreference
             android:key="default_usb_configuration"
             android:fragment="com.android.settings.connecteddevice.usb.UsbDefaultFragment"
diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
index 8d29301..3cb22e0 100644
--- a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
@@ -43,7 +43,6 @@
 import com.android.settings.fuelgauge.BatteryEntry;
 import com.android.settings.fuelgauge.BatteryUsageStatsLoader;
 import com.android.settings.fuelgauge.BatteryUtils;
-import com.android.settings.fuelgauge.ConvertUtils;
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -118,11 +117,7 @@
         }
 
         if (mBatteryDiffEntry != null) {
-            Log.i(TAG, "BatteryDiffEntry not null, launch : "
-                    + mBatteryDiffEntry.getPackageName()
-                    + " | uid : "
-                    + mBatteryDiffEntry.mBatteryHistEntry.mUid
-                    + " with DiffEntry data");
+            Log.i(TAG, "handlePreferenceTreeClick():\n" + mBatteryDiffEntry);
             AdvancedPowerUsageDetail.startBatteryDetailPage(
                     mParent.getActivity(),
                     mParent,
@@ -176,30 +171,11 @@
                 if (mPackageName == null) {
                     return null;
                 }
-                final List<BatteryDiffEntry> batteryDiffEntries =
-                        BatteryChartPreferenceController.getBatteryLast24HrUsageData(mContext);
-                if (batteryDiffEntries == null) {
-                    return null;
-                }
-                // Filter entry with consumer type to avoid system app,
-                // then use user id to divide normal app and work profile app,
-                // return target application from filter list by package name.
-                return batteryDiffEntries.stream()
-                        .filter(entry -> entry.mBatteryHistEntry.mConsumerType
-                                == ConvertUtils.CONSUMER_TYPE_UID_BATTERY)
-                        .filter(entry -> entry.mBatteryHistEntry.mUserId == mUserId)
-                        .filter(entry -> {
-                            if (mPackageName.equals(entry.getPackageName())) {
-                                Log.i(TAG, "Return target application: "
-                                        + entry.mBatteryHistEntry.mPackageName
-                                        + " | uid: " + entry.mBatteryHistEntry.mUid
-                                        + " | userId: " + entry.mBatteryHistEntry.mUserId);
-                                return true;
-                            }
-                            return false;
-                        })
-                        .findFirst()
-                        .orElse(/* other */null);
+                final BatteryDiffEntry entry =
+                        BatteryChartPreferenceController.getBatteryLast24HrUsageData(
+                                mContext, mPackageName, mUserId);
+                Log.d(TAG, "loadBatteryDiffEntries():\n" + entry);
+                return entry;
             }
 
             @Override
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index 405c090..66059e7 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -312,9 +312,10 @@
                 default:
                     FingerprintManager fpm = Utils.getFingerprintManagerOrNull(this);
                     int enrolled = fpm.getEnrolledFingerprints().size();
-                    int max = getResources().getInteger(
-                            com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
-                    if (enrolled >= max) {
+                    final List<FingerprintSensorPropertiesInternal> props =
+                            fpm.getSensorPropertiesInternal();
+                    final int maxEnrollments = props.get(0).maxEnrollmentsPerUser;
+                    if (enrolled >= maxEnrollments) {
                         finish();
                     } else {
                         // We came back from enrolling but it wasn't completed, start again.
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
index 80a6f05..74e844a 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFinish.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
@@ -36,6 +37,7 @@
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupcompat.util.WizardManagerHelper;
 
+import java.util.List;
 /**
  * Activity which concludes fingerprint enrollment.
  */
@@ -92,10 +94,11 @@
         final FingerprintManager fpm = Utils.getFingerprintManagerOrNull(this);
         boolean hideAddAnother = false;
         if (fpm != null) {
+            final List<FingerprintSensorPropertiesInternal> props =
+                    fpm.getSensorPropertiesInternal();
+            int maxEnrollments = props.get(0).maxEnrollmentsPerUser;
             int enrolled = fpm.getEnrolledFingerprints(mUserId).size();
-            int max = getResources().getInteger(
-                    com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
-            hideAddAnother = enrolled >= max;
+            hideAddAnother = enrolled >= maxEnrollments;
         }
         if (hideAddAnother) {
             // Don't show "Add" button if too many fingerprints already added
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
index abc6d53..a5832ea 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java
@@ -457,8 +457,10 @@
             final Preference addPreference = findPreference(KEY_FINGERPRINT_ADD);
 
             /* Disable preference if too many fingerprints added */
-            final int max = getContext().getResources().getInteger(
-                    com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
+            final List<FingerprintSensorPropertiesInternal> props =
+                    mFingerprintManager.getSensorPropertiesInternal();
+            // This will need to be updated for devices with multiple fingerprint sensors
+            final int max = props.get(0).maxEnrollmentsPerUser;
             boolean tooMany = mFingerprintManager.getEnrolledFingerprints(mUserId).size() >= max;
             // retryFingerprint() will be called when remove finishes
             // need to disable enroll or have a way to determine if enroll is in progress
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index fa5a5be..636404e 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -606,6 +606,7 @@
         controllers.add(new SharedDataPreferenceController(context));
         controllers.add(new OverlaySettingsPreferenceController(context));
         controllers.add(new StylusHandwritingPreferenceController(context));
+        controllers.add(new IngressRateLimitPreferenceController((context)));
 
         return controllers;
     }
diff --git a/src/com/android/settings/development/IngressRateLimitPreferenceController.java b/src/com/android/settings/development/IngressRateLimitPreferenceController.java
new file mode 100644
index 0000000..2e84aba
--- /dev/null
+++ b/src/com/android/settings/development/IngressRateLimitPreferenceController.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.development;
+
+import android.content.Context;
+import android.net.ConnectivitySettingsManager;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+/**
+ * Controller for ingress rate limit developer setting.
+ */
+public class IngressRateLimitPreferenceController extends DeveloperOptionsPreferenceController
+        implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+    private static final String TAG = "IngressRateLimitPreferenceController";
+    private static final String INGRESS_RATE_LIMIT_KEY = "ingress_rate_limit";
+    private static final int RATE_LIMIT_DISABLED = -1;
+
+    public IngressRateLimitPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return INGRESS_RATE_LIMIT_KEY;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        final long value = Long.parseLong(newValue.toString());
+        try {
+            ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, value);
+            return true;
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "invalid rate limit", e);
+            return false;
+        }
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        final String ingressRateLimit = String.valueOf(
+                ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(mContext));
+
+        // verify ingressRateLimit is valid / present in ListPreference; else do nothing.
+        final CharSequence[] entryValues = ((ListPreference) preference).getEntryValues();
+        for (int i = 0; i < entryValues.length; i++) {
+            if (ingressRateLimit.contentEquals(entryValues[i])) {
+                ((ListPreference) preference).setValue(ingressRateLimit);
+                return;
+            }
+        }
+    }
+
+    @Override
+    protected void onDeveloperOptionsSwitchDisabled() {
+        super.onDeveloperOptionsSwitchDisabled();
+        // disable rate limiting when developer options are disabled
+        ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext,
+                RATE_LIMIT_DISABLED);
+        ((ListPreference) mPreference).setValue(String.valueOf(RATE_LIMIT_DISABLED));
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
index 90a86f1..249ee49 100644
--- a/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryChartPreferenceController.java
@@ -617,6 +617,7 @@
         return true;
     }
 
+    /** Used for {@link AppBatteryPreferenceController}. */
     public static List<BatteryDiffEntry> getBatteryLast24HrUsageData(Context context) {
         final long start = System.currentTimeMillis();
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
@@ -638,6 +639,28 @@
         return batteryIndexedMap.get(BatteryChartView.SELECTED_INDEX_ALL);
     }
 
+    /** Used for {@link AppBatteryPreferenceController}. */
+    public static BatteryDiffEntry getBatteryLast24HrUsageData(
+            Context context, String packageName, int userId) {
+        if (packageName == null) {
+            return null;
+        }
+        final List<BatteryDiffEntry> entries = getBatteryLast24HrUsageData(context);
+        if (entries == null) {
+            return null;
+        }
+        for (BatteryDiffEntry entry : entries) {
+            final BatteryHistEntry batteryHistEntry = entry.mBatteryHistEntry;
+            if (batteryHistEntry != null
+                    && batteryHistEntry.mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY
+                    && batteryHistEntry.mUserId == userId
+                    && packageName.equals(entry.getPackageName())) {
+                return entry;
+            }
+        }
+        return null;
+    }
+
     private static long[] getBatteryHistoryKeys(
             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
         final List<Long> batteryHistoryKeyList =
diff --git a/src/com/android/settings/nfc/PaymentDefaultDialog.java b/src/com/android/settings/nfc/PaymentDefaultDialog.java
index 75746ce..d333b3d 100644
--- a/src/com/android/settings/nfc/PaymentDefaultDialog.java
+++ b/src/com/android/settings/nfc/PaymentDefaultDialog.java
@@ -59,10 +59,13 @@
                 CardEmulation.EXTRA_SERVICE_COMPONENT);
         String category = intent.getStringExtra(CardEmulation.EXTRA_CATEGORY);
         UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+
+        int userId;
         if (userHandle == null) {
-            userHandle = UserHandle.CURRENT;
+            userId = UserHandle.myUserId();
+        } else {
+            userId = userHandle.getIdentifier();
         }
-        int userId = userHandle.getIdentifier();
 
         setResult(RESULT_CANCELED);
         if (!buildDialog(component, category, userId)) {
diff --git a/tests/robotests/src/com/android/settings/development/IngressRateLimitPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/IngressRateLimitPreferenceControllerTest.java
new file mode 100644
index 0000000..0f85a14
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/IngressRateLimitPreferenceControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.development;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivitySettingsManager;
+
+import androidx.preference.ListPreference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class IngressRateLimitPreferenceControllerTest {
+    private Context mContext = RuntimeEnvironment.application;
+    private ListPreference mPreference;
+    private IngressRateLimitPreferenceController mController;
+
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mPreference = new ListPreference(mContext);
+        mPreference.setEntries(R.array.ingress_rate_limit_entries);
+        mPreference.setEntryValues(R.array.ingress_rate_limit_values);
+
+        mController = new IngressRateLimitPreferenceController(mContext);
+        when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
+                mPreference);
+        mController.displayPreference(mPreferenceScreen);
+    }
+
+    @Test
+    public void onPreferenceChanged_select5Mbits_shouldEnableIngressRateLimit() {
+        final long newRateLimit = 625000; // 5mbit == 625000 B/s
+        assertThat(mController.onPreferenceChange(mPreference, newRateLimit)).isTrue();
+
+        final long configuredRateLimit =
+                ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(mContext);
+        assertThat(configuredRateLimit).isEqualTo(newRateLimit);
+    }
+
+    @Test
+    public void onPreferenceChanged_selectDisabled_shouldDisableIngressRateLimit() {
+        final long disabledRateLimit = -1; // -1 == disabled
+        assertThat(mController.onPreferenceChange(mPreference, disabledRateLimit)).isTrue();
+
+        final long configuredRateLimit =
+                ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(mContext);
+        assertThat(configuredRateLimit).isEqualTo(disabledRateLimit);
+    }
+
+    @Test
+    public void onPreferenceChanged_invalidValue_returnsFalse() {
+        final long invalidRateLimit = -123;
+        assertThat(mController.onPreferenceChange(mPreference, invalidRateLimit)).isFalse();
+    }
+
+    @Test
+    public void updateState_preferenceShouldBeSelected() {
+        final long newRateLimit = 625000; // 5mbit == 625000 B/s
+        ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, newRateLimit);
+        mController.updateState(mPreference);
+        assertThat(Long.parseLong(mPreference.getValue())).isEqualTo(newRateLimit);
+    }
+
+    @Test
+    public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() {
+        final long newRateLimit = 625000; // 5mbit == 625000 B/s
+        ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, newRateLimit);
+        mController.updateState(mPreference);
+
+        mController.onDeveloperOptionsSwitchDisabled();
+        assertThat(Long.parseLong(mPreference.getValue())).isEqualTo(-1);
+        assertThat(ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
+                mContext)).isEqualTo(-1);
+    }
+}