Merge "Update the limit of split screen dialog strings." into udc-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 21b3fa4..f537415 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2785,6 +2785,7 @@
</intent-filter>
<intent-filter>
<action android:name="com.android.settings.action.SETTINGS" />
+ <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
</intent-filter>
<meta-data android:name="com.android.settings.order" android:value="-40"/>
<meta-data android:name="com.android.settings.category"
diff --git a/res/drawable/ic_settings_safety_center.xml b/res/drawable/ic_settings_safety_center.xml
index e817b71..46684d1 100644
--- a/res/drawable/ic_settings_safety_center.xml
+++ b/res/drawable/ic_settings_safety_center.xml
@@ -19,13 +19,11 @@
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/textColorPrimary">
-
+ <group>
+ <clip-path
+ android:pathData="M4,2h16v20h-16z"/>
<path
- android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
- <path
- android:fillColor="#000000"
- android:pathData="M12,4.14l6,2.25v4.71c0,2.12-0.62,4-1.88,5.74c-1.13,1.55-2.48,2.56-4.12,3.09c-1.64-0.52-2.99-1.54-4.12-3.09 C6.62,15.1,6,13.22,6,11.1V6.39L12,4.14 M12,2L4,5v6.1c0,2.53,0.75,4.84,2.26,6.91C7.77,20.09,9.68,21.42,12,22 c2.32-0.58,4.23-1.91,5.74-3.99C19.25,15.94,20,13.63,20,11.1V5L12,2L12,2z" />
- <path
- android:fillColor="#000000"
- android:pathData="M 10.95 15.55 L 7.42 12.02 L 8.85 10.6 L 10.95 12.7 L 15.17 8.47 L 16.6 9.9 Z" />
-</vector>
\ No newline at end of file
+ android:pathData="M18.92,4.4L12.56,2.1C12.38,2.03 12.19,2 12,2C11.81,2 11.62,2.03 11.44,2.1L5.08,4.4C4.43,4.63 4,5.25 4,5.94V10.32C4.02,11.07 4.07,11.79 4.17,12.54C4.64,15.72 6.44,19.33 11.37,21.85C11.57,21.95 11.78,22 12,22C12.22,22 12.43,21.95 12.63,21.85C13.08,21.62 13.5,21.37 13.9,21.12C14.04,21.05 14.18,20.96 14.32,20.86C17.98,18.43 19.41,15.32 19.82,12.54C19.92,11.8 19.98,11.07 19.99,10.32V5.94C19.99,5.25 19.56,4.64 18.91,4.4H18.92ZM12.25,19.78C12.1,19.87 11.9,19.87 11.74,19.78C8.5,17.97 6.62,15.43 6.15,12.27C6.06,11.59 6.01,10.94 6,10.32V6.55C6,6.34 6.13,6.15 6.33,6.08L8.26,5.38C8.11,5.89 8.03,6.44 8.03,7.04C8.04,8.91 9.03,10.68 10.7,11.8C11.15,12.08 12.39,12.89 12.78,13.19C13.27,13.57 13.95,14.21 14.26,14.74C15.29,16.52 14.26,18.46 13.1,19.27C12.83,19.45 12.55,19.62 12.25,19.79V19.78ZM17.85,12.24C17.66,13.49 17.26,14.63 16.65,15.68C16.57,15.04 16.37,14.38 15.99,13.74C15.42,12.75 14.33,11.86 14.01,11.61C13.47,11.19 11.94,10.22 11.79,10.12C10.69,9.39 10.04,8.23 10.03,6.99C10.03,5.01 11.25,4.34 11.98,4.12C12.08,4.09 12.19,4.1 12.29,4.13L17.68,6.08C17.88,6.15 18.01,6.34 18.01,6.55V10.28C18,10.94 17.95,11.59 17.86,12.24H17.85Z"
+ android:fillColor="#000000"/>
+ </group>
+</vector>
diff --git a/res/layout/locale_order_list.xml b/res/layout/locale_order_list.xml
index acde145..5c1db15 100644
--- a/res/layout/locale_order_list.xml
+++ b/res/layout/locale_order_list.xml
@@ -14,11 +14,11 @@
limitations under the License.
-->
-<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layoutDirection="locale"
- android:textDirection="locale">
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layoutDirection="locale"
+ android:textDirection="locale">
<LinearLayout
android:layout_width="match_parent"
@@ -48,4 +48,4 @@
</LinearLayout>
-</androidx.core.widget.NestedScrollView>
+</FrameLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aa64923..5a5bc27 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4946,6 +4946,8 @@
<string name="keywords_color_correction">adjust color </string>
<!-- List of synonyms used in the settings search bar to find the “Color inversion”. [CHAR LIMIT=NONE] -->
<string name="keywords_color_inversion">turn screen dark, turn screen light</string>
+ <!-- List of synonyms used in the settings search bar to find the “Contrast”. [CHAR LIMIT=NONE] -->
+ <string name="keywords_contrast">color contrast</string>
<!-- List of synonyms used in the settings search bar to find the “Accessibility Menu”. [CHAR LIMIT=NONE] -->
<string name="keywords_accessibility_menu"></string>
<!-- List of synonyms used in the settings search bar to find the “Switch Access”. [CHAR LIMIT=NONE] -->
@@ -11522,9 +11524,12 @@
<!-- Summary for UWB preference. [CHAR_LIMIT=NONE]-->
<string name="uwb_settings_summary">Helps identify the relative position of nearby devices that have UWB</string>
- <!-- Summary for UWB preference when airplane mode is disabled. [CHAR_LIMIT=NONE]-->
+ <!-- Summary for UWB preference when airplane mode is enabled. [CHAR_LIMIT=NONE]-->
<string name="uwb_settings_summary_airplane_mode">Turn off airplane mode to use UWB </string>
+ <!-- Summary for UWB preference when UWB is unavailable due to regulatory requirements. [CHAR_LIMIT=NONE]-->
+ <string name="uwb_settings_summary_no_uwb_regulatory">UWB is unavailable in the current location</string>
+
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->
<string name="camera_toggle_title">Camera access</string>
<!-- Label for the camera use toggle [CHAR LIMIT=40] -->
@@ -12021,6 +12026,15 @@
<!-- Button to close the dialog without saving in screen flash color selection dialog. [CHAR LIMIT=20] -->
<string name="color_selector_dialog_cancel">Cancel</string>
+ <!-- Title for the contrast preference fragment [CHAR LIMIT=35] -->
+ <string name="contrast_title">Contrast</string>
+ <!-- 'Standard' contrast option [CHAR LIMIT=20] -->
+ <string name="contrast_standard">Standard</string>
+ <!-- 'Medium' contrast option [CHAR LIMIT=20] -->
+ <string name="contrast_medium">Medium</string>
+ <!-- 'High' contrast option [CHAR LIMIT=20] -->
+ <string name="contrast_high">High</string>
+
<!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
<string name="dock_multi_instances_not_supported_text">"This app can only be opened in 1 window"</string>
</resources>
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index 3b514b7..9e1dbad 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -515,6 +515,12 @@
android:title="@string/transparent_navigation_bar"
android:summary="@string/transparent_navigation_bar_summary" />
+ <Preference
+ android:key="contrast_preference"
+ android:title="@string/contrast_title"
+ android:persistent="false"
+ settings:keywords="@string/keywords_contrast" />
+
</PreferenceCategory>
<PreferenceCategory
diff --git a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
index b1b420d..695ea0d 100644
--- a/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
+++ b/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModel.java
@@ -16,6 +16,8 @@
package com.android.settings.biometrics2.ui.viewmodel;
+import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
+
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_REMAINING;
import static com.android.settings.biometrics2.ui.model.EnrollmentProgress.INITIAL_STEPS;
@@ -63,7 +65,6 @@
private final int mUserId;
private final FingerprintUpdater mFingerprintUpdater;
- private final MessageDisplayController mMessageDisplayController;
@Nullable private CancellationSignal mCancellationSignal = null;
private final EnrollmentCallback mEnrollmentCallback = new EnrollmentCallback() {
@@ -81,6 +82,9 @@
@Override
public void onEnrollmentHelp(int helpMsgId, CharSequence helpString) {
+ if (DEBUG) {
+ Log.d(TAG, "onEnrollmentHelp(" + helpMsgId + ", " + helpString + ")");
+ }
mHelpMessageLiveData.postValue(new EnrollmentStatusMessage(helpMsgId, helpString));
}
@@ -113,20 +117,6 @@
super(application);
mFingerprintUpdater = fingerprintUpdater;
mUserId = userId;
-
- final Resources res = application.getResources();
- mMessageDisplayController =
- res.getBoolean(R.bool.enrollment_message_display_controller_flag)
- ? new MessageDisplayController(
- application.getMainThreadHandler(),
- mEnrollmentCallback,
- SystemClock.elapsedRealtimeClock(),
- res.getInteger(R.integer.enrollment_help_minimum_time_display),
- res.getInteger(R.integer.enrollment_progress_minimum_time_display),
- res.getBoolean(R.bool.enrollment_progress_priority_over_help),
- res.getBoolean(R.bool.enrollment_prioritize_acquire_messages),
- res.getInteger(R.integer.enrollment_collect_time))
- : null;
}
public void setToken(byte[] token) {
@@ -195,9 +185,24 @@
mErrorMessageLiveData.setValue(null);
mCancellationSignal = new CancellationSignal();
- mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId,
- mMessageDisplayController != null ? mMessageDisplayController : mEnrollmentCallback,
- reason);
+
+ final Resources res = getApplication().getResources();
+ if (reason == ENROLL_ENROLL
+ && res.getBoolean(R.bool.enrollment_message_display_controller_flag)) {
+ final EnrollmentCallback callback = new MessageDisplayController(
+ getApplication().getMainThreadHandler(),
+ mEnrollmentCallback,
+ SystemClock.elapsedRealtimeClock(),
+ res.getInteger(R.integer.enrollment_help_minimum_time_display),
+ res.getInteger(R.integer.enrollment_progress_minimum_time_display),
+ res.getBoolean(R.bool.enrollment_progress_priority_over_help),
+ res.getBoolean(R.bool.enrollment_prioritize_acquire_messages),
+ res.getInteger(R.integer.enrollment_collect_time));
+ mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, callback, reason);
+ } else {
+ mFingerprintUpdater.enroll(mToken, mCancellationSignal, mUserId, mEnrollmentCallback,
+ reason);
+ }
return true;
}
diff --git a/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java b/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java
index 2bcc02e..1e5145a 100644
--- a/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java
+++ b/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java
@@ -22,6 +22,7 @@
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.deviceinfo.StorageDashboardFragment;
import com.android.settings.inputmethod.AvailableVirtualKeyboardFragment;
+import com.android.settings.inputmethod.NewKeyboardLayoutEnabledLocalesFragment;
import com.android.settings.location.LocationServices;
import java.util.Map;
@@ -49,5 +50,7 @@
ProfileSelectStorageFragment.class.getName());
FRAGMENT_MAP.put(AvailableVirtualKeyboardFragment.class.getName(),
ProfileSelectKeyboardFragment.class.getName());
+ FRAGMENT_MAP.put(NewKeyboardLayoutEnabledLocalesFragment.class.getName(),
+ ProfileSelectPhysicalKeyboardFragment.class.getName());
}
}
diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectPhysicalKeyboardFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectPhysicalKeyboardFragment.java
new file mode 100644
index 0000000..3c1546e
--- /dev/null
+++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectPhysicalKeyboardFragment.java
@@ -0,0 +1,72 @@
+/*
+ * 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.dashboard.profileselector;
+
+import android.hardware.input.InputDeviceIdentifier;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.settings.R;
+import com.android.settings.inputmethod.NewKeyboardLayoutEnabledLocalesFragment;
+
+/**
+ * When current user has work profile, this fragment used following fragments to represent the
+ * enabled IMEs keyboard layout settings page.
+ *
+ * <p>{@link NewKeyboardLayoutEnabledLocalesFragment} used to show both of personal/work user
+ * enabled IMEs and their physical keyboard layouts.</p>
+ */
+public final class ProfileSelectPhysicalKeyboardFragment extends ProfileSelectFragment {
+
+ private InputDeviceIdentifier mInputDeviceIdentifier;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ Bundle arguments = getArguments();
+ mInputDeviceIdentifier =
+ arguments.getParcelable(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER);
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.keyboard_settings_enabled_locales_list;
+ }
+
+ @Override
+ public Fragment[] getFragments() {
+ final Bundle personalOnly = new Bundle();
+ personalOnly.putInt(EXTRA_PROFILE, ProfileType.PERSONAL);
+ final Fragment personalFragment = new NewKeyboardLayoutEnabledLocalesFragment();
+ personalOnly.putParcelable(
+ Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, mInputDeviceIdentifier);
+ personalFragment.setArguments(personalOnly);
+
+ final Bundle workOnly = new Bundle();
+ workOnly.putInt(EXTRA_PROFILE, ProfileType.WORK);
+ final Fragment workFragment = new NewKeyboardLayoutEnabledLocalesFragment();
+ workOnly.putParcelable(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, mInputDeviceIdentifier);
+ workFragment.setArguments(workOnly);
+
+ return new Fragment[]{
+ personalFragment,
+ workFragment
+ };
+ }
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 3180a79..68fe08e 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -19,6 +19,7 @@
import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES;
import android.app.Activity;
+import android.app.UiModeManager;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
@@ -63,6 +64,7 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.actionbar.SearchMenuController;
+import com.android.settings.theme.ContrastPreferenceController;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -685,6 +687,8 @@
controllers.add(new IngressRateLimitPreferenceController((context)));
controllers.add(new BackAnimationPreferenceController(context, fragment));
controllers.add(new PhantomProcessPreferenceController(context));
+ controllers.add(new ContrastPreferenceController(
+ context, context.getSystemService(UiModeManager.class)));
return controllers;
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
index 0be6c98..2121c60 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java
@@ -23,6 +23,7 @@
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
@@ -48,9 +49,11 @@
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.widget.FooterPreference;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/** Controller for battery usage breakdown preference group. */
public class BatteryUsageBreakdownController extends BasePreferenceController
@@ -61,6 +64,7 @@
private static final String SPINNER_PREFERENCE_KEY = "battery_usage_spinner";
private static final String APP_LIST_PREFERENCE_KEY = "app_list";
private static final String PACKAGE_NAME_NONE = "none";
+ private static final List<BatteryDiffEntry> EMPTY_ENTRY_LIST = new ArrayList<>();
private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
@@ -183,7 +187,7 @@
if (mSpinnerPosition != position) {
mSpinnerPosition = position;
mHandler.post(() -> {
- removeAndCacheAllPreferences();
+ removeAndCacheAllUnusedPreferences();
addAllPreferences();
mMetricsFeatureProvider.action(
mPrefContext,
@@ -238,27 +242,34 @@
private void showSpinnerAndAppList() {
if (mBatteryDiffData == null) {
mHandler.post(() -> {
- removeAndCacheAllPreferences();
+ removeAndCacheAllUnusedPreferences();
});
return;
}
mSpinnerPreference.setVisible(true);
mAppListPreferenceGroup.setVisible(true);
mHandler.post(() -> {
- removeAndCacheAllPreferences();
+ removeAndCacheAllUnusedPreferences();
addAllPreferences();
});
}
+ private List<BatteryDiffEntry> getBatteryDiffEntries() {
+ if (mBatteryDiffData == null) {
+ return EMPTY_ENTRY_LIST;
+ }
+ return mSpinnerPosition == 0
+ ? mBatteryDiffData.getAppDiffEntryList()
+ : mBatteryDiffData.getSystemDiffEntryList();
+ }
+
@VisibleForTesting
void addAllPreferences() {
if (mBatteryDiffData == null) {
return;
}
final long start = System.currentTimeMillis();
- final List<BatteryDiffEntry> entries = mSpinnerPosition == 0
- ? mBatteryDiffData.getAppDiffEntryList()
- : mBatteryDiffData.getSystemDiffEntryList();
+ final List<BatteryDiffEntry> entries = getBatteryDiffEntries();
int prefIndex = mAppListPreferenceGroup.getPreferenceCount();
for (BatteryDiffEntry entry : entries) {
boolean isAdded = false;
@@ -272,7 +283,6 @@
PowerGaugePreference pref = mAppListPreferenceGroup.findPreference(prefKey);
if (pref != null) {
isAdded = true;
- Log.w(TAG, "preference should be removed for:" + entry.getPackageName());
} else {
pref = (PowerGaugePreference) mPreferenceCache.get(prefKey);
}
@@ -301,16 +311,25 @@
}
@VisibleForTesting
- void removeAndCacheAllPreferences() {
+ void removeAndCacheAllUnusedPreferences() {
+ List<BatteryDiffEntry> entries = getBatteryDiffEntries();
+ Set<String> entryKeySet = new ArraySet<>();
+ for (BatteryDiffEntry entry : entries) {
+ entryKeySet.add(entry.getKey());
+ }
+
final int prefsCount = mAppListPreferenceGroup.getPreferenceCount();
- for (int index = 0; index < prefsCount; index++) {
+ for (int index = prefsCount - 1; index >= 0; index--) {
final Preference pref = mAppListPreferenceGroup.getPreference(index);
- if (TextUtils.isEmpty(pref.getKey())) {
+ if (entryKeySet.contains(pref.getKey())) {
+ // The pref is still used, don't remove.
continue;
}
- mPreferenceCache.put(pref.getKey(), pref);
+ if (!TextUtils.isEmpty(pref.getKey())) {
+ mPreferenceCache.put(pref.getKey(), pref);
+ }
+ mAppListPreferenceGroup.removePreference(pref);
}
- mAppListPreferenceGroup.removeAll();
}
@VisibleForTesting
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 6270eb3..ec1a4be 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -1446,31 +1446,39 @@
final int workProfileUserId =
userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE;
// Each time slot usage diff data =
- // Math.abs(timestamp[i+2] data - timestamp[i+1] data) +
- // Math.abs(timestamp[i+1] data - timestamp[i] data);
- // since we want to aggregate every two hours data into a single time slot.
+ // sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
+ // since we want to aggregate every hour usage diff data into a single time slot.
for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
final Map<Integer, BatteryDiffData> dailyDiffMap = new ArrayMap<>();
resultMap.put(dailyIndex, dailyDiffMap);
if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
continue;
}
- final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
- for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
+ final List<Long> hourlyTimestamps =
+ hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
+ for (int hourlyIndex = 0; hourlyIndex < hourlyTimestamps.size() - 1; hourlyIndex++) {
+ final Long startTimestamp = hourlyTimestamps.get(hourlyIndex);
+ final Long endTimestamp = hourlyTimestamps.get(hourlyIndex + 1);
+ final long slotDuration = endTimestamp - startTimestamp;
+ List<Map<String, BatteryHistEntry>> slotBatteryHistoryList = new ArrayList<>();
+ for (Long timestamp = startTimestamp; timestamp <= endTimestamp;
+ timestamp += DateUtils.HOUR_IN_MILLIS) {
+ slotBatteryHistoryList.add(
+ batteryHistoryMap.getOrDefault(timestamp, EMPTY_BATTERY_MAP));
+ }
final BatteryDiffData hourlyBatteryDiffData =
insertHourlyUsageDiffDataPerSlot(
context,
currentUserId,
workProfileUserId,
- hourlyIndex,
- timestamps,
+ slotDuration,
systemAppsPackageNames,
systemAppsUids,
appUsagePeriodMap == null
|| appUsagePeriodMap.get(dailyIndex) == null
? null
: appUsagePeriodMap.get(dailyIndex).get(hourlyIndex),
- batteryHistoryMap);
+ slotBatteryHistoryList);
dailyDiffMap.put(hourlyIndex, hourlyBatteryDiffData);
}
}
@@ -1508,54 +1516,42 @@
final Context context,
final int currentUserId,
final int workProfileUserId,
- final int currentIndex,
- final List<Long> timestamps,
+ final long slotDuration,
final Set<String> systemAppsPackageNames,
final Set<Integer> systemAppsUids,
final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap,
- final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
+ final List<Map<String, BatteryHistEntry>> slotBatteryHistoryList) {
final List<BatteryDiffEntry> appEntries = new ArrayList<>();
final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
- final Long currentTimestamp = timestamps.get(currentIndex);
- final Long nextTimestamp = currentTimestamp + DateUtils.HOUR_IN_MILLIS;
- final Long nextTwoTimestamp = nextTimestamp + DateUtils.HOUR_IN_MILLIS;
- // Fetches BatteryHistEntry data from corresponding time slot.
- final Map<String, BatteryHistEntry> currentBatteryHistMap =
- batteryHistoryMap.getOrDefault(currentTimestamp, EMPTY_BATTERY_MAP);
- final Map<String, BatteryHistEntry> nextBatteryHistMap =
- batteryHistoryMap.getOrDefault(nextTimestamp, EMPTY_BATTERY_MAP);
- final Map<String, BatteryHistEntry> nextTwoBatteryHistMap =
- batteryHistoryMap.getOrDefault(nextTwoTimestamp, EMPTY_BATTERY_MAP);
- // We should not get the empty list since we have at least one fake data to record
- // the battery level and status in each time slot, the empty list is used to
- // represent there is no enough data to apply interpolation arithmetic.
- if (currentBatteryHistMap.isEmpty()
- || nextBatteryHistMap.isEmpty()
- || nextTwoBatteryHistMap.isEmpty()) {
- return null;
- }
-
// Collects all keys in these three time slot records as all populations.
final Set<String> allBatteryHistEntryKeys = new ArraySet<>();
- allBatteryHistEntryKeys.addAll(currentBatteryHistMap.keySet());
- allBatteryHistEntryKeys.addAll(nextBatteryHistMap.keySet());
- allBatteryHistEntryKeys.addAll(nextTwoBatteryHistMap.keySet());
+ for (Map<String, BatteryHistEntry> slotBatteryHistMap : slotBatteryHistoryList) {
+ if (slotBatteryHistMap.isEmpty()) {
+ // We should not get the empty list since we have at least one fake data to record
+ // the battery level and status in each time slot, the empty list is used to
+ // represent there is no enough data to apply interpolation arithmetic.
+ return null;
+ }
+ allBatteryHistEntryKeys.addAll(slotBatteryHistMap.keySet());
+ }
// Calculates all packages diff usage data in a specific time slot.
for (String key : allBatteryHistEntryKeys) {
if (key == null) {
continue;
}
- final BatteryHistEntry currentEntry =
- currentBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
- final BatteryHistEntry nextEntry =
- nextBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
- final BatteryHistEntry nextTwoEntry =
- nextTwoBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
- final BatteryHistEntry selectedBatteryEntry =
- selectBatteryHistEntry(currentEntry, nextEntry, nextTwoEntry);
+ BatteryHistEntry selectedBatteryEntry = null;
+ final List<BatteryHistEntry> batteryHistEntries = new ArrayList<>();
+ for (Map<String, BatteryHistEntry> slotBatteryHistMap : slotBatteryHistoryList) {
+ BatteryHistEntry entry =
+ slotBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
+ batteryHistEntries.add(entry);
+ if (selectedBatteryEntry == null && entry != EMPTY_BATTERY_HIST_ENTRY) {
+ selectedBatteryEntry = entry;
+ }
+ }
if (selectedBatteryEntry == null) {
continue;
}
@@ -1568,41 +1564,45 @@
}
// Cumulative values is a specific time slot for a specific app.
- long foregroundUsageTimeInMs =
- getDiffValue(
- currentEntry.mForegroundUsageTimeInMs,
- nextEntry.mForegroundUsageTimeInMs,
- nextTwoEntry.mForegroundUsageTimeInMs);
- long backgroundUsageTimeInMs =
- getDiffValue(
- currentEntry.mBackgroundUsageTimeInMs,
- nextEntry.mBackgroundUsageTimeInMs,
- nextTwoEntry.mBackgroundUsageTimeInMs);
- double consumePower =
- getDiffValue(
- currentEntry.mConsumePower,
- nextEntry.mConsumePower,
- nextTwoEntry.mConsumePower);
- double foregroundUsageConsumePower =
- getDiffValue(
- currentEntry.mForegroundUsageConsumePower,
- nextEntry.mForegroundUsageConsumePower,
- nextTwoEntry.mForegroundUsageConsumePower);
- double foregroundServiceUsageConsumePower =
- getDiffValue(
- currentEntry.mForegroundServiceUsageConsumePower,
- nextEntry.mForegroundServiceUsageConsumePower,
- nextTwoEntry.mForegroundServiceUsageConsumePower);
- double backgroundUsageConsumePower =
- getDiffValue(
- currentEntry.mBackgroundUsageConsumePower,
- nextEntry.mBackgroundUsageConsumePower,
- nextTwoEntry.mBackgroundUsageConsumePower);
- double cachedUsageConsumePower =
- getDiffValue(
- currentEntry.mCachedUsageConsumePower,
- nextEntry.mCachedUsageConsumePower,
- nextTwoEntry.mCachedUsageConsumePower);
+ long foregroundUsageTimeInMs = 0;
+ long backgroundUsageTimeInMs = 0;
+ double consumePower = 0;
+ double foregroundUsageConsumePower = 0;
+ double foregroundServiceUsageConsumePower = 0;
+ double backgroundUsageConsumePower = 0;
+ double cachedUsageConsumePower = 0;
+ for (int i = 0; i < batteryHistEntries.size() - 1; i++) {
+ final BatteryHistEntry currentEntry = batteryHistEntries.get(i);
+ final BatteryHistEntry nextEntry = batteryHistEntries.get(i + 1);
+ foregroundUsageTimeInMs +=
+ getDiffValue(
+ currentEntry.mForegroundUsageTimeInMs,
+ nextEntry.mForegroundUsageTimeInMs);
+ backgroundUsageTimeInMs +=
+ getDiffValue(
+ currentEntry.mBackgroundUsageTimeInMs,
+ nextEntry.mBackgroundUsageTimeInMs);
+ consumePower +=
+ getDiffValue(
+ currentEntry.mConsumePower,
+ nextEntry.mConsumePower);
+ foregroundUsageConsumePower +=
+ getDiffValue(
+ currentEntry.mForegroundUsageConsumePower,
+ nextEntry.mForegroundUsageConsumePower);
+ foregroundServiceUsageConsumePower +=
+ getDiffValue(
+ currentEntry.mForegroundServiceUsageConsumePower,
+ nextEntry.mForegroundServiceUsageConsumePower);
+ backgroundUsageConsumePower +=
+ getDiffValue(
+ currentEntry.mBackgroundUsageConsumePower,
+ nextEntry.mBackgroundUsageConsumePower);
+ cachedUsageConsumePower +=
+ getDiffValue(
+ currentEntry.mCachedUsageConsumePower,
+ nextEntry.mCachedUsageConsumePower);
+ }
// Excludes entry since we don't have enough data to calculate.
if (foregroundUsageTimeInMs == 0
&& backgroundUsageTimeInMs == 0
@@ -1613,13 +1613,13 @@
// will apply the interpolation arithmetic.
final float totalUsageTimeInMs =
foregroundUsageTimeInMs + backgroundUsageTimeInMs;
- if (totalUsageTimeInMs > TOTAL_HOURLY_TIME_THRESHOLD) {
- final float ratio = TOTAL_HOURLY_TIME_THRESHOLD / totalUsageTimeInMs;
+ if (totalUsageTimeInMs > slotDuration) {
+ final float ratio = slotDuration / totalUsageTimeInMs;
if (sDebug) {
Log.w(TAG, String.format("abnormal usage time %d|%d for:\n%s",
Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(),
Duration.ofMillis(backgroundUsageTimeInMs).getSeconds(),
- currentEntry));
+ selectedBatteryEntry));
}
foregroundUsageTimeInMs =
Math.round(foregroundUsageTimeInMs * ratio);
@@ -1634,14 +1634,14 @@
// Compute the screen on time and make sure it won't exceed the threshold.
final long screenOnTime = Math.min(
- (long) TOTAL_HOURLY_TIME_THRESHOLD,
+ (long) slotDuration,
getScreenOnTime(
appUsageMap,
selectedBatteryEntry.mUserId,
selectedBatteryEntry.mPackageName));
// Make sure the background + screen-on time will not exceed the threshold.
backgroundUsageTimeInMs = Math.min(
- backgroundUsageTimeInMs, (long) TOTAL_HOURLY_TIME_THRESHOLD - screenOnTime);
+ backgroundUsageTimeInMs, (long) slotDuration - screenOnTime);
final BatteryDiffEntry currentBatteryDiffEntry = new BatteryDiffEntry(
context,
foregroundUsageTimeInMs,
@@ -1948,23 +1948,12 @@
return calendar.getTimeInMillis();
}
- private static long getDiffValue(long v1, long v2, long v3) {
- return (v2 > v1 ? v2 - v1 : 0) + (v3 > v2 ? v3 - v2 : 0);
+ private static long getDiffValue(long v1, long v2) {
+ return v2 > v1 ? v2 - v1 : 0;
}
- private static double getDiffValue(double v1, double v2, double v3) {
- return (v2 > v1 ? v2 - v1 : 0) + (v3 > v2 ? v3 - v2 : 0);
- }
-
- @Nullable
- private static BatteryHistEntry selectBatteryHistEntry(
- final BatteryHistEntry... batteryHistEntries) {
- for (BatteryHistEntry entry : batteryHistEntries) {
- if (entry != null && entry != EMPTY_BATTERY_HIST_ENTRY) {
- return entry;
- }
- }
- return null;
+ private static double getDiffValue(double v1, double v2) {
+ return v2 > v1 ? v2 - v1 : 0;
}
private static Set<String> getSystemAppsPackageNames(Context context) {
diff --git a/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java b/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java
index 3344f4e..054ce61 100644
--- a/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java
+++ b/src/com/android/settings/inputmethod/NewKeyboardLayoutEnabledLocalesFragment.java
@@ -23,6 +23,7 @@
import android.hardware.input.KeyboardLayout;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
@@ -34,8 +35,10 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.inputmethod.NewKeyboardSettingsUtils.KeyboardInfo;
import java.util.ArrayList;
@@ -57,6 +60,39 @@
private ArrayList<KeyboardInfo> mKeyboardInfoList = new ArrayList<>();
@Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ mContext = context;
+ final int profileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE);
+ final int currentUserId = UserHandle.myUserId();
+ final int newUserId;
+ final UserManager userManager = mContext.getSystemService(UserManager.class);
+
+ switch (profileType) {
+ case ProfileSelectFragment.ProfileType.WORK: {
+ // If the user is a managed profile user, use currentUserId directly. Or get the
+ // managed profile userId instead.
+ newUserId = userManager.isManagedProfile()
+ ? currentUserId : Utils.getManagedProfileId(userManager, currentUserId);
+ break;
+ }
+ case ProfileSelectFragment.ProfileType.PERSONAL: {
+ final UserHandle primaryUser = userManager.getPrimaryUser().getUserHandle();
+ newUserId = primaryUser.getIdentifier();
+ break;
+ }
+ default:
+ newUserId = currentUserId;
+ }
+
+ mUserId = newUserId;
+ mIm = mContext.getSystemService(InputManager.class);
+ mImm = mContext.getSystemService(InputMethodManager.class);
+ mInputDeviceId = -1;
+ }
+
+ @Override
public void onActivityCreated(final Bundle icicle) {
super.onActivityCreated(icicle);
Bundle arguments = getArguments();
@@ -74,13 +110,39 @@
}
final String title = inputDevice.getName();
getActivity().setTitle(title);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mIm.registerInputDeviceListener(this, null);
+ InputDevice inputDevice =
+ NewKeyboardSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier);
+ if (inputDevice == null) {
+ getActivity().finish();
+ return;
+ }
+ mInputDeviceId = inputDevice.getId();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
updateCheckedState();
}
+ @Override
+ public void onStop() {
+ super.onStop();
+ mIm.unregisterInputDeviceListener(this);
+ mInputDeviceId = -1;
+ }
+
private void updateCheckedState() {
if (NewKeyboardSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier) == null) {
return;
}
+
PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.removeAll();
List<InputMethodInfo> infoList = mImm.getEnabledInputMethodListAsUser(mUserId);
@@ -95,7 +157,7 @@
for (InputMethodInfo info : infoList) {
mKeyboardInfoList.clear();
List<InputMethodSubtype> subtypes =
- mImm.getEnabledInputMethodSubtypeList(info, true);
+ mImm.getEnabledInputMethodSubtypeListAsUser(info.getId(), true, mUserId);
for (InputMethodSubtype subtype : subtypes) {
if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
mapLanguageWithLayout(info, subtype);
@@ -189,42 +251,6 @@
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mContext = getContext();
- mIm = mContext.getSystemService(InputManager.class);
- mImm = mContext.getSystemService(InputMethodManager.class);
- mInputDeviceId = -1;
- mUserId = UserHandle.myUserId();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- mIm.registerInputDeviceListener(this, null);
- InputDevice inputDevice =
- NewKeyboardSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier);
- if (inputDevice == null) {
- getActivity().finish();
- return;
- }
- mInputDeviceId = inputDevice.getId();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- mIm.unregisterInputDeviceListener(this);
- mInputDeviceId = -1;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateCheckedState();
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index bdb9295..6317f24 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -329,6 +329,7 @@
list.setLayoutManager(llm);
list.setHasFixedSize(true);
+ list.setNestedScrollingEnabled(false);
mAdapter.setRecyclerView(list);
list.setAdapter(mAdapter);
diff --git a/src/com/android/settings/network/AirplaneModePreferenceController.java b/src/com/android/settings/network/AirplaneModePreferenceController.java
index 720a334..5b4ce1b 100644
--- a/src/com/android/settings/network/AirplaneModePreferenceController.java
+++ b/src/com/android/settings/network/AirplaneModePreferenceController.java
@@ -79,7 +79,7 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
- if (KEY_AIRPLANE_MODE.equals(preference.getKey())
+ if (KEY_AIRPLANE_MODE.equals(preference.getKey()) && isAvailable()
&& mAirplaneModeEnabler.isInEcmMode()) {
// In ECM mode launch ECM app dialog
if (mFragment != null) {
@@ -141,12 +141,14 @@
@Override
public void onDestroy() {
- mAirplaneModeEnabler.close();
+ if (isAvailable()) {
+ mAirplaneModeEnabler.close();
+ }
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == REQUEST_CODE_EXIT_ECM) {
+ if (requestCode == REQUEST_CODE_EXIT_ECM && isAvailable()) {
final boolean isChoiceYes = (resultCode == Activity.RESULT_OK);
// Set Airplane mode based on the return value and checkbox state
mAirplaneModeEnabler.setAirplaneModeInECM(isChoiceYes,
@@ -156,7 +158,7 @@
@Override
public boolean isChecked() {
- return mAirplaneModeEnabler.isAirplaneModeOn();
+ return isAvailable() && mAirplaneModeEnabler.isAirplaneModeOn();
}
@Override
@@ -164,7 +166,9 @@
if (isChecked() == isChecked) {
return false;
}
- mAirplaneModeEnabler.setAirplaneMode(isChecked);
+ if (isAvailable()) {
+ mAirplaneModeEnabler.setAirplaneMode(isChecked);
+ }
return true;
}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index ce83053..83d2117 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -351,9 +351,14 @@
}
@Override
+ public void onPause() {
+ mMobileNetworkRepository.removeRegister(this);
+ super.onPause();
+ }
+
+ @Override
public void onDestroy() {
super.onDestroy();
- mMobileNetworkRepository.removeRegister(this);
}
@VisibleForTesting
diff --git a/src/com/android/settings/nfc/NfcEnabler.java b/src/com/android/settings/nfc/NfcEnabler.java
index 88482d5..fe8762b8 100644
--- a/src/com/android/settings/nfc/NfcEnabler.java
+++ b/src/com/android/settings/nfc/NfcEnabler.java
@@ -18,9 +18,6 @@
import android.content.Context;
import android.nfc.NfcAdapter;
-import android.provider.Settings;
-
-import androidx.annotation.VisibleForTesting;
import com.android.settingslib.widget.MainSwitchPreference;
@@ -41,7 +38,7 @@
switch (newState) {
case NfcAdapter.STATE_OFF:
mPreference.updateStatus(false);
- mPreference.setEnabled(isToggleable());
+ mPreference.setEnabled(true);
break;
case NfcAdapter.STATE_ON:
mPreference.updateStatus(true);
@@ -57,15 +54,4 @@
break;
}
}
-
- @VisibleForTesting
- boolean isToggleable() {
- if (NfcPreferenceController.isToggleableInAirplaneMode(mContext)
- || !NfcPreferenceController.shouldTurnOffNFCInAirplaneMode(mContext)) {
- return true;
- }
- final int airplaneMode = Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
- return airplaneMode != 1;
- }
}
diff --git a/src/com/android/settings/nfc/NfcPreferenceController.java b/src/com/android/settings/nfc/NfcPreferenceController.java
index 483fead..2ba00c6 100644
--- a/src/com/android/settings/nfc/NfcPreferenceController.java
+++ b/src/com/android/settings/nfc/NfcPreferenceController.java
@@ -21,7 +21,6 @@
import android.content.IntentFilter;
import android.net.Uri;
import android.nfc.NfcAdapter;
-import android.provider.Settings;
import android.util.Log;
import android.widget.Switch;
@@ -128,18 +127,6 @@
}
}
- public static boolean shouldTurnOffNFCInAirplaneMode(Context context) {
- final String airplaneModeRadios = Settings.Global.getString(context.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_RADIOS);
- return airplaneModeRadios != null && airplaneModeRadios.contains(Settings.Global.RADIO_NFC);
- }
-
- public static boolean isToggleableInAirplaneMode(Context context) {
- final String toggleable = Settings.Global.getString(context.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
- return toggleable != null && toggleable.contains(Settings.Global.RADIO_NFC);
- }
-
/**
* Listener for background changes to NFC.
*
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
index 1bb6df0..f4cfabc 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialBaseFragment.java
@@ -17,6 +17,7 @@
// TODO (b/35202196): move this class out of the root of the package.
package com.android.settings.password;
+import static android.app.Activity.RESULT_FIRST_USER;
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
@@ -24,7 +25,6 @@
import android.annotation.Nullable;
import android.app.Dialog;
import android.app.KeyguardManager;
-import android.app.RemoteLockscreenValidationResult;
import android.app.RemoteLockscreenValidationSession;
import android.app.admin.DevicePolicyManager;
import android.app.admin.ManagedSubscriptionsPolicy;
@@ -38,7 +38,6 @@
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
-import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -55,14 +54,11 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
-import com.android.security.SecureBox;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
+import com.google.android.setupdesign.GlifLayout;
/**
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
@@ -89,9 +85,13 @@
/** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
+ protected static final String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION =
+ "remote_lockscreen_validation";
+
protected boolean mReturnCredentials = false;
protected boolean mReturnGatekeeperPassword = false;
protected boolean mForceVerifyPath = false;
+ protected GlifLayout mGlifLayout;
protected CheckBox mCheckBox;
protected Button mCancelButton;
/** Button allowing managed profile password reset, null when is not shown. */
@@ -109,8 +109,8 @@
protected BiometricManager mBiometricManager;
@Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
/** Credential saved so the credential can be set for device if remote validation passes */
- @Nullable protected LockscreenCredential mDeviceCredentialGuess;
@Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
+ protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment;
private boolean isInternalActivity() {
return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
@@ -136,8 +136,8 @@
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) {
mRemoteValidation = true;
} else {
- Log.e(TAG, "Remote device credential validation not enabled.");
- getActivity().finish();
+ onRemoteLockscreenValidationFailure(
+ "Remote lockscreen validation not enabled.");
}
}
if (mRemoteValidation) {
@@ -146,23 +146,31 @@
RemoteLockscreenValidationSession.class);
if (mRemoteLockscreenValidationSession == null
|| mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) {
- Log.e(TAG, "RemoteLockscreenValidationSession is null or "
+ onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or "
+ "no more attempts for remote lockscreen validation.");
- getActivity().finish();
}
ComponentName remoteLockscreenValidationServiceComponent =
intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
if (remoteLockscreenValidationServiceComponent == null) {
- Log.e(TAG, "RemoteLockscreenValidationService ComponentName is null");
- getActivity().finish();
+ onRemoteLockscreenValidationFailure(
+ "RemoteLockscreenValidationService ComponentName is null");
}
mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient
.create(getContext(), remoteLockscreenValidationServiceComponent);
if (!mRemoteLockscreenValidationClient.isServiceAvailable()) {
- Log.e(TAG, String.format("RemoteLockscreenValidationService at %s is not available",
+ onRemoteLockscreenValidationFailure(String.format(
+ "RemoteLockscreenValidationService at %s is not available",
remoteLockscreenValidationServiceComponent.getClassName()));
- getActivity().finish();
+ }
+
+ mRemoteLockscreenValidationFragment =
+ (RemoteLockscreenValidationFragment) getFragmentManager()
+ .findFragmentByTag(FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION);
+ if (mRemoteLockscreenValidationFragment == null) {
+ mRemoteLockscreenValidationFragment = new RemoteLockscreenValidationFragment();
+ getFragmentManager().beginTransaction().add(mRemoteLockscreenValidationFragment,
+ FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION).commit();
}
}
@@ -194,8 +202,10 @@
mCancelButton.setOnClickListener(v -> {
if (hasAlternateButton) {
getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
+ getActivity().finish();
+ } else if (mRemoteValidation) {
+ onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed.");
}
- getActivity().finish();
});
setupForgotButtonIfManagedProfile(view);
@@ -299,17 +309,11 @@
if (mRemoteLockscreenValidationClient != null) {
mRemoteLockscreenValidationClient.disconnect();
}
- if (mDeviceCredentialGuess != null) {
- mDeviceCredentialGuess.zeroize();
- }
super.onDestroy();
}
protected abstract void authenticationSucceeded();
- protected abstract void onRemoteDeviceCredentialValidationResult(
- RemoteLockscreenValidationResult result);
-
public void prepareEnterAnimation() {
}
@@ -411,43 +415,33 @@
}
protected void validateGuess(LockscreenCredential credentialGuess) {
- if (mCheckBox.isChecked()) {
- // Keep credential in memory since user wants to set guess as screen lock.
- mDeviceCredentialGuess = credentialGuess;
- } else if (mDeviceCredentialGuess != null) {
- mDeviceCredentialGuess.zeroize();
- }
-
- mRemoteLockscreenValidationClient.validateLockscreenGuess(
- encryptDeviceCredentialGuess(credentialGuess.getCredential()),
- new IRemoteLockscreenValidationCallback.Stub() {
- @Override
- public void onSuccess(RemoteLockscreenValidationResult result) {
- mHandler.post(()->onRemoteDeviceCredentialValidationResult(result));
- }
-
- @Override
- public void onFailure(String message) {
- Log.e(TAG, "A failure occurred while trying "
- + "to validate lockscreen guess: " + message);
- mHandler.post(()->getActivity().finish());
- }
- });
+ mRemoteLockscreenValidationFragment.validateLockscreenGuess(
+ mRemoteLockscreenValidationClient, credentialGuess,
+ mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked());
}
- private byte[] encryptDeviceCredentialGuess(byte[] guess) {
- try {
- byte[] encodedPublicKey = mRemoteLockscreenValidationSession.getSourcePublicKey();
- PublicKey publicKey = SecureBox.decodePublicKey(encodedPublicKey);
- return SecureBox.encrypt(
- publicKey,
- /* sharedSecret= */ null,
- LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
- guess);
- } catch (NoSuchAlgorithmException | InvalidKeyException e) {
- Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e);
- return new byte[0];
+ protected void updateRemoteLockscreenValidationViews() {
+ if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) {
+ return;
}
+
+ boolean enable = mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
+ mGlifLayout.setProgressBarShown(enable);
+ mCheckBox.setEnabled(!enable);
+ mCancelButton.setEnabled(!enable);
+ }
+
+ /**
+ * Finishes the activity with result code {@link android.app.Activity#RESULT_FIRST_USER}
+ * after logging the error message.
+ * @param message Optional message to log.
+ */
+ public void onRemoteLockscreenValidationFailure(String message) {
+ if (!TextUtils.isEmpty(message)) {
+ Log.w(TAG, message);
+ }
+ getActivity().setResult(RESULT_FIRST_USER);
+ getActivity().finish();
}
protected abstract void onShowError();
diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java
index 81bd8c2..03b89f2 100644
--- a/src/com/android/settings/password/ConfirmLockPassword.java
+++ b/src/com/android/settings/password/ConfirmLockPassword.java
@@ -71,8 +71,6 @@
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.google.android.setupdesign.GlifLayout;
-
import java.util.ArrayList;
public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
@@ -127,7 +125,8 @@
public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
implements OnClickListener, OnEditorActionListener,
- CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener {
+ CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener,
+ RemoteLockscreenValidationFragment.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
private ImeAwareEditText mPasswordEntry;
private TextViewInputDisabler mPasswordEntryInputDisabler;
@@ -140,7 +139,6 @@
private AppearAnimationUtils mAppearAnimationUtils;
private DisappearAnimationUtils mDisappearAnimationUtils;
private boolean mIsManagedProfile;
- private GlifLayout mGlifLayout;
private CharSequence mCheckBoxLabel;
// required constructor for fragments
@@ -255,6 +253,7 @@
? R.string.lockpassword_forgot_password
: R.string.lockpassword_forgot_pin);
}
+ updateRemoteLockscreenValidationViews();
}
if (mForgotButton != null) {
@@ -405,6 +404,9 @@
mCountdownTimer = null;
}
mCredentialCheckResultTracker.setListener(null);
+ if (mRemoteLockscreenValidationFragment != null) {
+ mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
+ }
}
@Override
@@ -426,6 +428,9 @@
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
}
mCredentialCheckResultTracker.setListener(this);
+ if (mRemoteLockscreenValidationFragment != null) {
+ mRemoteLockscreenValidationFragment.setListener(this, mHandler);
+ }
}
@Override
@@ -436,13 +441,17 @@
private void updatePasswordEntry() {
final boolean isLockedOut =
mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
- mPasswordEntry.setEnabled(!isLockedOut);
- mPasswordEntryInputDisabler.setInputEnabled(!isLockedOut);
- if (isLockedOut) {
- mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/);
- } else {
+ final boolean isRemoteLockscreenValidationInProgress =
+ mRemoteLockscreenValidationFragment != null
+ && mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
+ boolean shouldEnableInput = !isLockedOut && !isRemoteLockscreenValidationInProgress;
+ mPasswordEntry.setEnabled(shouldEnableInput);
+ mPasswordEntryInputDisabler.setInputEnabled(shouldEnableInput);
+ if (shouldEnableInput) {
mPasswordEntry.scheduleShowSoftInput();
mPasswordEntry.requestFocus();
+ } else {
+ mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), /* flags= */0);
}
}
@@ -472,7 +481,8 @@
if (mRemoteValidation) {
validateGuess(credential);
- mGlifLayout.setProgressBarShown(true);
+ updateRemoteLockscreenValidationViews();
+ updatePasswordEntry();
return;
}
@@ -604,14 +614,15 @@
}
@Override
- protected void onRemoteDeviceCredentialValidationResult(
+ public void onRemoteLockscreenValidationResult(
RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
- if (mCheckBox.isChecked()) {
+ if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
+ .getLockscreenCredential() != null) {
+ Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
ChooseLockPassword.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPassword.SaveAndFinishWorker();
- Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
.commit();
getFragmentManager().executePendingTransactions();
@@ -619,14 +630,14 @@
saveAndFinishWorker.start(
mLockPatternUtils,
/* requestGatekeeperPassword= */ true,
- mDeviceCredentialGuess,
+ mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null,
mEffectiveUserId);
- return;
+ } else {
+ mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
+ /* timeoutMs= */ 0, mEffectiveUserId);
}
- mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
- /* timeoutMs= */ 0, mEffectiveUserId);
- break;
+ return;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
@@ -636,12 +647,15 @@
(int) result.getTimeoutMillis(), mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
- getActivity().finish();
- break;
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
- getActivity().finish();
+ onRemoteLockscreenValidationFailure(String.format(
+ "Cannot continue remote lockscreen validation. ResultCode=%d",
+ result.getResultCode()));
+ break;
}
- mGlifLayout.setProgressBarShown(false);
+ updateRemoteLockscreenValidationViews();
+ updatePasswordEntry();
+ mRemoteLockscreenValidationFragment.clearLockscreenCredential();
}
@Override
@@ -701,21 +715,18 @@
}
/**
- * Callback for when the device credential guess used for remote validation was set as the
- * current device's device credential.
+ * Callback for when the current device's lockscreen was set to the guess used for
+ * remote lockscreen validation.
*/
@Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
- if (mDeviceCredentialGuess != null) {
- mDeviceCredentialGuess.zeroize();
- }
+ Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
+ mRemoteLockscreenValidationFragment.clearLockscreenCredential();
Intent result = new Intent();
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
}
-
- mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
/* timeoutMs= */ 0, mEffectiveUserId);
}
diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java
index 7c21739..7db25fd 100644
--- a/src/com/android/settings/password/ConfirmLockPattern.java
+++ b/src/com/android/settings/password/ConfirmLockPattern.java
@@ -59,8 +59,6 @@
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
-import com.google.android.setupdesign.GlifLayout;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -97,7 +95,7 @@
public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
- SaveChosenLockWorkerBase.Listener {
+ SaveChosenLockWorkerBase.Listener, RemoteLockscreenValidationFragment.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
@@ -107,7 +105,6 @@
private boolean mDisappearing = false;
private CountDownTimer mCountdownTimer;
- private GlifLayout mGlifLayout;
private View mSudContent;
// caller-supplied text for various prompts
@@ -239,6 +236,7 @@
if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) {
mCancelButton.setText(R.string.lockpassword_forgot_pattern);
}
+ updateRemoteLockscreenValidationViews();
}
if (mForgotButton != null) {
@@ -259,6 +257,9 @@
mCountdownTimer.cancel();
}
mCredentialCheckResultTracker.setListener(null);
+ if (mRemoteLockscreenValidationFragment != null) {
+ mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
+ }
}
@Override
@@ -281,6 +282,12 @@
updateStage(Stage.NeedToUnlock);
}
mCredentialCheckResultTracker.setListener(this);
+ if (mRemoteLockscreenValidationFragment != null) {
+ mRemoteLockscreenValidationFragment.setListener(this, mHandler);
+ if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) {
+ mLockPatternView.setEnabled(false);
+ }
+ }
}
@Override
@@ -502,7 +509,7 @@
if (mRemoteValidation) {
validateGuess(credential);
- mGlifLayout.setProgressBarShown(true);
+ updateRemoteLockscreenValidationViews();
return;
}
@@ -617,11 +624,12 @@
}
@Override
- protected void onRemoteDeviceCredentialValidationResult(
+ public void onRemoteLockscreenValidationResult(
RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
- if (mCheckBox.isChecked()) {
+ if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
+ .getLockscreenCredential() != null) {
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPattern.SaveAndFinishWorker();
@@ -632,14 +640,14 @@
saveAndFinishWorker.start(
mLockPatternUtils,
/* requestGatekeeperPassword= */ true,
- mDeviceCredentialGuess,
+ mRemoteLockscreenValidationFragment.getLockscreenCredential(),
/* currentCredential= */ null,
mEffectiveUserId);
- return;
+ } else {
+ mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
+ /* timeoutMs= */ 0, mEffectiveUserId);
}
- mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
- /* timeoutMs= */ 0, mEffectiveUserId);
- break;
+ return;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
@@ -649,12 +657,14 @@
(int) result.getTimeoutMillis(), mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
- getActivity().finish();
- break;
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
- getActivity().finish();
+ onRemoteLockscreenValidationFailure(String.format(
+ "Cannot continue remote lockscreen validation. ResultCode=%d",
+ result.getResultCode()));
+ break;
}
- mGlifLayout.setProgressBarShown(false);
+ updateRemoteLockscreenValidationViews();
+ mRemoteLockscreenValidationFragment.clearLockscreenCredential();
}
@Override
@@ -728,21 +738,18 @@
}
/**
- * Callback for when the device credential guess used for remote validation was set as the
- * current device's device credential.
+ * Callback for when the current device's lockscreen to the guess used for
+ * remote lockscreen validation.
*/
@Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
- if (mDeviceCredentialGuess != null) {
- mDeviceCredentialGuess.zeroize();
- }
+ Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
+ mRemoteLockscreenValidationFragment.clearLockscreenCredential();
Intent result = new Intent();
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
}
-
- mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
/* timeoutMs= */ 0, mEffectiveUserId);
}
diff --git a/src/com/android/settings/password/RemoteLockscreenValidationFragment.java b/src/com/android/settings/password/RemoteLockscreenValidationFragment.java
new file mode 100644
index 0000000..5819376
--- /dev/null
+++ b/src/com/android/settings/password/RemoteLockscreenValidationFragment.java
@@ -0,0 +1,190 @@
+/*
+ * 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.password;
+
+import android.app.RemoteLockscreenValidationResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
+import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
+import android.util.Log;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.security.SecureBox;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+
+/**
+ * A fragment used to hold state for remote lockscreen validation.
+ * If the original listener is ever re-created, the new listener must be set again using
+ * {@link #setListener} so that the validation result does not get handled by the old listener.
+ */
+public class RemoteLockscreenValidationFragment extends Fragment {
+
+ private static final String TAG = RemoteLockscreenValidationFragment.class.getSimpleName();
+
+ private Listener mListener;
+ private Handler mHandler;
+ private boolean mIsInProgress;
+ private RemoteLockscreenValidationResult mResult;
+ private String mErrorMessage;
+ private LockscreenCredential mLockscreenCredential;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ }
+
+ @Override
+ public void onDestroy() {
+ clearLockscreenCredential();
+ if (mResult != null && mErrorMessage != null) {
+ Log.w(TAG, "Unprocessed remote lockscreen validation result");
+ }
+ super.onDestroy();
+ }
+
+ /**
+ * @return {@code true} if remote lockscreen guess validation has started or
+ * the validation result has not yet been handled.
+ */
+ public boolean isRemoteValidationInProgress() {
+ return mIsInProgress;
+ }
+
+ /**
+ * Sets the listener and handler that will handle the result of remote lockscreen validation.
+ * Unprocessed results or failures will be handled after the listener is set.
+ */
+ public void setListener(Listener listener, Handler handler) {
+ if (mListener == listener) {
+ return;
+ }
+
+ mListener = listener;
+ mHandler = handler;
+
+ if (mResult != null) {
+ handleResult();
+ } else if (mErrorMessage != null) {
+ handleFailure();
+ }
+ }
+
+ /**
+ * @return {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
+ */
+ public LockscreenCredential getLockscreenCredential() {
+ return mLockscreenCredential;
+ }
+
+ /**
+ * Clears the {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
+ */
+ public void clearLockscreenCredential() {
+ if (mLockscreenCredential != null) {
+ mLockscreenCredential.zeroize();
+ mLockscreenCredential = null;
+ }
+ }
+
+ /**
+ * Validates the lockscreen guess on the remote device.
+ * @param remoteLockscreenValidationClient the client that should be used to send the guess to
+ * for validation
+ * @param guess the {@link LockscreenCredential} guess that the user entered
+ * @param encryptionKey the key that should be used to encrypt the guess before validation
+ * @param shouldCacheGuess whether to cache to guess so it can be used to set the current
+ * device's lockscreen after validation succeeds.
+ */
+ public void validateLockscreenGuess(
+ RemoteLockscreenValidationClient remoteLockscreenValidationClient,
+ LockscreenCredential guess, byte[] encryptionKey, boolean shouldCacheGuess) {
+ if (shouldCacheGuess) {
+ mLockscreenCredential = guess;
+ }
+
+ remoteLockscreenValidationClient.validateLockscreenGuess(
+ encryptDeviceCredentialGuess(guess.getCredential(), encryptionKey),
+ new IRemoteLockscreenValidationCallback.Stub() {
+ @Override
+ public void onSuccess(RemoteLockscreenValidationResult result) {
+ mResult = result;
+ handleResult();
+ }
+
+ @Override
+ public void onFailure(String message) {
+ mErrorMessage = message;
+ handleFailure();
+ }
+ });
+ mIsInProgress = true;
+ }
+
+ private byte[] encryptDeviceCredentialGuess(byte[] guess, byte[] encryptionKey) {
+ try {
+ PublicKey publicKey = SecureBox.decodePublicKey(encryptionKey);
+ return SecureBox.encrypt(
+ publicKey,
+ /* sharedSecret= */ null,
+ LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
+ guess);
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e);
+ return new byte[0];
+ }
+ }
+
+ private void handleResult() {
+ if (mHandler != null) {
+ mHandler.post(()-> {
+ if (mListener == null || mResult == null) {
+ return;
+ }
+ mIsInProgress = false;
+ mListener.onRemoteLockscreenValidationResult(mResult);
+ mResult = null;
+ });
+ }
+ }
+
+ private void handleFailure() {
+ if (mHandler != null) {
+ mHandler.post(()-> {
+ if (mListener == null || mErrorMessage == null) {
+ return;
+ }
+ mIsInProgress = false;
+ mListener.onRemoteLockscreenValidationFailure(
+ String.format("Remote lockscreen validation failed: %s", mErrorMessage));
+ mErrorMessage = null;
+ });
+ }
+ }
+
+ interface Listener {
+ void onRemoteLockscreenValidationResult(RemoteLockscreenValidationResult result);
+ void onRemoteLockscreenValidationFailure(String message);
+ }
+}
diff --git a/src/com/android/settings/security/MemtagHelper.java b/src/com/android/settings/security/MemtagHelper.java
index b04dcf9..a09a219 100644
--- a/src/com/android/settings/security/MemtagHelper.java
+++ b/src/com/android/settings/security/MemtagHelper.java
@@ -17,6 +17,7 @@
package com.android.settings.security;
import android.os.SystemProperties;
+import android.text.TextUtils;
import com.android.internal.os.Zygote;
import com.android.settings.R;
@@ -25,18 +26,15 @@
import java.util.Arrays;
public class MemtagHelper {
+ public static final String DEVICE_CONFIG_PROP =
+ "persist.device_config.runtime_native_boot.bootloader_override";
+
public static boolean isForcedOff() {
- return "force_off"
- .equals(
- SystemProperties.get(
- "persist.device_config.memory_safety_native_boot.bootloader_override"));
+ return TextUtils.equals("force_on", SystemProperties.get(DEVICE_CONFIG_PROP));
}
public static boolean isForcedOn() {
- return "force_on"
- .equals(
- SystemProperties.get(
- "persist.device_config.memory_safety_native_boot.bootloader_override"));
+ return TextUtils.equals("force_on", SystemProperties.get(DEVICE_CONFIG_PROP));
}
public static boolean isChecked() {
diff --git a/src/com/android/settings/theme/ContrastPreferenceController.kt b/src/com/android/settings/theme/ContrastPreferenceController.kt
new file mode 100644
index 0000000..7f3844a
--- /dev/null
+++ b/src/com/android/settings/theme/ContrastPreferenceController.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.theme
+
+import android.app.UiModeManager
+import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
+import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
+import android.app.UiModeManager.ContrastUtils.toContrastLevel
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import android.text.TextUtils
+import androidx.preference.Preference
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+
+/**
+ * Controller that opens the contrast dialog and updates the text describing the current contrast
+ */
+class ContrastPreferenceController(
+ private val context: Context,
+ private val uiModeManager: UiModeManager) : BasePreferenceController(context, KEY) {
+
+ companion object {
+ @VisibleForTesting
+ const val KEY = "contrast_preference"
+ }
+
+ override fun getAvailabilityStatus(): Int {
+ return AVAILABLE
+ }
+
+ override fun handlePreferenceTreeClick(preference: Preference): Boolean {
+ if (TextUtils.equals(preference.key, preferenceKey)) {
+ val intent = Intent(Intent.ACTION_SHOW_CONTRAST_DIALOG)
+ context.startActivityAsUser(intent, UserHandle(UserHandle.USER_CURRENT))
+ return true
+ }
+ return false
+ }
+
+ override fun getSummary(): CharSequence = getSummary(toContrastLevel(uiModeManager.contrast))
+
+ @VisibleForTesting
+ fun getSummary(contrast: Int): String {
+ return when (contrast) {
+ CONTRAST_LEVEL_HIGH -> context.getString(R.string.contrast_high)
+ CONTRAST_LEVEL_MEDIUM -> context.getString(R.string.contrast_medium)
+ else -> context.getString(R.string.contrast_standard)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/users/TimeoutToDockUserPreferenceController.java b/src/com/android/settings/users/TimeoutToDockUserPreferenceController.java
index 57e8d40..322a4ab 100644
--- a/src/com/android/settings/users/TimeoutToDockUserPreferenceController.java
+++ b/src/com/android/settings/users/TimeoutToDockUserPreferenceController.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import androidx.preference.PreferenceScreen;
@@ -34,6 +35,8 @@
* automatically switch to the designated Dock User when the device is docked.
*/
public class TimeoutToDockUserPreferenceController extends BasePreferenceController {
+ private final UserManager mUserManager;
+
private final String[] mEntries;
private final String[] mValues;
@@ -41,6 +44,8 @@
String preferenceKey) {
super(context, preferenceKey);
+ mUserManager = context.getSystemService(UserManager.class);
+
mEntries = mContext.getResources().getStringArray(
com.android.settings.R.array.switch_to_dock_user_when_docked_timeout_entries);
mValues = mContext.getResources().getStringArray(
@@ -62,9 +67,10 @@
return UNSUPPORTED_ON_DEVICE;
}
- // Multi-user feature disabled by user.
+ // Multi-user feature disabled by user, or user switching blocked on the user.
if (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.USER_SWITCHER_ENABLED, 0) != 1) {
+ Settings.Global.USER_SWITCHER_ENABLED, 0) != 1
+ || mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)) {
return CONDITIONALLY_UNAVAILABLE;
}
diff --git a/src/com/android/settings/uwb/UwbPreferenceController.java b/src/com/android/settings/uwb/UwbPreferenceController.java
index fb0836d..7f19765 100644
--- a/src/com/android/settings/uwb/UwbPreferenceController.java
+++ b/src/com/android/settings/uwb/UwbPreferenceController.java
@@ -16,6 +16,11 @@
package com.android.settings.uwb;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_REGULATION;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_DISABLED;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE;
+
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
@@ -25,7 +30,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.provider.Settings;
+import android.os.HandlerExecutor;
import android.uwb.UwbManager;
import android.uwb.UwbManager.AdapterStateCallback;
@@ -39,52 +44,68 @@
import com.android.settings.core.TogglePreferenceController;
import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
/** Controller for "UWB" toggle. */
public class UwbPreferenceController extends TogglePreferenceController implements
- AdapterStateCallback, LifecycleObserver {
- @VisibleForTesting
- static final String KEY_UWB_SETTINGS = "uwb_settings";
- @VisibleForTesting
- UwbManager mUwbManager;
- @VisibleForTesting
- boolean mAirplaneModeOn;
- @VisibleForTesting
+ LifecycleObserver {
+ private final UwbManager mUwbManager;
+ private final UwbUtils mUwbUtils;
+ private boolean mAirplaneModeOn;
+ private /* @AdapterStateCallback.State */ int mState;
+ private /* @AdapterStateCallback.StateChangedReason */ int mStateReason;
private final BroadcastReceiver mAirplaneModeChangedReceiver;
+ private final AdapterStateCallback mAdapterStateCallback;
private final Executor mExecutor;
private final Handler mHandler;
private Preference mPreference;
- public UwbPreferenceController(Context context, String key) {
+ @VisibleForTesting
+ public UwbPreferenceController(Context context, String key, UwbUtils uwbUtils) {
super(context, key);
- mExecutor = Executors.newSingleThreadExecutor();
mHandler = new Handler(context.getMainLooper());
+ mExecutor = new HandlerExecutor(mHandler);
+ mUwbUtils = uwbUtils;
if (isUwbSupportedOnDevice()) {
mUwbManager = context.getSystemService(UwbManager.class);
- }
- mAirplaneModeOn = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
- mAirplaneModeChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mAirplaneModeOn = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ mAirplaneModeChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mAirplaneModeOn = mUwbUtils.isAirplaneModeOn(mContext);
+ updateState(mPreference);
+ }
+ };
+ mAdapterStateCallback = (state, reason) -> {
+ mState = state;
+ mStateReason = reason;
updateState(mPreference);
- }
- };
+ };
+ } else {
+ mUwbManager = null;
+ mAirplaneModeChangedReceiver = null;
+ mAdapterStateCallback = null;
+ }
+ }
+
+ public UwbPreferenceController(Context context, String key) {
+ this(context, key, new UwbUtils());
}
public boolean isUwbSupportedOnDevice() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB);
}
+ private boolean isUwbDisabledDueToRegulatory() {
+ return mState == STATE_DISABLED && mStateReason == STATE_CHANGED_REASON_SYSTEM_REGULATION;
+ }
+
@Override
public int getAvailabilityStatus() {
if (!isUwbSupportedOnDevice()) {
return UNSUPPORTED_ON_DEVICE;
} else if (mAirplaneModeOn) {
return DISABLED_DEPENDENT_SETTING;
+ } else if (isUwbDisabledDueToRegulatory()) {
+ return CONDITIONALLY_UNAVAILABLE;
} else {
return AVAILABLE;
}
@@ -101,14 +122,11 @@
if (!isUwbSupportedOnDevice()) {
return false;
}
- int state = mUwbManager.getAdapterState();
- return state == STATE_ENABLED_ACTIVE || state == STATE_ENABLED_INACTIVE;
+ return mState == STATE_ENABLED_ACTIVE || mState == STATE_ENABLED_INACTIVE;
}
@Override
public boolean setChecked(boolean isChecked) {
- mAirplaneModeOn = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
if (isUwbSupportedOnDevice()) {
if (mAirplaneModeOn) {
mUwbManager.setUwbEnabled(false);
@@ -119,32 +137,25 @@
return true;
}
- @Override
- public void onStateChanged(int state, int reason) {
- Runnable runnable = () -> updateState(mPreference);
- mHandler.post(runnable);
- }
-
/** Called when activity starts being displayed to user. */
@OnLifecycleEvent(ON_START)
public void onStart() {
if (isUwbSupportedOnDevice()) {
- mUwbManager.registerAdapterStateCallback(mExecutor, this);
- }
- if (mAirplaneModeChangedReceiver != null) {
+ mState = mUwbManager.getAdapterState();
+ mStateReason = AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN;
+ mAirplaneModeOn = mUwbUtils.isAirplaneModeOn(mContext);
+ mUwbManager.registerAdapterStateCallback(mExecutor, mAdapterStateCallback);
mContext.registerReceiver(mAirplaneModeChangedReceiver,
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED), null, mHandler);
+ refreshSummary(mPreference);
}
- refreshSummary(mPreference);
}
/** Called when activity stops being displayed to user. */
@OnLifecycleEvent(ON_STOP)
public void onStop() {
if (isUwbSupportedOnDevice()) {
- mUwbManager.unregisterAdapterStateCallback(this);
- }
- if (mAirplaneModeChangedReceiver != null) {
+ mUwbManager.unregisterAdapterStateCallback(mAdapterStateCallback);
mContext.unregisterReceiver(mAirplaneModeChangedReceiver);
}
}
@@ -153,13 +164,16 @@
public void updateState(Preference preference) {
super.updateState(preference);
preference.setEnabled(!mAirplaneModeOn);
- refreshSummary(preference);
+ refreshSummary(mPreference);
}
@Override
public CharSequence getSummary() {
if (mAirplaneModeOn) {
return mContext.getResources().getString(R.string.uwb_settings_summary_airplane_mode);
+ } else if (isUwbDisabledDueToRegulatory()) {
+ return mContext.getResources().getString(
+ R.string.uwb_settings_summary_no_uwb_regulatory);
} else {
return mContext.getResources().getString(R.string.uwb_settings_summary);
}
diff --git a/src/com/android/settings/uwb/UwbUtils.java b/src/com/android/settings/uwb/UwbUtils.java
new file mode 100644
index 0000000..940e1af
--- /dev/null
+++ b/src/com/android/settings/uwb/UwbUtils.java
@@ -0,0 +1,34 @@
+/*
+ * 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.uwb;
+
+import android.content.Context;
+import android.provider.Settings;
+
+/**
+ * Utils to help mock static methods in {@link UwbPreferenceController}.
+ */
+public class UwbUtils {
+ /**
+ * Returns whether airplane mode is on or off.
+ */
+ public boolean isAirplaneModeOn(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
+}
+
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
index 9cc8439..94febff 100644
--- a/tests/robotests/Android.bp
+++ b/tests/robotests/Android.bp
@@ -42,6 +42,7 @@
"androidx.test.core",
"androidx.test.runner",
"androidx.test.ext.junit",
+ "frameworks-base-testutils",
"guava",
"jsr305",
"settings-contextual-card-protos-lite",
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
index fc3e6c0..f4fa397 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java
@@ -394,7 +394,7 @@
assertThat(buttonsPref.isChecked()).isEqualTo(false);
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
- Secure.STYLUS_BUTTONS_ENABLED, -1)).isEqualTo(0);
+ Secure.STYLUS_BUTTONS_ENABLED, -1)).isEqualTo(1);
}
private void showScreen(StylusDevicesController controller) {
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
index 37f05bc..16fec09 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
@@ -55,6 +55,7 @@
@RunWith(RobolectricTestRunner.class)
public final class BatteryUsageBreakdownControllerTest {
private static final String PREF_KEY = "pref_key";
+ private static final String PREF_KEY2 = "pref_key2";
private static final String PREF_SUMMARY = "fake preference summary";
@Mock
@@ -175,7 +176,24 @@
}
@Test
- public void removeAndCacheAllPreferences_buildCacheAndRemoveAllPreference() {
+ public void removeAndCacheAllUnusedPreferences_removePerf_buildCacheAndRemoveAllPreference() {
+ doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
+ doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0);
+ doReturn(PREF_KEY2).when(mBatteryHistEntry).getKey();
+ doReturn(PREF_KEY).when(mPowerGaugePreference).getKey();
+ doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).findPreference(PREF_KEY);
+ // Ensures the testing data is correct.
+ assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty();
+
+ mBatteryUsageBreakdownController.removeAndCacheAllUnusedPreferences();
+
+ assertThat(mBatteryUsageBreakdownController.mPreferenceCache.get(PREF_KEY))
+ .isEqualTo(mPowerGaugePreference);
+ verify(mAppListPreferenceGroup).removePreference(mPowerGaugePreference);
+ }
+
+ @Test
+ public void removeAndCacheAllUnusedPreferences_keepPerf_KeepAllPreference() {
doReturn(1).when(mAppListPreferenceGroup).getPreferenceCount();
doReturn(mPowerGaugePreference).when(mAppListPreferenceGroup).getPreference(0);
doReturn(PREF_KEY).when(mBatteryHistEntry).getKey();
@@ -184,11 +202,10 @@
// Ensures the testing data is correct.
assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty();
- mBatteryUsageBreakdownController.removeAndCacheAllPreferences();
+ mBatteryUsageBreakdownController.removeAndCacheAllUnusedPreferences();
- assertThat(mBatteryUsageBreakdownController.mPreferenceCache.get(PREF_KEY))
- .isEqualTo(mPowerGaugePreference);
- verify(mAppListPreferenceGroup).removeAll();
+ verify(mAppListPreferenceGroup, never()).removePreference(any());
+ assertThat(mBatteryUsageBreakdownController.mPreferenceCache).isEmpty();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/nfc/NfcEnablerTest.java b/tests/robotests/src/com/android/settings/nfc/NfcEnablerTest.java
index cc45190..5299bbd 100644
--- a/tests/robotests/src/com/android/settings/nfc/NfcEnablerTest.java
+++ b/tests/robotests/src/com/android/settings/nfc/NfcEnablerTest.java
@@ -16,15 +16,12 @@
package com.android.settings.nfc;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.content.ContentResolver;
import android.content.Context;
import android.nfc.NfcAdapter;
-import android.provider.Settings;
import com.android.settingslib.widget.MainSwitchPreference;
@@ -53,54 +50,13 @@
}
@Test
- public void isToggleable_AirplaneModeOff_shouldReturnTrue() {
- final ContentResolver contentResolver = mContext.getContentResolver();
- Settings.Global.putInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0);
- Settings.Global.putString(contentResolver,
- Settings.Global.AIRPLANE_MODE_RADIOS, Settings.Global.RADIO_NFC);
- Settings.Global.putString(contentResolver,
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS, Settings.Global.RADIO_NFC);
-
- assertThat(mNfcEnabler.isToggleable()).isTrue();
- }
-
- @Test
- public void isToggleable_AirplaneModeOnNfcNotInAirplaneModeRadio_shouldReturnTrue() {
- final ContentResolver contentResolver = mContext.getContentResolver();
- Settings.Global.putInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1);
- Settings.Global.putString(contentResolver, Settings.Global.AIRPLANE_MODE_RADIOS, "");
-
- assertThat(mNfcEnabler.isToggleable()).isTrue();
- }
-
- @Test
- public void isToggleable_AirplaneModeOnNfcToggleable_shouldReturnTrue() {
- final ContentResolver contentResolver = mContext.getContentResolver();
- Settings.Global.putInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1);
- Settings.Global.putString(contentResolver,
- Settings.Global.AIRPLANE_MODE_RADIOS, Settings.Global.RADIO_NFC);
- Settings.Global.putString(contentResolver,
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS, Settings.Global.RADIO_NFC);
-
- assertThat(mNfcEnabler.isToggleable()).isTrue();
- }
-
- @Test
- public void isToggleable_AirplaneModeOnNfcNotToggleable_shouldReturnFalse() {
- final ContentResolver contentResolver = mContext.getContentResolver();
- Settings.Global.putInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 1);
- Settings.Global.putString(contentResolver,
- Settings.Global.AIRPLANE_MODE_RADIOS, Settings.Global.RADIO_NFC);
- Settings.Global.putString(contentResolver,
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS, "");
-
- assertThat(mNfcEnabler.isToggleable()).isFalse();
- }
-
- @Test
public void handleNfcStateChanged_stateOff_shouldCheckIfPreferenceEnableState() {
mNfcEnabler.handleNfcStateChanged(NfcAdapter.STATE_OFF);
+ verify(mNfcPreference).updateStatus(false);
+ verify(mNfcPreference).setEnabled(true);
- verify(mNfcEnabler).isToggleable();
+ mNfcEnabler.handleNfcStateChanged(NfcAdapter.STATE_ON);
+ verify(mNfcPreference).updateStatus(true);
+ verify(mNfcPreference, times(2)).setEnabled(true);
}
}
diff --git a/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java
index 3ca553b..4db51aa 100644
--- a/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/nfc/NfcPreferenceControllerTest.java
@@ -28,7 +28,6 @@
import android.nfc.NfcAdapter;
import android.nfc.NfcManager;
import android.os.UserManager;
-import android.provider.Settings;
import androidx.preference.PreferenceScreen;
@@ -189,44 +188,6 @@
}
@Test
- public void isToggleableInAirplaneMode_containNfc_shouldReturnTrue() {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
- Settings.Global.RADIO_NFC);
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 1);
-
- assertThat(NfcPreferenceController.isToggleableInAirplaneMode(mContext)).isTrue();
- }
-
- @Test
- public void isToggleableInAirplaneMode_withoutNfc_shouldReturnFalse() {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
- "null");
- Settings.Global.putInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 1);
-
- assertThat(NfcPreferenceController.isToggleableInAirplaneMode(mContext)).isFalse();
- }
-
- @Test
- public void shouldTurnOffNFCInAirplaneMode_airplaneModeRadiosContainsNfc_shouldReturnTrue() {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_RADIOS, Settings.Global.RADIO_NFC);
-
- assertThat(NfcPreferenceController.shouldTurnOffNFCInAirplaneMode(mContext)).isTrue();
- }
-
- @Test
- public void shouldTurnOffNFCInAirplaneMode_airplaneModeRadiosWithoutNfc_shouldReturnFalse() {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_RADIOS, "");
-
- assertThat(NfcPreferenceController.shouldTurnOffNFCInAirplaneMode(mContext)).isFalse();
- }
-
- @Test
public void ncfSliceWorker_nfcBroadcast_noExtra_sliceDoesntUpdate() {
final NfcSliceWorker worker = spy(
new NfcSliceWorker(mContext, mNfcController.getSliceUri()));
diff --git a/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java
index 77a013d..d26c33b 100644
--- a/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ConfirmLockPasswordTest.java
@@ -197,7 +197,7 @@
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
- assertThat(fragment.mDeviceCredentialGuess).isNotNull();
+ assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test
@@ -223,7 +223,7 @@
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
- assertThat(fragment.mDeviceCredentialGuess).isNull();
+ assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java b/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java
index 4374e38..2ed7988 100644
--- a/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java
+++ b/tests/robotests/src/com/android/settings/password/ConfirmLockPatternTest.java
@@ -177,7 +177,7 @@
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
- assertThat(fragment.mDeviceCredentialGuess).isNotNull();
+ assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test
@@ -203,7 +203,7 @@
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
- assertThat(fragment.mDeviceCredentialGuess).isNull();
+ assertThat(fragment.mRemoteLockscreenValidationFragment.getLockscreenCredential()).isNull();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/security/MemtagHelperTest.java b/tests/robotests/src/com/android/settings/security/MemtagHelperTest.java
index 4ef7de4..e3be8e2 100644
--- a/tests/robotests/src/com/android/settings/security/MemtagHelperTest.java
+++ b/tests/robotests/src/com/android/settings/security/MemtagHelperTest.java
@@ -33,8 +33,6 @@
public class MemtagHelperTest {
private final String mMemtagProperty = "arm64.memtag.bootctl";
private final String mMemtagSupportedProperty = "ro.arm64.memtag.bootctl_settings_toggle";
- private final String mDeviceConfigOverride =
- "persist.device_config.memory_safety_native_boot.bootloader_override";
@Test
public void isChecked_empty_isFalse() {
@@ -80,7 +78,7 @@
@Test
public void getAvailabilityStatus_isForcedOff_isDISABLED_DEPENDENT_SETTING() {
- ShadowSystemProperties.override(mDeviceConfigOverride, "force_off");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "force_off");
ShadowSystemProperties.override(mMemtagSupportedProperty, "true");
assertThat(MemtagHelper.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.DISABLED_DEPENDENT_SETTING);
@@ -88,7 +86,7 @@
@Test
public void getAvailabilityStatus_isForcedOn_isDISABLED_DEPENDENT_SETTING() {
- ShadowSystemProperties.override(mDeviceConfigOverride, "force_on");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "force_on");
ShadowSystemProperties.override(mMemtagSupportedProperty, "true");
assertThat(MemtagHelper.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.DISABLED_DEPENDENT_SETTING);
@@ -96,7 +94,7 @@
@Test
public void getAvailabilityStatus_isUnsupported_isUNSUPPORTED_ON_DEVICE() {
- ShadowSystemProperties.override(mDeviceConfigOverride, "");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "");
ShadowSystemProperties.override(mMemtagSupportedProperty, "false");
assertThat(MemtagHelper.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
@@ -127,7 +125,7 @@
@Config(shadows = {ZygoteShadow.class})
public void getSummary_memtagAndZygoteSupportsMemoryTagging_memtag_on() {
ZygoteShadow.setSupportsMemoryTagging(true);
- ShadowSystemProperties.override(mDeviceConfigOverride, "");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "");
ShadowSystemProperties.override(mMemtagProperty, "memtag");
assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_on);
}
@@ -136,7 +134,7 @@
@Config(shadows = {ZygoteShadow.class})
public void getSummary_noMemtagAndZygoteSupportsMemoryTagging_memtag_off_pending() {
ZygoteShadow.setSupportsMemoryTagging(true);
- ShadowSystemProperties.override(mDeviceConfigOverride, "");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "");
ShadowSystemProperties.override(mMemtagProperty, "");
assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_off_pending);
}
@@ -145,7 +143,7 @@
@Config(shadows = {ZygoteShadow.class})
public void getSummary_noMemtagAndNoZygoteSupportsMemoryTagging_memtag_off() {
ZygoteShadow.setSupportsMemoryTagging(false);
- ShadowSystemProperties.override(mDeviceConfigOverride, "");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "");
ShadowSystemProperties.override(mMemtagProperty, "");
assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_off);
}
@@ -154,7 +152,7 @@
@Config(shadows = {ZygoteShadow.class})
public void getSummary_memtagAndNoZygoteSupportsMemoryTagging_memtag_on_pending() {
ZygoteShadow.setSupportsMemoryTagging(false);
- ShadowSystemProperties.override(mDeviceConfigOverride, "");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "");
ShadowSystemProperties.override(mMemtagProperty, "memtag");
assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_on_pending);
}
@@ -163,7 +161,7 @@
@Config(shadows = {ZygoteShadow.class})
public void getSummary_forceOffOverride_memtag_force_off() {
ZygoteShadow.setSupportsMemoryTagging(false);
- ShadowSystemProperties.override(mDeviceConfigOverride, "force_off");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "force_off");
ShadowSystemProperties.override(mMemtagProperty, "memtag");
assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_force_off);
}
@@ -172,20 +170,20 @@
@Config(shadows = {ZygoteShadow.class})
public void getSummary_forceOffOverride_memtag_force_on() {
ZygoteShadow.setSupportsMemoryTagging(false);
- ShadowSystemProperties.override(mDeviceConfigOverride, "force_on");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "force_on");
ShadowSystemProperties.override(mMemtagProperty, "memtag");
assertThat(MemtagHelper.getSummary()).isEqualTo(R.string.memtag_force_on);
}
@Test
public void isForcedOn_forceOnOverride_isTrue() {
- ShadowSystemProperties.override(mDeviceConfigOverride, "force_on");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "force_on");
assertThat(MemtagHelper.isForcedOn()).isTrue();
}
@Test
public void isForcedOff_forceOffOverride_isTrue() {
- ShadowSystemProperties.override(mDeviceConfigOverride, "force_off");
+ ShadowSystemProperties.override(MemtagHelper.DEVICE_CONFIG_PROP, "force_off");
assertThat(MemtagHelper.isForcedOff()).isTrue();
}
}
diff --git a/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
new file mode 100644
index 0000000..dbd3372
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.theme;
+
+import static android.app.UiModeManager.ContrastUtils;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.stream.Stream;
+
+@RunWith(RobolectricTestRunner.class)
+public class ContrastPreferenceControllerTest {
+
+ @Rule
+ public MockitoRule mocks = MockitoJUnit.rule();
+
+ private ContrastPreferenceController mController;
+
+ @Mock
+ private UiModeManager mMockUiModeManager;
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+ mController = new ContrastPreferenceController(mContext, mMockUiModeManager);
+ }
+
+ @Test
+ public void controllerIsAvailable() {
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void testHandlePreferenceTreeClick() {
+ Preference preference = new Preference(mContext);
+ preference.setKey(ContrastPreferenceController.KEY);
+ assertThat(mController.handlePreferenceTreeClick(preference)).isTrue();
+
+ Preference otherPreference = new Preference(mContext);
+ otherPreference.setKey("wrong key");
+ assertThat(mController.handlePreferenceTreeClick(otherPreference)).isFalse();
+ }
+
+ @Test
+ public void controllerSummary() {
+ float initialContrast = mContext.getSystemService(UiModeManager.class).getContrast();
+ try {
+ allContrastValues().forEach(contrastLevel -> {
+ float contrast = ContrastUtils.fromContrastLevel(contrastLevel);
+ clearInvocations(mMockUiModeManager);
+ when(mMockUiModeManager.getContrast()).thenReturn(contrast);
+ String summary = mController.getSummary().toString();
+ verify(mMockUiModeManager).getContrast();
+ assertThat(summary).isEqualTo(mController.getSummary(contrastLevel));
+ });
+ } finally {
+ putContrastInSettings(initialContrast);
+ }
+ }
+
+ private static Stream<Integer> allContrastValues() {
+ return Stream.of(CONTRAST_LEVEL_STANDARD, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HIGH);
+ }
+
+ private void putContrastInSettings(float contrast) {
+ Settings.Secure.putFloat(mContext.getContentResolver(), CONTRAST_LEVEL, contrast);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/users/TimeoutToDockUserPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/users/TimeoutToDockUserPreferenceControllerTest.java
index 2e7e2d7..3d8e893 100644
--- a/tests/robotests/src/com/android/settings/users/TimeoutToDockUserPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/users/TimeoutToDockUserPreferenceControllerTest.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import androidx.test.core.app.ApplicationProvider;
@@ -41,6 +42,8 @@
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.annotation.Config;
@@ -51,6 +54,9 @@
private Resources mResources;
private TimeoutToDockUserPreferenceController mController;
+ @Mock
+ private UserManager mUserManager;
+
private static final String FAKE_PREFERENCE_KEY = "timeout_to_dock_user_preference";
private String[] mEntries;
@@ -58,9 +64,12 @@
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
mContext = spy(ApplicationProvider.getApplicationContext());
mResources = spy(mContext.getResources());
doReturn(mResources).when(mContext).getResources();
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
mEntries = mResources.getStringArray(
R.array.switch_to_dock_user_when_docked_timeout_entries);
@@ -78,6 +87,9 @@
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.USER_SWITCHER_ENABLED,
1);
+ // User switching not blocked.
+ when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(false);
+
// Set to user 1;
ShadowUserHandle.setUid(1);
}
@@ -107,6 +119,14 @@
}
@Test
+ public void getAvailabilityStatus_userSwitchingBlocked_returnConditionallyUnavailable() {
+ when(mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)).thenReturn(true);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
public void getAvailabilityStatus_isCurrentlyMainUser_returnDisabledForUser() {
when(Utils.canCurrentUserDream(mContext)).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
index 94d797a..23aca51 100644
--- a/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
@@ -16,110 +16,157 @@
package com.android.settings.uwb;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_REGULATION;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_DISABLED;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.test.TestLooper;
import android.uwb.UwbManager;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
/** Unit tests for UWB preference toggle. */
@RunWith(RobolectricTestRunner.class)
public class UwbPreferenceControllerTest {
+ private static final String TEST_SUMMARY = "uwb";
+ private static final String TEST_AIRPLANE_SUMMARY = "apm_uwb";
+ private static final String TEST_NO_UWB_REGULATORY_SUMMARY = "regulatory_uwb";
@Rule
public MockitoRule rule = MockitoJUnit.rule();
+ @Mock
private Context mContext;
+ @Mock
private PackageManager mPackageManager;
private UwbPreferenceController mController;
-
+ private ArgumentCaptor<UwbManager.AdapterStateCallback> mAdapterStateCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(UwbManager.AdapterStateCallback.class);
+ private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ private TestLooper mTestLooper;
@Mock
private UwbManager mUwbManager;
+ @Mock
+ private UwbUtils mUwbUtils;
+ @Mock
+ private Preference mPreference;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private Resources mResources;
@Before
- public void setUp() {
- mContext = spy(RuntimeEnvironment.application);
- mPackageManager = spy(mContext.getPackageManager());
- mController = new UwbPreferenceController(mContext, "uwb_settings");
- mController.mUwbManager = mUwbManager;
- }
-
- @Test
- public void getAvailabilityStatus_uwbDisabled_shouldReturnDisabled() {
+ public void setUp() throws Exception {
+ mTestLooper = new TestLooper();
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(true).when(mPackageManager)
.hasSystemFeature(PackageManager.FEATURE_UWB);
- mController.mAirplaneModeOn = true;
+ when(mResources.getString(R.string.uwb_settings_summary))
+ .thenReturn(TEST_SUMMARY);
+ when(mResources.getString(R.string.uwb_settings_summary_airplane_mode))
+ .thenReturn(TEST_AIRPLANE_SUMMARY);
+ when(mResources.getString(R.string.uwb_settings_summary_no_uwb_regulatory))
+ .thenReturn(TEST_NO_UWB_REGULATORY_SUMMARY);
+ when(mContext.getMainLooper()).thenReturn(mTestLooper.getLooper());
+ when(mContext.getSystemService(UwbManager.class)).thenReturn(mUwbManager);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mUwbUtils.isAirplaneModeOn(any())).thenReturn(false);
+ doReturn(STATE_ENABLED_ACTIVE).when(mUwbManager).getAdapterState();
+ mController = new UwbPreferenceController(mContext, "uwb_settings", mUwbUtils);
+ when(mPreferenceScreen.findPreference(anyString())).thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+ }
+ private void startControllerAndCaptureCallbacks() {
+ mController.onStart();
+ verify(mContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+ verify(mUwbManager).registerAdapterStateCallback(
+ any(), mAdapterStateCallbackArgumentCaptor.capture());
+ }
+
+ @Test
+ public void getAvailabilityStatus_uwbDisabled_shouldReturnDisabled() throws Exception {
+ when(mUwbUtils.isAirplaneModeOn(any())).thenReturn(true);
+ startControllerAndCaptureCallbacks();
assertThat(mController.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.DISABLED_DEPENDENT_SETTING);
}
@Test
- public void getAvailabilityStatus_uwbShown_shouldReturnAvailable() {
- doReturn(mPackageManager).when(mContext).getPackageManager();
- doReturn(true).when(mPackageManager)
- .hasSystemFeature(PackageManager.FEATURE_UWB);
- mController.mAirplaneModeOn = false;
-
+ public void getAvailabilityStatus_uwbShown_shouldReturnAvailable() throws Exception {
+ when(mUwbUtils.isAirplaneModeOn(any())).thenReturn(false);
+ startControllerAndCaptureCallbacks();
assertThat(mController.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.AVAILABLE);
}
@Test
public void getAvailabilityStatus_uwbNotShown_shouldReturnUnsupported() {
- doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(false).when(mPackageManager)
.hasSystemFeature(PackageManager.FEATURE_UWB);
+ mController.onStart();
+ verify(mContext, never()).registerReceiver(any(), any(), any(), any());
+ verify(mUwbManager, never()).registerAdapterStateCallback(any(), any());
assertThat(mController.getAvailabilityStatus())
.isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
}
@Test
public void isChecked_uwbEnabled_shouldReturnTrue() {
- doReturn(mPackageManager).when(mContext).getPackageManager();
- doReturn(true).when(mPackageManager)
- .hasSystemFeature(PackageManager.FEATURE_UWB);
- doReturn(mController.STATE_ENABLED_ACTIVE).when(mUwbManager).getAdapterState();
+ doReturn(STATE_ENABLED_ACTIVE).when(mUwbManager).getAdapterState();
+ startControllerAndCaptureCallbacks();
assertThat(mController.isChecked()).isTrue();
}
@Test
public void isChecked_uwbDisabled_shouldReturnFalse() {
- doReturn(mPackageManager).when(mContext).getPackageManager();
- doReturn(true).when(mPackageManager)
- .hasSystemFeature(PackageManager.FEATURE_UWB);
- doReturn(mController.STATE_DISABLED).when(mUwbManager).getAdapterState();
+ doReturn(STATE_DISABLED).when(mUwbManager).getAdapterState();
+ startControllerAndCaptureCallbacks();
assertThat(mController.isChecked()).isFalse();
}
@Test
public void setChecked_uwbDisabled_shouldEnableUwb() {
clearInvocations(mUwbManager);
- doReturn(mPackageManager).when(mContext).getPackageManager();
- doReturn(true).when(mPackageManager)
- .hasSystemFeature(PackageManager.FEATURE_UWB);
+ startControllerAndCaptureCallbacks();
mController.setChecked(true);
verify(mUwbManager).setUwbEnabled(true);
@@ -129,14 +176,65 @@
@Test
public void setChecked_uwbEnabled_shouldDisableUwb() {
clearInvocations(mUwbManager);
- doReturn(mPackageManager).when(mContext).getPackageManager();
- doReturn(true).when(mPackageManager)
- .hasSystemFeature(PackageManager.FEATURE_UWB);
+ startControllerAndCaptureCallbacks();
mController.setChecked(false);
verify(mUwbManager).setUwbEnabled(false);
verify(mUwbManager, never()).setUwbEnabled(true);
}
+
+ @Test
+ public void updateStateAndSummary_uwbDisabledAndEnabled() {
+ startControllerAndCaptureCallbacks();
+ clearInvocations(mUwbManager, mPreference);
+
+ mAdapterStateCallbackArgumentCaptor.getValue().onStateChanged(
+ STATE_DISABLED, STATE_CHANGED_REASON_SYSTEM_POLICY);
+
+ verify(mPreference).setEnabled(true);
+ assertThat(mController.isChecked()).isFalse();
+ verify(mPreference, times(2)).setSummary(TEST_SUMMARY);
+
+ mAdapterStateCallbackArgumentCaptor.getValue().onStateChanged(
+ STATE_ENABLED_INACTIVE, STATE_CHANGED_REASON_SYSTEM_POLICY);
+
+ verify(mPreference, times(2)).setEnabled(true);
+ assertThat(mController.isChecked()).isTrue();
+ verify(mPreference, times(4)).setSummary(TEST_SUMMARY);
+ }
+
+ @Test
+ public void updateStateAndSummary_apmEnabledAndDisabled() {
+ startControllerAndCaptureCallbacks();
+ clearInvocations(mUwbManager, mPreference);
+
+ when(mUwbUtils.isAirplaneModeOn(any())).thenReturn(true);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(
+ mock(Context.class), mock(Intent.class));
+
+ verify(mPreference).setEnabled(false);
+ verify(mPreference, times(2)).setSummary(TEST_AIRPLANE_SUMMARY);
+
+ when(mUwbUtils.isAirplaneModeOn(any())).thenReturn(false);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(
+ mock(Context.class), mock(Intent.class));
+
+ verify(mPreference).setEnabled(true);
+ verify(mPreference, times(2)).setSummary(TEST_SUMMARY);
+ }
+
+ @Test
+ public void updateStateAndSummary_uwbDisabledDueToRegulatory() {
+ startControllerAndCaptureCallbacks();
+ clearInvocations(mUwbManager, mPreference);
+
+ mAdapterStateCallbackArgumentCaptor.getValue().onStateChanged(
+ STATE_DISABLED, STATE_CHANGED_REASON_SYSTEM_REGULATION);
+
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
+ verify(mPreference, times(2)).setSummary(TEST_NO_UWB_REGULATORY_SUMMARY);
+ }
}
diff --git a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java
index 323618a..6190c5e 100644
--- a/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java
+++ b/tests/unit/src/com/android/settings/biometrics2/ui/viewmodel/FingerprintEnrollProgressViewModelTest.java
@@ -21,6 +21,8 @@
import static android.hardware.fingerprint.FingerprintManager.EnrollReason;
import static android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
+import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -29,18 +31,24 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Application;
import android.content.res.Resources;
import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.settings.R;
import com.android.settings.biometrics.fingerprint.FingerprintUpdater;
+import com.android.settings.biometrics.fingerprint.MessageDisplayController;
import com.android.settings.biometrics2.ui.model.EnrollmentProgress;
import com.android.settings.biometrics2.ui.model.EnrollmentStatusMessage;
import com.android.settings.testutils.InstantTaskExecutorRule;
@@ -68,12 +76,18 @@
private FingerprintEnrollProgressViewModel mViewModel;
private final TestWrapper<CancellationSignal> mCancellationSignalWrapper = new TestWrapper<>();
private final TestWrapper<EnrollmentCallback> mCallbackWrapper = new TestWrapper<>();
+ private int mEnrollmentMessageDisplayControllerFlagResId;
@Before
public void setUp() {
+ mEnrollmentMessageDisplayControllerFlagResId = ApplicationProvider.getApplicationContext()
+ .getResources().getIdentifier("enrollment_message_display_controller_flag", "bool",
+ SETTINGS_PACKAGE_NAME);
+
when(mApplication.getResources()).thenReturn(mResources);
- when(mResources.getBoolean(R.bool.enrollment_message_display_controller_flag))
- .thenReturn(false);
+
+ // Not use MessageDisplayController by default
+ when(mResources.getBoolean(mEnrollmentMessageDisplayControllerFlagResId)).thenReturn(false);
mViewModel = new FingerprintEnrollProgressViewModel(mApplication, mFingerprintUpdater,
TEST_USER_ID);
@@ -88,7 +102,7 @@
}
@Test
- public void testStartEnrollment() {
+ public void testStartFindSensor() {
@EnrollReason final int enrollReason = ENROLL_FIND_SENSOR;
final byte[] token = new byte[] { 1, 2, 3 };
mViewModel.setToken(token);
@@ -99,6 +113,54 @@
assertThat(ret).isTrue();
verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
eq(TEST_USER_ID), any(EnrollmentCallback.class), eq(enrollReason));
+ assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isFalse();
+ }
+
+ @Test
+ public void testStartEnrolling() {
+ @EnrollReason final int enrollReason = ENROLL_ENROLL;
+ final byte[] token = new byte[] { 1, 2, 3 };
+ mViewModel.setToken(token);
+
+ // Start enrollment
+ final boolean ret = mViewModel.startEnrollment(enrollReason);
+
+ assertThat(ret).isTrue();
+ verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
+ eq(TEST_USER_ID), any(EnrollmentCallback.class), eq(enrollReason));
+ assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isFalse();
+ }
+
+ @Test
+ public void testStartEnrollingWithMessageDisplayController() {
+ // Enable MessageDisplayController and mock handler for it
+ when(mResources.getBoolean(mEnrollmentMessageDisplayControllerFlagResId)).thenReturn(true);
+ when(mApplication.getMainThreadHandler()).thenReturn(new TestHandler());
+
+ @EnrollReason final int enrollReason = ENROLL_ENROLL;
+ final byte[] token = new byte[] { 1, 2, 3 };
+ mViewModel.setToken(token);
+
+ // Start enrollment
+ final boolean ret = mViewModel.startEnrollment(enrollReason);
+
+ assertThat(ret).isTrue();
+ verify(mFingerprintUpdater, only()).enroll(eq(token), any(CancellationSignal.class),
+ eq(TEST_USER_ID), any(MessageDisplayController.class), eq(enrollReason));
+ assertThat(mCallbackWrapper.mValue).isNotNull();
+
+ assertThat(mCallbackWrapper.mValue instanceof MessageDisplayController).isTrue();
+ final EnrollmentCallback callback1 = mCallbackWrapper.mValue;
+
+ // Cancel and start again
+ mViewModel.cancelEnrollment();
+ mViewModel.startEnrollment(enrollReason);
+
+ // Shall not use the same MessageDisplayController
+ verify(mFingerprintUpdater, times(2)).enroll(eq(token), any(CancellationSignal.class),
+ eq(TEST_USER_ID), any(MessageDisplayController.class), eq(enrollReason));
+ assertThat(mCallbackWrapper.mValue).isNotNull();
+ assertThat(callback1).isNotEqualTo(mCallbackWrapper.mValue);
}
@Test
@@ -163,6 +225,48 @@
}
@Test
+ public void testProgressUpdateWithMessageDisplayController() {
+ // Enable MessageDisplayController and mock handler for it
+ when(mResources.getBoolean(mEnrollmentMessageDisplayControllerFlagResId)).thenReturn(true);
+ when(mApplication.getMainThreadHandler()).thenReturn(new TestHandler());
+
+ mViewModel.setToken(new byte[] { 1, 2, 3 });
+
+ // Start enrollment
+ final boolean ret = mViewModel.startEnrollment(ENROLL_ENROLL);
+ assertThat(ret).isTrue();
+ assertThat(mCallbackWrapper.mValue).isNotNull();
+
+ // Test default value
+ final LiveData<EnrollmentProgress> progressLiveData = mViewModel.getProgressLiveData();
+ EnrollmentProgress progress = progressLiveData.getValue();
+ assertThat(progress).isNotNull();
+ assertThat(progress.getSteps()).isEqualTo(-1);
+ assertThat(progress.getRemaining()).isEqualTo(0);
+
+ // Update first progress
+ mCallbackWrapper.mValue.onEnrollmentProgress(25);
+ progress = progressLiveData.getValue();
+ assertThat(progress).isNotNull();
+ assertThat(progress.getSteps()).isEqualTo(25);
+ assertThat(progress.getRemaining()).isEqualTo(25);
+
+ // Update second progress
+ mCallbackWrapper.mValue.onEnrollmentProgress(20);
+ progress = progressLiveData.getValue();
+ assertThat(progress).isNotNull();
+ assertThat(progress.getSteps()).isEqualTo(25);
+ assertThat(progress.getRemaining()).isEqualTo(20);
+
+ // Update latest progress
+ mCallbackWrapper.mValue.onEnrollmentProgress(0);
+ progress = progressLiveData.getValue();
+ assertThat(progress).isNotNull();
+ assertThat(progress.getSteps()).isEqualTo(25);
+ assertThat(progress.getRemaining()).isEqualTo(0);
+ }
+
+ @Test
public void testGetErrorMessageLiveData() {
// Start enrollment
mViewModel.setToken(new byte[] { 1, 2, 3 });
@@ -262,4 +366,17 @@
private static class TestWrapper<T> {
T mValue;
}
+
+ private static class TestHandler extends Handler {
+
+ TestHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
+ msg.getCallback().run();
+ return true;
+ }
+ }
}