diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7ca32f4a207..1e4d010be90 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -936,6 +936,24 @@
android:value="true" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
android:textColor="?android:attr/textColorPrimary"/>
-
- android:textColor="?android:attr/textColorPrimary"/>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/face_enroll_introduction.xml b/res/layout/face_enroll_introduction.xml
index f551e3637a6..55ac6f908db 100644
--- a/res/layout/face_enroll_introduction.xml
+++ b/res/layout/face_enroll_introduction.xml
@@ -124,9 +124,9 @@
android:layout_width="24dp"
android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"/>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 68ad98ce558..b9a19398af7 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -100,6 +100,14 @@
- 1800000
+
+
+
+ - @string/dark_ui_auto_mode_never
+
+ - @string/dark_ui_auto_mode_auto
+
+
diff --git a/res/values/config.xml b/res/values/config.xml
index 5eb55ae7fb6..e4b481234dc 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -417,4 +417,7 @@
+
+
+
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 001578a15c1..5ee7171e016 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -414,4 +414,11 @@
320dp
+
+
+ 16sp
+
+
+ 7.8dp
+ 13dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d7f786254ce..92db0fbad7f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -827,7 +827,9 @@
Set up face unlock
- Use face unlock to unlock your device, sign in to apps, and confirm payments.\n\nKeep in mind:\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour phone can be unlocked by someone else if it\u2019s held up to your face, even if your eyes are closed.\n\nYour phone can be unlocked by someone who looks a lot like you, say, an identical sibling.
+ Use face unlock to unlock your device, sign in to apps, and confirm payments.\n\nKeep in mind:\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour phone can be unlocked by someone else if it\u2019s held up to your face while your eyes are open.\n\nYour phone can be unlocked by someone who looks a lot like you, say, an identical sibling.
+
+ Use face unlock to unlock your device, sign in to apps, and confirm payments.\n\nKeep in mind:\nLooking at the phone can unlock it when you don\u2019t intend to.\n\nYour phone can be unlocked by someone else if it\u2019s held up to your face, even if your eyes are closed.\n\nYour phone can be unlocked by someone who looks a lot like you, say, an identical sibling.
Delete face data?
@@ -2757,6 +2759,40 @@
Night Light not currently on
+
+
+ Turn on now
+
+ Turn off now
+
+ Turn on until sunrise
+
+ Turn off until sunset
+
+ Dark Mode
+
+ Schedule
+
+ None
+
+ Turns on from sunset to sunrise
+
+ Status
+
+ Off / %1$s
+
+ Will never turn on automatically
+
+ Will turn on automatically at sunset
+
+ On / %1$s
+
+ Will never turn off automatically
+
+ Will turn off automatically at sunrise
+
+ Dark theme uses a black background to help keep battery alive longer on some screens. Dark theme schedules wait to turn on until your screen is off.
+
Screen timeout
@@ -6880,6 +6916,7 @@
+
@@ -9975,6 +10012,12 @@
Got it
+
+ Try Dark theme
+
+
+ Helps extend battery life
+
Quick settings developer tiles
@@ -11237,5 +11280,13 @@
Your work policy info
Settings managed by your IT admin
+
+
+
+
+
+
+
+
diff --git a/res/xml/dark_mode_settings.xml b/res/xml/dark_mode_settings.xml
new file mode 100644
index 00000000000..9247a0c3788
--- /dev/null
+++ b/res/xml/dark_mode_settings.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index c45e6a2ae10..9bc67108459 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -31,6 +31,15 @@
+
+
-
-
-
-
-
-
+
+
+
+
+ android:layout="@layout/face_enroll_button" />
mControllers;
private List mTogglePreferences;
private Preference mRemoveButton;
private Preference mEnrollButton;
+ private FaceFeatureProvider mFaceFeatureProvider;
private boolean mConfirmingPassword;
@@ -117,6 +121,7 @@ public class FaceSettings extends DashboardFragment {
mFaceManager = getPrefContext().getSystemService(FaceManager.class);
mUserId = getActivity().getIntent().getIntExtra(
Intent.EXTRA_USER_ID, UserHandle.myUserId());
+ mFaceFeatureProvider = FeatureFactory.getFactory(getContext()).getFaceFeatureProvider();
if (mUserManager.getUserInfo(mUserId).isManagedProfile()) {
getActivity().setTitle(getActivity().getResources().getString(
@@ -125,18 +130,19 @@ public class FaceSettings extends DashboardFragment {
Preference keyguardPref = findPreference(FaceSettingsKeyguardPreferenceController.KEY);
Preference appPref = findPreference(FaceSettingsAppPreferenceController.KEY);
+ Preference attentionPref = findPreference(FaceSettingsAttentionPreferenceController.KEY);
Preference confirmPref = findPreference(FaceSettingsConfirmPreferenceController.KEY);
Preference bypassPref =
- findPreference(FaceSettingsLockscreenBypassPreferenceController.KEY);
+ findPreference(mLockscreenController.getPreferenceKey());
mTogglePreferences = new ArrayList<>(
- Arrays.asList(keyguardPref, appPref, confirmPref, bypassPref));
+ Arrays.asList(keyguardPref, appPref, attentionPref, confirmPref, bypassPref));
mRemoveButton = findPreference(FaceSettingsRemoveButtonPreferenceController.KEY);
mEnrollButton = findPreference(FaceSettingsEnrollButtonPreferenceController.KEY);
// There is no better way to do this :/
for (AbstractPreferenceController controller : mControllers) {
- if (controller instanceof FaceSettingsPreferenceController) {
+ if (controller instanceof FaceSettingsPreferenceController) {
((FaceSettingsPreferenceController) controller).setUserId(mUserId);
} else if (controller instanceof FaceSettingsEnrollButtonPreferenceController) {
((FaceSettingsEnrollButtonPreferenceController) controller).setUserId(mUserId);
@@ -147,6 +153,7 @@ public class FaceSettings extends DashboardFragment {
// Don't show keyguard controller for work profile settings.
if (mUserManager.isManagedProfile(mUserId)) {
removePreference(FaceSettingsKeyguardPreferenceController.KEY);
+ removePreference(mLockscreenController.getPreferenceKey());
}
if (savedInstanceState != null) {
@@ -154,6 +161,14 @@ public class FaceSettings extends DashboardFragment {
}
}
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ mLockscreenController = use(FaceSettingsLockscreenBypassPreferenceController.class);
+ mLockscreenController.setUserId(mUserId);
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -173,12 +188,17 @@ public class FaceSettings extends DashboardFragment {
finish();
}
} else {
+ mAttentionController.setToken(mToken);
mEnrollController.setToken(mToken);
}
final boolean hasEnrolled = mFaceManager.hasEnrolledTemplates(mUserId);
mEnrollButton.setVisible(!hasEnrolled);
mRemoveButton.setVisible(hasEnrolled);
+
+ if (!mFaceFeatureProvider.isAttentionSupported(getContext())) {
+ removePreference(FaceSettingsAttentionPreferenceController.KEY);
+ }
}
@Override
@@ -193,6 +213,7 @@ public class FaceSettings extends DashboardFragment {
mToken = data.getByteArrayExtra(
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
if (mToken != null) {
+ mAttentionController.setToken(mToken);
mEnrollController.setToken(mToken);
}
}
@@ -236,7 +257,9 @@ public class FaceSettings extends DashboardFragment {
mControllers = buildPreferenceControllers(context, getSettingsLifecycle());
// There's no great way of doing this right now :/
for (AbstractPreferenceController controller : mControllers) {
- if (controller instanceof FaceSettingsRemoveButtonPreferenceController) {
+ if (controller instanceof FaceSettingsAttentionPreferenceController) {
+ mAttentionController = (FaceSettingsAttentionPreferenceController) controller;
+ } else if (controller instanceof FaceSettingsRemoveButtonPreferenceController) {
mRemoveController = (FaceSettingsRemoveButtonPreferenceController) controller;
mRemoveController.setListener(mRemovalListener);
mRemoveController.setActivity((SettingsActivity) getActivity());
@@ -255,6 +278,7 @@ public class FaceSettings extends DashboardFragment {
controllers.add(new FaceSettingsVideoPreferenceController(context));
controllers.add(new FaceSettingsKeyguardPreferenceController(context));
controllers.add(new FaceSettingsAppPreferenceController(context));
+ controllers.add(new FaceSettingsAttentionPreferenceController(context));
controllers.add(new FaceSettingsRemoveButtonPreferenceController(context));
controllers.add(new FaceSettingsFooterPreferenceController(context));
controllers.add(new FaceSettingsConfirmPreferenceController(context));
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java
index 838dc0d9808..c66546790ea 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsFooterPreferenceController.java
@@ -23,6 +23,7 @@ import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AnnotationSpan;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.widget.FooterPreference;
@@ -34,8 +35,11 @@ public class FaceSettingsFooterPreferenceController extends BasePreferenceContro
private static final String ANNOTATION_URL = "url";
+ private FaceFeatureProvider mProvider;
+
public FaceSettingsFooterPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mProvider = FeatureFactory.getFactory(context).getFaceFeatureProvider();
}
public FaceSettingsFooterPreferenceController(Context context) {
@@ -55,7 +59,12 @@ public class FaceSettingsFooterPreferenceController extends BasePreferenceContro
mContext, mContext.getString(R.string.help_url_face), getClass().getName());
final AnnotationSpan.LinkInfo linkInfo =
new AnnotationSpan.LinkInfo(mContext, ANNOTATION_URL, helpIntent);
+
+ final int footerRes = mProvider.isAttentionSupported(mContext)
+ ? R.string.security_settings_face_settings_footer
+ : R.string.security_settings_face_settings_footer_attention_not_supported;
+
preference.setTitle(AnnotationSpan.linkify(
- mContext.getText(R.string.security_settings_face_settings_footer), linkInfo));
+ mContext.getText(footerRes), linkInfo));
}
}
diff --git a/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java
index e717cb2008b..cef174d5243 100644
--- a/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java
+++ b/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java
@@ -30,8 +30,6 @@ import com.android.internal.annotations.VisibleForTesting;
public class FaceSettingsLockscreenBypassPreferenceController
extends FaceSettingsPreferenceController {
- static final String KEY = "security_lockscreen_bypass";
-
@VisibleForTesting
protected FaceManager mFaceManager;
private UserManager mUserManager;
diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
index 6817d0d3ee3..86196fd0584 100644
--- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
@@ -60,6 +60,7 @@ import java.util.Map;
public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceController implements
LifecycleObserver, OnStart, OnStop, OnDestroy, CachedBluetoothDevice.Callback {
private static final String TAG = "AdvancedBtHeaderCtrl";
+ private static final int LOW_BATTERY_LEVEL = 20;
@VisibleForTesting
LayoutPreference mLayoutPreference;
@@ -180,12 +181,18 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
Drawable createBtBatteryIcon(Context context, int level, boolean charging) {
final BatteryMeterView.BatteryMeterDrawable drawable =
new BatteryMeterView.BatteryMeterDrawable(context,
- context.getColor(R.color.meter_background_color));
+ context.getColor(R.color.meter_background_color),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.advanced_bluetooth_battery_meter_width),
+ context.getResources().getDimensionPixelSize(
+ R.dimen.advanced_bluetooth_battery_meter_height));
drawable.setBatteryLevel(level);
+ final int attr = level > LOW_BATTERY_LEVEL || charging
+ ? android.R.attr.colorControlNormal
+ : android.R.attr.colorError;
drawable.setColorFilter(new PorterDuffColorFilter(
- com.android.settings.Utils.getColorAttrDefaultColor(context,
- android.R.attr.colorControlNormal),
- PorterDuff.Mode.SRC_IN));
+ com.android.settings.Utils.getColorAttrDefaultColor(context, attr),
+ PorterDuff.Mode.SRC));
drawable.setCharging(charging);
return drawable;
diff --git a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java
index 18df872a28a..5a4ee1fe029 100644
--- a/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java
+++ b/src/com/android/settings/bluetooth/BluetoothSliceBuilder.java
@@ -136,6 +136,6 @@ public class BluetoothSliceBuilder {
final Intent intent = new Intent(ACTION_BLUETOOTH_SLICE_CHANGED)
.setClass(context, SliceBroadcastReceiver.class);
return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT);
}
}
diff --git a/src/com/android/settings/core/SettingsBaseActivity.java b/src/com/android/settings/core/SettingsBaseActivity.java
index 5ff81d54aa6..6e37b161924 100644
--- a/src/com/android/settings/core/SettingsBaseActivity.java
+++ b/src/com/android/settings/core/SettingsBaseActivity.java
@@ -17,6 +17,7 @@ package com.android.settings.core;
import android.annotation.LayoutRes;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -26,6 +27,7 @@ import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
@@ -61,6 +63,10 @@ public class SettingsBaseActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (isLockTaskModePinned() && !isSettingsRunOnTop()) {
+ Log.w(TAG, "Devices lock task mode pinned.");
+ finish();
+ }
final long startTime = System.currentTimeMillis();
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
@@ -148,6 +154,20 @@ public class SettingsBaseActivity extends FragmentActivity {
}
}
+ private boolean isLockTaskModePinned() {
+ final ActivityManager activityManager =
+ getApplicationContext().getSystemService(ActivityManager.class);
+ return activityManager.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
+ }
+
+ private boolean isSettingsRunOnTop() {
+ final ActivityManager activityManager =
+ getApplicationContext().getSystemService(ActivityManager.class);
+ final String taskPkgName = activityManager.getRunningTasks(1 /* maxNum */)
+ .get(0 /* index */).baseActivity.getPackageName();
+ return TextUtils.equals(getPackageName(), taskPkgName);
+ }
+
/**
* @return whether or not the enabled state actually changed.
*/
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 384f262a718..fd036976e50 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -74,6 +74,7 @@ import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment;
import com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings;
import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard;
import com.android.settings.display.NightDisplaySettings;
+import com.android.settings.display.darkmode.DarkModeSettingsFragment;
import com.android.settings.dream.DreamSettings;
import com.android.settings.enterprise.EnterprisePrivacySettings;
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
@@ -281,7 +282,8 @@ public class SettingsGateway {
PreviouslyConnectedDeviceDashboardFragment.class.getName(),
BatterySaverScheduleSettings.class.getName(),
MobileNetworkListFragment.class.getName(),
- GlobalActionsPanelSettings.class.getName()
+ GlobalActionsPanelSettings.class.getName(),
+ DarkModeSettingsFragment.class.getName()
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java
index fd54f19b819..95f3ae3ca59 100644
--- a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java
@@ -111,7 +111,7 @@ public class MainlineModuleVersionPreferenceController extends BasePreferenceCon
try {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern,
Locale.getDefault());
- simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ simpleDateFormat.setTimeZone(TimeZone.getDefault());
return Optional.of(simpleDateFormat.parse(text));
} catch (ParseException e) {
// ignore and try next pattern
diff --git a/src/com/android/settings/display/DarkUIPreferenceController.java b/src/com/android/settings/display/DarkUIPreferenceController.java
index d3d30b50221..98d9a69ea7c 100644
--- a/src/com/android/settings/display/DarkUIPreferenceController.java
+++ b/src/com/android/settings/display/DarkUIPreferenceController.java
@@ -21,6 +21,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.os.PowerManager;
import android.provider.Settings;
@@ -28,7 +29,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
@@ -44,7 +44,7 @@ public class DarkUIPreferenceController extends TogglePreferenceController imple
public static final int DIALOG_SEEN = 1;
@VisibleForTesting
- SwitchPreference mPreference;
+ Preference mPreference;
private UiModeManager mUiModeManager;
private PowerManager mPowerManager;
@@ -68,7 +68,8 @@ public class DarkUIPreferenceController extends TogglePreferenceController imple
@Override
public boolean isChecked() {
- return mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES;
+ return (mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_YES) != 0;
}
@Override
@@ -90,17 +91,13 @@ public class DarkUIPreferenceController extends TogglePreferenceController imple
Settings.Secure.DARK_MODE_DIALOG_SEEN, 0) == DIALOG_SEEN;
if (!dialogSeen && isChecked) {
showDarkModeDialog();
- return false;
}
- mUiModeManager.setNightMode(isChecked
- ? UiModeManager.MODE_NIGHT_YES
- : UiModeManager.MODE_NIGHT_NO);
- return true;
+ return mUiModeManager.setNightModeActivated(isChecked);
}
private void showDarkModeDialog() {
final DarkUIInfoDialogFragment frag = new DarkUIInfoDialogFragment();
- if (mFragment.getFragmentManager() != null) {
+ if (mFragment != null && mFragment.getFragmentManager() != null) {
frag.show(mFragment.getFragmentManager(), getClass().getName());
}
}
@@ -113,12 +110,10 @@ public class DarkUIPreferenceController extends TogglePreferenceController imple
boolean isBatterySaver = isPowerSaveMode();
mPreference.setEnabled(!isBatterySaver);
if (isBatterySaver) {
- int stringId = mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES
+ int stringId = isChecked()
? R.string.dark_ui_mode_disabled_summary_dark_theme_on
: R.string.dark_ui_mode_disabled_summary_dark_theme_off;
mPreference.setSummary(mContext.getString(stringId));
- } else {
- mPreference.setSummary(null);
}
}
@@ -127,22 +122,17 @@ public class DarkUIPreferenceController extends TogglePreferenceController imple
return mPowerManager.isPowerSaveMode();
}
-
- @VisibleForTesting
- void setUiModeManager(UiModeManager uiModeManager) {
- mUiModeManager = uiModeManager;
- }
-
- public void setParentFragment(Fragment fragment) {
- mFragment = fragment;
- }
-
@Override
public void onStart() {
mContext.registerReceiver(mReceiver,
new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
}
+ // used by AccessibilitySettings
+ public void setParentFragment(Fragment fragment) {
+ mFragment = fragment;
+ }
+
@Override
public void onStop() {
mContext.unregisterReceiver(mReceiver);
diff --git a/src/com/android/settings/display/darkmode/DarkModeActivationPreferenceController.java b/src/com/android/settings/display/darkmode/DarkModeActivationPreferenceController.java
new file mode 100644
index 00000000000..23f625f2899
--- /dev/null
+++ b/src/com/android/settings/display/darkmode/DarkModeActivationPreferenceController.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.view.View;
+import android.widget.Button;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.LayoutPreference;
+
+/**
+ * Controller for activate/deactivate night mode button
+ */
+public class DarkModeActivationPreferenceController extends BasePreferenceController {
+ private final UiModeManager mUiModeManager;
+ private PowerManager mPowerManager;
+ private Button mTurnOffButton;
+ private Button mTurnOnButton;
+
+ public DarkModeActivationPreferenceController(Context context,
+ String preferenceKey) {
+ super(context, preferenceKey);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ mUiModeManager = context.getSystemService(UiModeManager.class);
+ }
+
+ @Override
+ public final void updateState(Preference preference) {
+
+ final boolean batterySaver = mPowerManager.isPowerSaveMode();
+ if (batterySaver) {
+ mTurnOnButton.setVisibility(View.GONE);
+ mTurnOffButton.setVisibility(View.GONE);
+ return;
+ }
+
+ final boolean active = (mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_YES) != 0;
+ updateNightMode(active);
+ }
+
+ private void updateNightMode(boolean active) {
+ final int autoMode = mUiModeManager.getNightMode();
+ String buttonText;
+
+ if (autoMode == UiModeManager.MODE_NIGHT_AUTO) {
+ buttonText = mContext.getString(active
+ ? R.string.dark_ui_activation_off_auto
+ : R.string.dark_ui_activation_on_auto);
+ } else {
+ buttonText = mContext.getString(active
+ ? R.string.dark_ui_activation_off_manual
+ : R.string.dark_ui_activation_on_manual);
+ }
+ if (active) {
+ mTurnOnButton.setVisibility(View.GONE);
+ mTurnOffButton.setVisibility(View.VISIBLE);
+ mTurnOffButton.setText(buttonText);
+ } else {
+ mTurnOnButton.setVisibility(View.VISIBLE);
+ mTurnOffButton.setVisibility(View.GONE);
+ mTurnOnButton.setText(buttonText);
+ }
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ final boolean isActivated = (mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_YES) != 0;
+ final int autoMode = mUiModeManager.getNightMode();
+ if (autoMode == UiModeManager.MODE_NIGHT_AUTO) {
+ return mContext.getString(isActivated
+ ? R.string.dark_ui_summary_on_auto_mode_auto
+ : R.string.dark_ui_summary_off_auto_mode_auto);
+ } else {
+ return mContext.getString(isActivated
+ ? R.string.dark_ui_summary_on_auto_mode_never
+ : R.string.dark_ui_summary_off_auto_mode_never);
+ }
+ }
+
+ private final View.OnClickListener mListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final boolean active = (mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_YES) != 0;
+ mUiModeManager.setNightModeActivated(!active);
+ updateNightMode(!active);
+ }
+ };
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ final LayoutPreference preference = screen.findPreference(getPreferenceKey());
+ mTurnOnButton = preference.findViewById(R.id.dark_ui_turn_on_button);
+ mTurnOnButton.setOnClickListener(mListener);
+ mTurnOffButton = preference.findViewById(R.id.dark_ui_turn_off_button);
+ mTurnOffButton.setOnClickListener(mListener);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/display/darkmode/DarkModeObserver.java b/src/com/android/settings/display/darkmode/DarkModeObserver.java
new file mode 100644
index 00000000000..4d0d7c58f17
--- /dev/null
+++ b/src/com/android/settings/display/darkmode/DarkModeObserver.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Observes changes for dark night settings*/
+public class DarkModeObserver {
+ private static final String TAG = "DarkModeObserver";
+ private ContentObserver mContentObserver;
+ private final BroadcastReceiver mBatterySaverReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mCallback.run();
+ }
+ };
+ private Runnable mCallback;
+ private Context mContext;
+
+ public DarkModeObserver(Context context) {
+ mContext = context;
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ final String setting = uri == null ? null : uri.getLastPathSegment();
+ if (setting != null && mCallback != null) {
+ switch (setting) {
+ case Settings.Secure.UI_NIGHT_MODE:
+ mCallback.run();
+ break;
+ default:
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * subscribe callback when night mode changed in the database
+ *
+ * @param callback the callback that gets triggered when subscribed
+ */
+ public void subscribe(Runnable callback) {
+ callback.run();
+ mCallback = callback;
+ final Uri uri = Settings.Secure.getUriFor(Settings.Secure.UI_NIGHT_MODE);
+ mContext.getContentResolver().registerContentObserver(uri, false, mContentObserver);
+ final IntentFilter batteryFilter = new IntentFilter();
+ batteryFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ mContext.registerReceiver(
+ mBatterySaverReceiver, batteryFilter);
+ }
+
+ /**
+ * unsubscribe from dark ui database changes
+ */
+ public void unsubscribe() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ try {
+ mContext.unregisterReceiver(mBatterySaverReceiver);
+ } catch (IllegalArgumentException e) {
+ /* Ignore: unregistering an unregistered receiver */
+ Log.w(TAG, e.getMessage());
+ }
+ // NO-OP
+ mCallback = null;
+ }
+
+ @VisibleForTesting
+ protected void setContentObserver(ContentObserver co) {
+ mContentObserver = co;
+ }
+
+ @VisibleForTesting
+ protected ContentObserver getContentObserver() {
+ return mContentObserver;
+ }
+}
diff --git a/src/com/android/settings/display/darkmode/DarkModePreference.java b/src/com/android/settings/display/darkmode/DarkModePreference.java
new file mode 100644
index 00000000000..c5fbdedf579
--- /dev/null
+++ b/src/com/android/settings/display/darkmode/DarkModePreference.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.util.AttributeSet;
+import com.android.settings.R;
+import com.android.settings.widget.MasterSwitchPreference;
+
+/**
+ * component for the display settings dark ui summary*/
+public class DarkModePreference extends MasterSwitchPreference {
+
+ private UiModeManager mUiModeManager;
+ private DarkModeObserver mDarkModeObserver;
+ private PowerManager mPowerManager;
+ private Runnable mCallback;
+
+ public DarkModePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDarkModeObserver = new DarkModeObserver(context);
+ mUiModeManager = context.getSystemService(UiModeManager.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ mCallback = () -> {
+ final boolean batterySaver = mPowerManager.isPowerSaveMode();
+ final boolean active = (getContext().getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_YES) != 0;
+ setSwitchEnabled(!batterySaver);
+ updateSummary(batterySaver, active);
+ };
+ mDarkModeObserver.subscribe(mCallback);
+ }
+
+ @Override
+ public void onAttached() {
+ super.onAttached();
+ mDarkModeObserver.subscribe(mCallback);
+ }
+
+ @Override
+ public void onDetached() {
+ super.onDetached();
+ mDarkModeObserver.unsubscribe();
+ }
+
+ private void updateSummary(boolean batterySaver, boolean active) {
+ if (batterySaver) {
+ final int stringId = active ? R.string.dark_ui_mode_disabled_summary_dark_theme_on
+ : R.string.dark_ui_mode_disabled_summary_dark_theme_off;
+ setSummary(getContext().getString(stringId));
+ return;
+ }
+ final boolean auto = mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_AUTO;
+
+ String detail;
+ if (active) {
+ detail = getContext().getString(auto
+ ? R.string.dark_ui_summary_on_auto_mode_auto
+ : R.string.dark_ui_summary_on_auto_mode_never);
+ } else {
+ detail = getContext().getString(auto
+ ? R.string.dark_ui_summary_off_auto_mode_auto
+ : R.string.dark_ui_summary_off_auto_mode_never);
+ }
+ String summary = getContext().getString(active
+ ? R.string.dark_ui_summary_on
+ : R.string.dark_ui_summary_off, detail);
+
+ setSummary(summary);
+ }
+}
diff --git a/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java b/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java
new file mode 100644
index 00000000000..d99c7bf59e8
--- /dev/null
+++ b/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import androidx.preference.DropDownPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * Controller for the dark ui option dropdown
+ */
+public class DarkModeScheduleSelectorController extends BasePreferenceController
+ implements Preference.OnPreferenceChangeListener {
+
+ private final UiModeManager mUiModeManager;
+ private PowerManager mPowerManager;
+ private DropDownPreference mPreference;
+ private String mCurrentMode;
+
+ public DarkModeScheduleSelectorController(Context context, String key) {
+ super(context, key);
+ mUiModeManager = context.getSystemService(UiModeManager.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return BasePreferenceController.AVAILABLE;
+ }
+
+ @Override
+ public final void updateState(Preference preference) {
+ final boolean batterySaver = mPowerManager.isPowerSaveMode();
+ mPreference.setEnabled(!batterySaver);
+ mCurrentMode =
+ mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_AUTO
+ ? mContext.getString(R.string.dark_ui_auto_mode_auto)
+ : mContext.getString(R.string.dark_ui_auto_mode_never);
+ mPreference.setValue(mCurrentMode);
+ }
+ @Override
+ public final boolean onPreferenceChange(Preference preference, Object newValue) {
+ String newMode = (String) newValue;
+ if (newMode == mCurrentMode) {
+ return false;
+ }
+ mCurrentMode = newMode;
+ if (mCurrentMode == mContext.getString(R.string.dark_ui_auto_mode_never)) {
+ boolean active = (mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_YES) != 0;
+ int mode = active ? UiModeManager.MODE_NIGHT_YES
+ : UiModeManager.MODE_NIGHT_NO;
+ mUiModeManager.setNightMode(mode);
+
+ } else if (mCurrentMode ==
+ mContext.getString(R.string.dark_ui_auto_mode_auto)) {
+ mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_AUTO);
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/display/darkmode/DarkModeSettingsFragment.java b/src/com/android/settings/display/darkmode/DarkModeSettingsFragment.java
new file mode 100644
index 00000000000..045c2bbe177
--- /dev/null
+++ b/src/com/android/settings/display/darkmode/DarkModeSettingsFragment.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.app.settings.SettingsEnums;
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/**
+ * Settings screen for Dark UI Mode
+ */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class DarkModeSettingsFragment extends DashboardFragment {
+
+ private static final String TAG = "DarkModeSettingsFragment";
+ private DarkModeObserver mContentObserver;
+ private Runnable mCallback = () -> {
+ updatePreferenceStates();
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Context context = getContext();
+ mContentObserver = new DarkModeObserver(context);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Listen for changes only while visible.
+ mContentObserver.subscribe(mCallback);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ // Stop listening for state changes.
+ mContentObserver.unsubscribe();
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.dark_mode_settings;
+ }
+
+ @Override
+ public int getHelpResource() {
+ return R.string.help_url_dark_theme;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DARK_UI_SETTINGS;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider();
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
index 3064d4f2ba3..bfa43d1d3dd 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
@@ -55,8 +55,6 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
TextView mBatteryPercentText;
@VisibleForTesting
TextView mSummary1;
- @VisibleForTesting
- TextView mSummary2;
private Activity mActivity;
private PreferenceFragmentCompat mHost;
@@ -90,7 +88,6 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
.findViewById(R.id.battery_header_icon);
mBatteryPercentText = mBatteryLayoutPref.findViewById(R.id.battery_percent);
mSummary1 = mBatteryLayoutPref.findViewById(R.id.summary1);
- mSummary2 = mBatteryLayoutPref.findViewById(R.id.summary2);
quickUpdateHeaderPreference();
}
@@ -115,9 +112,6 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
} else {
mSummary1.setText(info.remainingLabel);
}
- // Clear this just to be sure we don't get UI jank on re-entering this view from another
- // activity.
- mSummary2.setText("");
mBatteryMeterView.setBatteryLevel(info.batteryLevel);
mBatteryMeterView.setCharging(!info.discharging);
diff --git a/src/com/android/settings/fuelgauge/BatteryMeterView.java b/src/com/android/settings/fuelgauge/BatteryMeterView.java
index dc30c28ace9..d54f5a4e2e0 100644
--- a/src/com/android/settings/fuelgauge/BatteryMeterView.java
+++ b/src/com/android/settings/fuelgauge/BatteryMeterView.java
@@ -117,6 +117,13 @@ public class BatteryMeterView extends ImageView {
.getDimensionPixelSize(R.dimen.battery_meter_height);
}
+ public BatteryMeterDrawable(Context context, int frameColor, int width, int height) {
+ super(context, frameColor);
+
+ mIntrinsicWidth = width;
+ mIntrinsicHeight = height;
+ }
+
@Override
public int getIntrinsicWidth() {
return mIntrinsicWidth;
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index 880255b212c..d25c2d491ae 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -19,7 +19,6 @@ package com.android.settings.fuelgauge;
import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType;
import android.app.settings.SettingsEnums;
-import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
@@ -27,7 +26,6 @@ import android.os.BatteryStats;
import android.os.Bundle;
import android.os.Handler;
import android.provider.SearchIndexableResource;
-import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.format.Formatter;
import android.view.Menu;
@@ -162,7 +160,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
final TextView percentRemaining =
mBatteryLayoutPref.findViewById(R.id.battery_percent);
final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1);
- final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2);
BatteryInfo oldInfo = batteryInfos.get(0);
BatteryInfo newInfo = batteryInfos.get(1);
percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel));
@@ -170,14 +167,13 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
// set the text to the old estimate (copied from battery info). Note that this
// can sometimes say 0 time remaining because battery stats requires the phone
// be unplugged for a period of time before being willing ot make an estimate.
- summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString(
+ final String OldEstimateString = mPowerFeatureProvider.getOldEstimateDebugString(
Formatter.formatShortElapsedTime(getContext(),
- PowerUtil.convertUsToMs(oldInfo.remainingTimeUs))));
-
- // for this one we can just set the string directly
- summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString(
+ PowerUtil.convertUsToMs(oldInfo.remainingTimeUs)));
+ final String NewEstimateString = mPowerFeatureProvider.getEnhancedEstimateDebugString(
Formatter.formatShortElapsedTime(getContext(),
- PowerUtil.convertUsToMs(newInfo.remainingTimeUs))));
+ PowerUtil.convertUsToMs(newInfo.remainingTimeUs)));
+ summary1.setText(OldEstimateString + "\n" + NewEstimateString);
batteryView.setBatteryLevel(oldInfo.batteryLevel);
batteryView.setCharging(!oldInfo.discharging);
diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java
index fa231012f96..5e6c54bb7c2 100644
--- a/src/com/android/settings/homepage/SettingsHomepageActivity.java
+++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java
@@ -33,6 +33,7 @@ import androidx.fragment.app.FragmentTransaction;
import com.android.settings.R;
import com.android.settings.accounts.AvatarViewMixin;
+import com.android.settings.core.HideNonSystemOverlayMixin;
import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
import com.android.settings.overlay.FeatureFactory;
@@ -54,8 +55,8 @@ public class SettingsHomepageActivity extends FragmentActivity {
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
final ImageView avatarView = findViewById(R.id.account_avatar);
- final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(this, avatarView);
- getLifecycle().addObserver(avatarViewMixin);
+ getLifecycle().addObserver(new AvatarViewMixin(this, avatarView));
+ getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
// Only allow contextual feature on high ram devices.
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
index 6d3649d4058..5a041438a2e 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardLoader.java
@@ -37,12 +37,11 @@ import com.android.settings.homepage.contextualcards.logging.ContextualCardLogUt
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.AsyncLoaderCompat;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -56,7 +55,6 @@ public class ContextualCardLoader extends AsyncLoaderCompat
private static final String TAG = "ContextualCardLoader";
private static final long ELIGIBILITY_CHECKER_TIMEOUT_MS = 250;
- private final ExecutorService mExecutorService;
private final ContentObserver mObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
@@ -76,7 +74,6 @@ public class ContextualCardLoader extends AsyncLoaderCompat
ContextualCardLoader(Context context) {
super(context);
mContext = context.getApplicationContext();
- mExecutorService = Executors.newCachedThreadPool();
}
@Override
@@ -163,8 +160,8 @@ public class ContextualCardLoader extends AsyncLoaderCompat
final List> eligibleCards = new ArrayList<>();
for (ContextualCard card : candidates) {
- final EligibleCardChecker future = new EligibleCardChecker(mContext, card);
- eligibleCards.add(mExecutorService.submit(future));
+ final EligibleCardChecker checker = new EligibleCardChecker(mContext, card);
+ eligibleCards.add(ThreadUtils.postOnBackgroundThread(checker));
}
// Collect future and eligible cards
for (Future cardFuture : eligibleCards) {
diff --git a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java
index 385f8cda010..1494293341f 100644
--- a/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java
+++ b/src/com/android/settings/homepage/contextualcards/SettingsContextualCardProvider.java
@@ -78,6 +78,12 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
.setCardName(CustomSliceRegistry.FACE_ENROLL_SLICE_URI.toString())
.setCardCategory(ContextualCard.Category.DEFAULT)
.build();
+ final ContextualCard darkThemeCard =
+ ContextualCard.newBuilder()
+ .setSliceUri(CustomSliceRegistry.DARK_THEME_SLICE_URI.toString())
+ .setCardName(CustomSliceRegistry.DARK_THEME_SLICE_URI.toString())
+ .setCardCategory(ContextualCard.Category.IMPORTANT)
+ .build();
final ContextualCardList cards = ContextualCardList.newBuilder()
.addCard(wifiCard)
.addCard(connectedDeviceCard)
@@ -86,6 +92,7 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
.addCard(notificationChannelCard)
.addCard(contextualAdaptiveSleepCard)
.addCard(contextualFaceSettingsCard)
+ .addCard(darkThemeCard)
.build();
return cards;
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java b/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
index 66f6c81e984..1beac9b87bf 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/ConditionManager.java
@@ -23,13 +23,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settings.homepage.contextualcards.ContextualCard;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -42,7 +41,6 @@ public class ConditionManager {
private static final long DISPLAYABLE_CHECKER_TIMEOUT_MS = 20;
- private final ExecutorService mExecutorService;
private final Context mAppContext;
private final ConditionListener mListener;
@@ -50,7 +48,6 @@ public class ConditionManager {
public ConditionManager(Context context, ConditionListener listener) {
mAppContext = context.getApplicationContext();
- mExecutorService = Executors.newCachedThreadPool();
mCardControllers = new ArrayList<>();
mListener = listener;
initCandidates();
@@ -64,8 +61,8 @@ public class ConditionManager {
final List> displayableCards = new ArrayList<>();
// Check displayable future
for (ConditionalCardController card : mCardControllers) {
- final DisplayableChecker future = new DisplayableChecker(getController(card.getId()));
- displayableCards.add(mExecutorService.submit(future));
+ final DisplayableChecker checker = new DisplayableChecker(getController(card.getId()));
+ displayableCards.add(ThreadUtils.postOnBackgroundThread(checker));
}
// Collect future and add displayable cards
for (Future cardFuture : displayableCards) {
diff --git a/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java
new file mode 100644
index 00000000000..1b7f003ac8f
--- /dev/null
+++ b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2019 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.homepage.contextualcards.slices;
+
+import static androidx.slice.builders.ListBuilder.ICON_IMAGE;
+
+import static android.provider.Settings.Global.LOW_POWER_MODE;
+
+import android.annotation.ColorInt;
+import android.app.PendingIntent;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.Slice;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.SliceAction;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.slices.CustomSliceRegistry;
+import com.android.settings.slices.CustomSliceable;
+import com.android.settings.slices.SliceBackgroundWorker;
+
+import java.io.IOException;
+
+public class DarkThemeSlice implements CustomSliceable {
+ private static final String TAG = "DarkThemeSlice";
+ private static final int BATTERY_LEVEL_THRESHOLD = 50;
+ private static final int DELAY_TIME_EXECUTING_DARK_THEME = 200;
+
+ // Keep the slice even Dark theme mode changed when it is on HomePage
+ @VisibleForTesting
+ static boolean sKeepSliceShow;
+ @VisibleForTesting
+ static long sActiveUiSession = -1000;
+
+ private final Context mContext;
+ private final UiModeManager mUiModeManager;
+ private final PowerManager mPowerManager;
+
+ public DarkThemeSlice(Context context) {
+ mContext = context;
+ mUiModeManager = context.getSystemService(UiModeManager.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ }
+
+ @Override
+ public Slice getSlice() {
+ final long currentUiSession = FeatureFactory.getFactory(mContext)
+ .getSlicesFeatureProvider().getUiSessionToken();
+ if (currentUiSession != sActiveUiSession) {
+ sActiveUiSession = currentUiSession;
+ sKeepSliceShow = false;
+ }
+ // Dark theme slice will disappear when battery saver is ON.
+ if (mPowerManager.isPowerSaveMode() || (!sKeepSliceShow && !isAvailable(mContext))) {
+ return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI,
+ ListBuilder.INFINITY)
+ .setIsError(true)
+ .build();
+ }
+ sKeepSliceShow = true;
+ final PendingIntent toggleAction = getBroadcastIntent(mContext);
+ @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
+ final IconCompat icon =
+ IconCompat.createWithResource(mContext, R.drawable.dark_theme);
+ return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI,
+ ListBuilder.INFINITY)
+ .setAccentColor(color)
+ .addRow(new ListBuilder.RowBuilder()
+ .setTitle(mContext.getText(R.string.dark_theme_slice_title))
+ .setTitleItem(icon, ICON_IMAGE)
+ .setSubtitle(mContext.getText(R.string.dark_theme_slice_subtitle))
+ .setPrimaryAction(
+ SliceAction.createToggle(toggleAction, null /* actionTitle */,
+ isDarkThemeMode(mContext))))
+ .build();
+ }
+
+ @Override
+ public Uri getUri() {
+ return CustomSliceRegistry.DARK_THEME_SLICE_URI;
+ }
+
+ @Override
+ public void onNotifyChange(Intent intent) {
+ final boolean isChecked = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE,
+ false);
+ // make toggle transition more smooth before dark theme takes effect
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ mUiModeManager.setNightModeActivated(isChecked);
+ }, DELAY_TIME_EXECUTING_DARK_THEME);
+ }
+
+ @Override
+ public Intent getIntent() {
+ return null;
+ }
+
+ @Override
+ public Class getBackgroundWorkerClass() {
+ return DarkThemeWorker.class;
+ }
+
+ @VisibleForTesting
+ boolean isAvailable(Context context) {
+ // checking dark theme mode.
+ if (isDarkThemeMode(context)) {
+ return false;
+ }
+
+ // checking the current battery level
+ final BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
+ final int level = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+ Log.d(TAG, "battery level=" + level);
+ return level <= BATTERY_LEVEL_THRESHOLD;
+ }
+
+ @VisibleForTesting
+ boolean isDarkThemeMode(Context context) {
+ final int currentNightMode =
+ context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ public static class DarkThemeWorker extends SliceBackgroundWorker {
+ private final Context mContext;
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean bChanged) {
+ if (mContext.getSystemService(PowerManager.class).isPowerSaveMode()) {
+ notifySliceChange();
+ }
+ }
+ };
+
+ public DarkThemeWorker(Context context, Uri uri) {
+ super(context, uri);
+ mContext = context;
+ }
+
+ @Override
+ protected void onSlicePinned() {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(LOW_POWER_MODE), false /* notifyForDescendants */,
+ mContentObserver);
+ }
+
+ @Override
+ protected void onSliceUnpinned() {
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ }
+}
diff --git a/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java b/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java
index ff76779fd69..bdf97a802a0 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java
@@ -20,8 +20,6 @@ import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
-import static com.android.settings.notification.ChannelListPreferenceController.ARG_FROM_SETTINGS;
-
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@@ -62,13 +60,12 @@ import com.android.settings.slices.SliceBuilderUtils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -135,7 +132,6 @@ public class NotificationChannelSlice implements CustomSliceable {
};
protected final Context mContext;
- private final ExecutorService mExecutorService;
@VisibleForTesting
NotificationBackend mNotificationBackend;
private NotificationBackend.AppRow mAppRow;
@@ -145,7 +141,6 @@ public class NotificationChannelSlice implements CustomSliceable {
public NotificationChannelSlice(Context context) {
mContext = context;
mNotificationBackend = new NotificationBackend();
- mExecutorService = Executors.newCachedThreadPool();
}
@Override
@@ -376,9 +371,9 @@ public class NotificationChannelSlice implements CustomSliceable {
// Create tasks to get notification data for multi-channel packages.
final List> appRowTasks = new ArrayList<>();
for (PackageInfo packageInfo : packageInfoList) {
- final NotificationMultiChannelAppRow future = new NotificationMultiChannelAppRow(
+ final NotificationMultiChannelAppRow appRow = new NotificationMultiChannelAppRow(
mContext, mNotificationBackend, packageInfo);
- appRowTasks.add(mExecutorService.submit(future));
+ appRowTasks.add(ThreadUtils.postOnBackgroundThread(appRow));
}
// Get the package which has sent at least ~10 notifications and not turn off channels.
diff --git a/src/com/android/settings/network/telephony/MmsMessagePreferenceController.java b/src/com/android/settings/network/telephony/MmsMessagePreferenceController.java
index c1f294977eb..00f5ebcc77a 100644
--- a/src/com/android/settings/network/telephony/MmsMessagePreferenceController.java
+++ b/src/com/android/settings/network/telephony/MmsMessagePreferenceController.java
@@ -22,11 +22,8 @@ import android.os.Looper;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
-import android.util.Log;
-import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
import com.android.settings.network.MobileDataContentObserver;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -41,14 +38,14 @@ public class MmsMessagePreferenceController extends TelephonyTogglePreferenceCon
private TelephonyManager mTelephonyManager;
private SubscriptionManager mSubscriptionManager;
private MobileDataContentObserver mMobileDataContentObserver;
- private SwitchPreference mPreference;
+ private PreferenceScreen mScreen;
public MmsMessagePreferenceController(Context context, String key) {
super(context, key);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
mMobileDataContentObserver = new MobileDataContentObserver(
new Handler(Looper.getMainLooper()));
- mMobileDataContentObserver.setOnMobileDataChangedListener(()->updateState(mPreference));
+ mMobileDataContentObserver.setOnMobileDataChangedListener(()->refreshPreference());
}
@Override
@@ -79,15 +76,9 @@ public class MmsMessagePreferenceController extends TelephonyTogglePreferenceCon
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
+ mScreen = screen;
}
- @Override
- public void updateState(Preference preference) {
- super.updateState(preference);
- preference.setVisible(isAvailable());
- ((SwitchPreference) preference).setChecked(isChecked());
- }
public void init(int subId) {
mSubId = subId;
@@ -104,4 +95,10 @@ public class MmsMessagePreferenceController extends TelephonyTogglePreferenceCon
public boolean isChecked() {
return mTelephonyManager.isDataEnabledForApn(ApnSetting.TYPE_MMS);
}
+
+ private void refreshPreference() {
+ if (mScreen != null) {
+ super.displayPreference(mScreen);
+ }
+ }
}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
index a4fa8453a92..bd9b709d3f4 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
@@ -228,16 +228,23 @@ public class MobileNetworkUtils {
final String currentCountry = tm.getNetworkCountryIso().toLowerCase();
final String supportedCountries =
Settings.Global.getString(cr, Settings.Global.EUICC_SUPPORTED_COUNTRIES);
+ final String unsupportedCountries =
+ Settings.Global.getString(cr, Settings.Global.EUICC_UNSUPPORTED_COUNTRIES);
+
boolean inEsimSupportedCountries = false;
- if (TextUtils.isEmpty(currentCountry)) {
- inEsimSupportedCountries = true;
- } else if (!TextUtils.isEmpty(supportedCountries)) {
- final List supportedCountryList =
- Arrays.asList(TextUtils.split(supportedCountries.toLowerCase(), ","));
- if (supportedCountryList.contains(currentCountry)) {
- inEsimSupportedCountries = true;
- }
+
+ if (TextUtils.isEmpty(supportedCountries)) {
+ // White list is empty, use blacklist.
+ Log.d(TAG, "Using blacklist unsupportedCountries=" + unsupportedCountries);
+ inEsimSupportedCountries = !isEsimUnsupportedCountry(currentCountry,
+ unsupportedCountries);
+ } else {
+ Log.d(TAG, "Using whitelist supportedCountries=" + supportedCountries);
+ inEsimSupportedCountries = isEsimSupportedCountry(currentCountry, supportedCountries);
}
+
+ Log.d(TAG, "inEsimSupportedCountries=" + inEsimSupportedCountries);
+
final boolean esimIgnoredDevice =
Arrays.asList(TextUtils.split(SystemProperties.get(KEY_ESIM_CID_IGNORE, ""), ","))
.contains(SystemProperties.get(KEY_CID, null));
@@ -613,4 +620,24 @@ public class MobileNetworkUtils {
}
return tm.getNetworkOperatorName();
}
+
+ private static boolean isEsimSupportedCountry(String country, String countriesListString) {
+ if (TextUtils.isEmpty(country)) {
+ return true;
+ } else if (TextUtils.isEmpty(countriesListString)) {
+ return false;
+ }
+ final List supportedCountries =
+ Arrays.asList(TextUtils.split(countriesListString.toLowerCase(), ","));
+ return supportedCountries.contains(country);
+ }
+
+ private static boolean isEsimUnsupportedCountry(String country, String countriesListString) {
+ if (TextUtils.isEmpty(country) || TextUtils.isEmpty(countriesListString)) {
+ return false;
+ }
+ final List unsupportedCountries =
+ Arrays.asList(TextUtils.split(countriesListString.toLowerCase(), ","));
+ return unsupportedCountries.contains(country);
+ }
}
diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java
index d9af345d652..afbce098f7d 100644
--- a/src/com/android/settings/overlay/FeatureFactory.java
+++ b/src/com/android/settings/overlay/FeatureFactory.java
@@ -26,6 +26,7 @@ import com.android.settings.R;
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.aware.AwareFeatureProvider;
+import com.android.settings.biometrics.face.FaceFeatureProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
@@ -133,6 +134,8 @@ public abstract class FeatureFactory {
public abstract AwareFeatureProvider getAwareFeatureProvider();
+ public abstract FaceFeatureProvider getFaceFeatureProvider();
+
public static final class FactoryNotFoundException extends RuntimeException {
public FactoryNotFoundException(Throwable throwable) {
super("Unable to create factory. Did you misconfigure Proguard?", throwable);
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java
index 2f9626d8d34..29beb5b2160 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.java
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java
@@ -30,6 +30,8 @@ import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProviderImpl;
import com.android.settings.aware.AwareFeatureProvider;
import com.android.settings.aware.AwareFeatureProviderImpl;
+import com.android.settings.biometrics.face.FaceFeatureProvider;
+import com.android.settings.biometrics.face.FaceFeatureProviderImpl;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.bluetooth.BluetoothFeatureProviderImpl;
import com.android.settings.connecteddevice.dock.DockUpdaterFeatureProviderImpl;
@@ -84,6 +86,7 @@ public class FeatureFactoryImpl extends FeatureFactory {
private ContextualCardFeatureProvider mContextualCardFeatureProvider;
private BluetoothFeatureProvider mBluetoothFeatureProvider;
private AwareFeatureProvider mAwareFeatureProvider;
+ private FaceFeatureProvider mFaceFeatureProvider;
@Override
public SupportFeatureProvider getSupportFeatureProvider(Context context) {
@@ -255,4 +258,12 @@ public class FeatureFactoryImpl extends FeatureFactory {
}
return mAwareFeatureProvider;
}
+
+ @Override
+ public FaceFeatureProvider getFaceFeatureProvider() {
+ if (mFaceFeatureProvider == null) {
+ mFaceFeatureProvider = new FaceFeatureProviderImpl();
+ }
+ return mFaceFeatureProvider;
+ }
}
diff --git a/src/com/android/settings/panel/SettingsPanelActivity.java b/src/com/android/settings/panel/SettingsPanelActivity.java
index 749a46e0cde..86931866afc 100644
--- a/src/com/android/settings/panel/SettingsPanelActivity.java
+++ b/src/com/android/settings/panel/SettingsPanelActivity.java
@@ -32,6 +32,7 @@ import androidx.fragment.app.FragmentManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
+import com.android.settings.core.HideNonSystemOverlayMixin;
/**
* Dialog Activity to host Settings Slices.
@@ -62,6 +63,7 @@ public class SettingsPanelActivity extends FragmentActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
createOrUpdatePanel(true /* shouldForceCreation */);
+ getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
}
@Override
diff --git a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java
deleted file mode 100644
index 28533df03a4..00000000000
--- a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2019 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.privacy;
-
-import static android.Manifest.permission_group.CAMERA;
-import static android.Manifest.permission_group.LOCATION;
-import static android.Manifest.permission_group.MICROPHONE;
-
-import static java.util.concurrent.TimeUnit.DAYS;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.permission.PermissionControllerManager;
-import android.permission.RuntimePermissionUsageInfo;
-import android.provider.DeviceConfig;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.Utils;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnCreate;
-import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
-import com.android.settingslib.core.lifecycle.events.OnStart;
-import com.android.settingslib.widget.BarChartInfo;
-import com.android.settingslib.widget.BarChartPreference;
-import com.android.settingslib.widget.BarViewInfo;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-public class PermissionBarChartPreferenceController extends BasePreferenceController implements
- PermissionControllerManager.OnPermissionUsageResultCallback, LifecycleObserver, OnCreate,
- OnStart, OnSaveInstanceState {
-
- private static final String TAG = "BarChartPreferenceCtl";
- private static final String KEY_PERMISSION_USAGE = "usage_infos";
-
- @VisibleForTesting
- List mOldUsageInfos;
- private PackageManager mPackageManager;
- private PrivacyDashboardFragment mParent;
- private BarChartPreference mBarChartPreference;
-
- public PermissionBarChartPreferenceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- mOldUsageInfos = new ArrayList<>();
- mPackageManager = context.getPackageManager();
- }
-
- public void setFragment(PrivacyDashboardFragment fragment) {
- mParent = fragment;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- mOldUsageInfos = savedInstanceState.getParcelableArrayList(KEY_PERMISSION_USAGE);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- outState.putParcelableList(KEY_PERMISSION_USAGE, mOldUsageInfos);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mBarChartPreference = screen.findPreference(getPreferenceKey());
-
- final BarChartInfo info = new BarChartInfo.Builder()
- .setTitle(R.string.permission_bar_chart_title)
- .setDetails(R.string.permission_bar_chart_details)
- .setEmptyText(R.string.permission_bar_chart_empty_text)
- .setDetailsOnClickListener((View v) -> {
- final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
- intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
- mContext.startActivity(intent);
- })
- .build();
-
- mBarChartPreference.initializeBarChart(info);
- if (!mOldUsageInfos.isEmpty()) {
- mBarChartPreference.setBarViewInfos(createBarViews(mOldUsageInfos));
- }
- }
-
- @Override
- public void onStart() {
- if (!isAvailable()) {
- return;
- }
-
- // We don't hide chart when we have existing data.
- mBarChartPreference.updateLoadingState(mOldUsageInfos.isEmpty() /* isLoading */);
- // But we still need to hint user with progress bar that we are updating new usage data.
- mParent.setLoadingEnabled(true /* enabled */);
- retrievePermissionUsageData();
- }
-
- @Override
- public void onPermissionUsageResult(@NonNull List usageInfos) {
- usageInfos.sort((x, y) -> {
- int usageDiff = y.getAppAccessCount() - x.getAppAccessCount();
- if (usageDiff != 0) {
- return usageDiff;
- }
- String xName = x.getName();
- String yName = y.getName();
- if (xName.equals(LOCATION)) {
- return -1;
- } else if (yName.equals(LOCATION)) {
- return 1;
- } else if (xName.equals(MICROPHONE)) {
- return -1;
- } else if (yName.equals(MICROPHONE)) {
- return 1;
- } else if (xName.equals(CAMERA)) {
- return -1;
- } else if (yName.equals(CAMERA)) {
- return 1;
- }
- return x.getName().compareTo(y.getName());
- });
-
- // If the result is different, we need to update bar views.
- if (!areSamePermissionGroups(usageInfos)) {
- mBarChartPreference.setBarViewInfos(createBarViews(usageInfos));
- mOldUsageInfos = usageInfos;
- }
-
- mBarChartPreference.updateLoadingState(false /* isLoading */);
- mParent.setLoadingEnabled(false /* enabled */);
- }
-
- private void retrievePermissionUsageData() {
- mContext.getSystemService(PermissionControllerManager.class).getPermissionUsages(
- false /* countSystem */, (int) DAYS.toMillis(1),
- mContext.getMainExecutor() /* executor */, this /* callback */);
- }
-
- private BarViewInfo[] createBarViews(List usageInfos) {
- if (usageInfos.isEmpty()) {
- return null;
- }
-
- final BarViewInfo[] barViewInfos = new BarViewInfo[
- Math.min(BarChartPreference.MAXIMUM_BAR_VIEWS, usageInfos.size())];
-
- for (int index = 0; index < barViewInfos.length; index++) {
- final RuntimePermissionUsageInfo permissionGroupInfo = usageInfos.get(index);
- final int count = permissionGroupInfo.getAppAccessCount();
- final CharSequence permLabel = getPermissionGroupLabel(permissionGroupInfo.getName());
-
- barViewInfos[index] = new BarViewInfo(
- getPermissionGroupIcon(permissionGroupInfo.getName()), count, permLabel,
- mContext.getResources().getQuantityString(R.plurals.permission_bar_chart_label,
- count, count), permLabel);
-
- // Set the click listener for each bar view.
- // The listener will navigate user to permission usage app.
- barViewInfos[index].setClickListener((View v) -> {
- final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE);
- intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permissionGroupInfo.getName());
- intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1));
- mContext.startActivity(intent);
- });
- }
-
- return barViewInfos;
- }
-
- private Drawable getPermissionGroupIcon(String permissionGroup) {
- Drawable icon = null;
- try {
- icon = mPackageManager.getPermissionGroupInfo(permissionGroup, 0)
- .loadIcon(mPackageManager);
- icon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorSecondary));
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Cannot find group icon for " + permissionGroup, e);
- }
-
- return icon;
- }
-
- private CharSequence getPermissionGroupLabel(String permissionGroup) {
- CharSequence label = null;
- try {
- label = mPackageManager.getPermissionGroupInfo(permissionGroup, 0)
- .loadLabel(mPackageManager);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Cannot find group label for " + permissionGroup, e);
- }
-
- return label;
- }
-
- private boolean areSamePermissionGroups(List newUsageInfos) {
- if (newUsageInfos.size() != mOldUsageInfos.size()) {
- return false;
- }
-
- for (int index = 0; index < newUsageInfos.size(); index++) {
- final RuntimePermissionUsageInfo newInfo = newUsageInfos.get(index);
- final RuntimePermissionUsageInfo oldInfo = mOldUsageInfos.get(index);
-
- if (!newInfo.getName().equals(oldInfo.getName()) ||
- newInfo.getAppAccessCount() != oldInfo.getAppAccessCount()) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/src/com/android/settings/privacy/PrivacyDashboardFragment.java b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
index fa21f9dd444..1869cffa5df 100644
--- a/src/com/android/settings/privacy/PrivacyDashboardFragment.java
+++ b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
@@ -18,14 +18,12 @@ package com.android.settings.privacy;
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.os.Bundle;
import android.provider.SearchIndexableResource;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
-import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.notification.LockScreenNotificationPreferenceController;
import com.android.settings.search.BaseSearchIndexProvider;
@@ -75,34 +73,6 @@ public class PrivacyDashboardFragment extends DashboardFragment {
return buildPreferenceControllers(context, getSettingsLifecycle());
}
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- use(PermissionBarChartPreferenceController.class).setFragment(this /* fragment */);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- Utils.setActionBarShadowAnimation(getActivity(), getSettingsLifecycle(), getListView());
- initLoadingBar();
- }
-
- @VisibleForTesting
- void initLoadingBar() {
- mProgressHeader = setPinnedHeaderView(R.layout.progress_header);
- mProgressAnimation = mProgressHeader.findViewById(R.id.progress_bar_animation);
- setLoadingEnabled(false);
- }
-
- @VisibleForTesting
- void setLoadingEnabled(boolean enabled) {
- if (mProgressHeader != null && mProgressAnimation != null) {
- mProgressHeader.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
- mProgressAnimation.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
- }
- }
-
private static List buildPreferenceControllers(
Context context, Lifecycle lifecycle) {
final List controllers = new ArrayList<>();
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index 946a9d3dfda..42d81e95598 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -37,6 +37,7 @@ import com.android.settings.homepage.contextualcards.slices.BatteryFixSlice;
import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice;
+import com.android.settings.homepage.contextualcards.slices.DarkThemeSlice;
import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice;
@@ -342,6 +343,16 @@ public class CustomSliceRegistry {
.appendPath("media_output_indicator")
.build();
+ /**
+ * Backing Uri for the Dark theme Slice.
+ */
+ public static final Uri DARK_THEME_SLICE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("dark_theme")
+ .build();
+
@VisibleForTesting
static final Map> sUriToSlice;
@@ -367,6 +378,7 @@ public class CustomSliceRegistry {
sUriToSlice.put(NOTIFICATION_CHANNEL_SLICE_URI, NotificationChannelSlice.class);
sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class);
sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
+ sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class);
}
public static Class extends CustomSliceable> getSliceClassByUri(Uri uri) {
diff --git a/src/com/android/settings/slices/CustomSliceable.java b/src/com/android/settings/slices/CustomSliceable.java
index 9566be1285a..92c604e189a 100644
--- a/src/com/android/settings/slices/CustomSliceable.java
+++ b/src/com/android/settings/slices/CustomSliceable.java
@@ -95,7 +95,7 @@ public interface CustomSliceable extends Sliceable {
.setData(getUri())
.setClass(context, SliceBroadcastReceiver.class);
return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java
index b3ddc582418..45da77625cc 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -175,7 +175,7 @@ public class SliceBuilderUtils {
.putExtra(EXTRA_SLICE_KEY, data.getKey())
.putExtra(EXTRA_SLICE_PLATFORM_DEFINED, data.isPlatformDefined());
return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
diff --git a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
index 4762403f4db..ceda8a70a05 100644
--- a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
+++ b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java
@@ -310,7 +310,7 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp
mHandler.sendEmptyMessageDelayed(MESSAGE_STOP_SCAN_WIFI_LIST, DELAY_TIME_STOP_SCAN_MS);
if (mFilterWifiTracker == null) {
- mFilterWifiTracker = new FilterWifiTracker(getActivity(), getSettingsLifecycle());
+ mFilterWifiTracker = new FilterWifiTracker(getContext(), getSettingsLifecycle());
}
mFilterWifiTracker.onResume();
}
@@ -476,11 +476,13 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp
private final class FilterWifiTracker {
private final List mAccessPointKeys;
private final WifiTracker mWifiTracker;
+ private final Context mContext;
public FilterWifiTracker(Context context, Lifecycle lifecycle) {
mWifiTracker = WifiTrackerFactory.create(context, mWifiListener,
lifecycle, /* includeSaved */ true, /* includeScans */ true);
mAccessPointKeys = new ArrayList<>();
+ mContext = context;
}
/**
@@ -489,7 +491,7 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp
*/
public void updateKeys(List scanResults) {
for (ScanResult scanResult : scanResults) {
- final String key = AccessPoint.getKey(scanResult);
+ final String key = AccessPoint.getKey(mContext, scanResult);
if (!mAccessPointKeys.contains(key)) {
mAccessPointKeys.add(key);
}
diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java
index 0aae89af41b..c924aaf80b7 100644
--- a/src/com/android/settings/wifi/WifiConfigController.java
+++ b/src/com/android/settings/wifi/WifiConfigController.java
@@ -583,35 +583,6 @@ public class WifiConfigController implements TextWatcher,
}
}
- /**
- * Special handling for WPA2/WPA3 and OWE in Transition mode: The key
- * SECURITY_PSK_SAE_TRANSITION and SECURITY_OWE_TRANSITION are pseudo keys which result by the
- * scan results, but never appears in the saved networks.
- * A saved network is either WPA3 for supporting devices or WPA2 for non-supporting devices,
- * or, OWE for supporting devices or Open for non-supporting devices.
- *
- * @param accessPointSecurity Access point current security type
- * @return Converted security type (if required)
- */
- private int convertSecurityTypeForMatching(int accessPointSecurity) {
- if (accessPointSecurity == AccessPoint.SECURITY_PSK_SAE_TRANSITION) {
- if (mWifiManager.isWpa3SaeSupported()) {
- return AccessPoint.SECURITY_SAE;
- } else {
- return AccessPoint.SECURITY_PSK;
- }
- }
- if (accessPointSecurity == AccessPoint.SECURITY_OWE_TRANSITION) {
- if (mWifiManager.isEnhancedOpenSupported()) {
- return AccessPoint.SECURITY_OWE;
- } else {
- return AccessPoint.SECURITY_NONE;
- }
- }
-
- return accessPointSecurity;
- }
-
public WifiConfiguration getConfig() {
if (mMode == WifiConfigUiBase.MODE_VIEW) {
return null;
@@ -634,8 +605,6 @@ public class WifiConfigController implements TextWatcher,
config.shared = mSharedCheckBox.isChecked();
- mAccessPointSecurity = convertSecurityTypeForMatching(mAccessPointSecurity);
-
switch (mAccessPointSecurity) {
case AccessPoint.SECURITY_NONE:
config.allowedKeyManagement.set(KeyMgmt.NONE);
@@ -960,8 +929,7 @@ public class WifiConfigController implements TextWatcher,
private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertificates) {
if (mAccessPointSecurity == AccessPoint.SECURITY_NONE ||
- mAccessPointSecurity == AccessPoint.SECURITY_OWE ||
- mAccessPointSecurity == AccessPoint.SECURITY_OWE_TRANSITION) {
+ mAccessPointSecurity == AccessPoint.SECURITY_OWE) {
mView.findViewById(R.id.security_fields).setVisibility(View.GONE);
return;
}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 8c4bfa28ad8..4d3f230db1e 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -494,9 +494,7 @@ public class WifiSettings extends RestrictedSettingsFragment
if (isSavedNetwork) {
connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
} else if ((mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) ||
- (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_OWE) ||
- (mSelectedAccessPoint.getSecurity()
- == AccessPoint.SECURITY_OWE_TRANSITION)) {
+ (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_OWE)) {
/** Bypass dialog for unsecured networks */
mSelectedAccessPoint.generateOpenNetworkConfig();
connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
@@ -750,8 +748,7 @@ public class WifiSettings extends RestrictedSettingsFragment
preference.setOrder(index);
if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
&& (accessPoint.getSecurity() != AccessPoint.SECURITY_NONE &&
- accessPoint.getSecurity() != AccessPoint.SECURITY_OWE &&
- accessPoint.getSecurity() != AccessPoint.SECURITY_OWE_TRANSITION)) {
+ accessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) {
if (!accessPoint.isSaved() || isDisabledByWrongPassword(accessPoint)) {
onPreferenceTreeClick(preference);
mOpenSsid = null;
diff --git a/src/com/android/settings/wifi/WifiUtils.java b/src/com/android/settings/wifi/WifiUtils.java
index c4df5676246..9b3c1b368c9 100644
--- a/src/com/android/settings/wifi/WifiUtils.java
+++ b/src/com/android/settings/wifi/WifiUtils.java
@@ -268,8 +268,7 @@ public class WifiUtils {
if (accessPoint.isOsuProvider()) {
return CONNECT_TYPE_OSU_PROVISION;
} else if ((accessPoint.getSecurity() == AccessPoint.SECURITY_NONE) ||
- (accessPoint.getSecurity() == AccessPoint.SECURITY_OWE) ||
- (accessPoint.getSecurity() == AccessPoint.SECURITY_OWE_TRANSITION)) {
+ (accessPoint.getSecurity() == AccessPoint.SECURITY_OWE)) {
return CONNECT_TYPE_OPEN_NETWORK;
} else if (accessPoint.isSaved() && config != null
&& config.getNetworkSelectionStatus() != null
diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
index 2a958e801e0..fc1bc25b763 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
@@ -206,7 +206,7 @@ public class WifiDppUtils {
setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);
// For a transition mode Wi-Fi AP, creates a QR code that's compatible with more devices
- if (accessPoint.getSecurity() == AccessPoint.SECURITY_PSK_SAE_TRANSITION) {
+ if (accessPoint.isPskSaeTransitionMode()) {
intent.putExtra(EXTRA_WIFI_SECURITY, WifiQrCode.SECURITY_WPA_PSK);
}
@@ -406,7 +406,6 @@ public class WifiDppUtils {
}
break;
case AccessPoint.SECURITY_PSK:
- case AccessPoint.SECURITY_PSK_SAE_TRANSITION:
return true;
default:
}
@@ -419,8 +418,6 @@ public class WifiDppUtils {
case AccessPoint.SECURITY_PSK:
case AccessPoint.SECURITY_WEP:
case AccessPoint.SECURITY_NONE:
- case AccessPoint.SECURITY_PSK_SAE_TRANSITION:
- case AccessPoint.SECURITY_OWE_TRANSITION:
return true;
case AccessPoint.SECURITY_SAE:
if (wifiManager.isWpa3SaeSupported()) {
diff --git a/tests/robotests/src/com/android/settings/display/DarkUIPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/DarkUIPreferenceControllerTest.java
index 3659803cc80..9924cb4b628 100644
--- a/tests/robotests/src/com/android/settings/display/DarkUIPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/DarkUIPreferenceControllerTest.java
@@ -16,25 +16,9 @@
package com.android.settings.display;
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-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.Context;
-import android.os.Handler;
-import android.os.IPowerManager;
-import android.os.PowerManager;
-
import androidx.fragment.app.Fragment;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
-
-import org.junit.Assert;
+import com.android.settings.display.darkmode.DarkModePreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,6 +27,10 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
@RunWith(RobolectricTestRunner.class)
public class DarkUIPreferenceControllerTest {
@@ -57,7 +45,7 @@ public class DarkUIPreferenceControllerTest {
mContext = spy(RuntimeEnvironment.application);
mController = spy(new DarkUIPreferenceController(mContext, "dark_ui_mode"));
mController.setParentFragment(mFragment);
- mController.mPreference = new SwitchPreference(mContext);
+ mController.mPreference = new DarkModePreference(mContext, null /* AttributeSet attrs */);
mController.onStart();
}
diff --git a/tests/robotests/src/com/android/settings/display/darkmode/DarkModeActivationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeActivationPreferenceControllerTest.java
new file mode 100644
index 00000000000..cd20ea230ab
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeActivationPreferenceControllerTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.view.View;
+import android.widget.Button;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
+import com.android.settingslib.widget.LayoutPreference;
+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 static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.eq;
+
+@RunWith(RobolectricTestRunner.class)
+public class DarkModeActivationPreferenceControllerTest {
+ private DarkModeActivationPreferenceController mController;
+ private String mPreferenceKey = "key";
+ @Mock
+ private LayoutPreference mPreference;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private Resources res;
+ @Mock
+ private Context mContext;
+ @Mock
+ private UiModeManager mService;
+ @Mock
+ private Button mTurnOffButton;
+ @Mock
+ private Button mTurnOnButton;
+ @Mock
+ private PowerManager mPM;
+
+ private Configuration configNightYes = new Configuration();
+ private Configuration configNightNo = new Configuration();;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mService = mock(UiModeManager.class);
+ when(mContext.getResources()).thenReturn(res);
+ when(mContext.getSystemService(UiModeManager.class)).thenReturn(mService);
+ when(mContext.getSystemService(PowerManager.class)).thenReturn(mPM);
+ when(mScreen.findPreference(anyString())).thenReturn(mPreference);
+ when(mPreference.findViewById(
+ eq(R.id.dark_ui_turn_on_button))).thenReturn(mTurnOnButton);
+ when(mPreference.findViewById(
+ eq(R.id.dark_ui_turn_off_button))).thenReturn(mTurnOffButton);
+ when(mService.setNightModeActivated(anyBoolean())).thenReturn(true);
+ when(mContext.getString(
+ R.string.dark_ui_activation_off_auto)).thenReturn("off_auto");
+ when(mContext.getString(
+ R.string.dark_ui_activation_on_auto)).thenReturn("on_auto");
+ when(mContext.getString(
+ R.string.dark_ui_activation_off_manual)).thenReturn("off_manual");
+ when(mContext.getString(
+ R.string.dark_ui_activation_on_manual)).thenReturn("on_manual");
+ when(mContext.getString(
+ R.string.dark_ui_summary_off_auto_mode_auto)).thenReturn("summary_off_auto");
+ when(mContext.getString(
+ R.string.dark_ui_summary_on_auto_mode_auto)).thenReturn("summary_on_auto");
+ when(mContext.getString(
+ R.string.dark_ui_summary_off_auto_mode_never)).thenReturn("summary_off_manual");
+ when(mContext.getString(
+ R.string.dark_ui_summary_on_auto_mode_never)).thenReturn("summary_on_manual");
+ mController = new DarkModeActivationPreferenceController(mContext, mPreferenceKey);
+ mController.displayPreference(mScreen);
+ configNightNo.uiMode = Configuration.UI_MODE_NIGHT_NO;
+ configNightYes.uiMode = Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ @Test
+ public void nightMode_toggleButton_offManual() {
+ when(mService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);
+ when(res.getConfiguration()).thenReturn(configNightYes);
+
+ mController.updateState(mPreference);
+
+ verify(mTurnOnButton).setVisibility(eq(View.GONE));
+ verify(mTurnOffButton).setVisibility(eq(View.VISIBLE));
+ verify(mTurnOffButton).setText(eq(mContext.getString(
+ R.string.dark_ui_activation_off_manual)));
+ }
+
+
+ @Test
+ public void nightMode_toggleButton_onAutoWhenModeIsYes() {
+ when(mService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);
+ when(res.getConfiguration()).thenReturn(configNightNo);
+
+ mController.updateState(mPreference);
+
+ verify(mTurnOffButton).setVisibility(eq(View.GONE));
+ verify(mTurnOnButton).setVisibility(eq(View.VISIBLE));
+ verify(mTurnOnButton).setText(eq(mContext.getString(
+ R.string.dark_ui_activation_on_manual)));
+ }
+
+ @Test
+ public void nightMode_toggleButton_onAutoWhenModeIsAuto() {
+ when(mService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_AUTO);
+ when(res.getConfiguration()).thenReturn(configNightNo);
+
+ mController.updateState(mPreference);
+
+ verify(mTurnOffButton).setVisibility(eq(View.GONE));
+ verify(mTurnOnButton).setVisibility(eq(View.VISIBLE));
+ verify(mTurnOnButton).setText(eq(mContext.getString(
+ R.string.dark_ui_activation_on_auto)));
+ }
+
+ @Test
+ public void nightModeSummary_buttonText_onManual() {
+ when(mService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_NO);
+ when(res.getConfiguration()).thenReturn(configNightYes);
+
+ assertEquals(mController.getSummary(), mContext.getString(
+ R.string.dark_ui_summary_on_auto_mode_never));
+ }
+
+ @Test
+ public void nightModeSummary_buttonText_offAuto() {
+ when(mService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_AUTO);
+ when(res.getConfiguration()).thenReturn(configNightNo);
+
+ assertEquals(mController.getSummary(), mContext.getString(
+ R.string.dark_ui_summary_off_auto_mode_auto));
+ }
+
+ @Test
+ public void buttonVisisbility_hideButton_offWhenInPowerSaveMode() {
+ when(mPM.isPowerSaveMode()).thenReturn(true);
+ mController.updateState(mPreference);
+ verify(mTurnOffButton).setVisibility(eq(View.GONE));
+ verify(mTurnOnButton).setVisibility(eq(View.GONE));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/display/darkmode/DarkModeObserverTest.java b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeObserverTest.java
new file mode 100644
index 00000000000..dfa8ba118e4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeObserverTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+
+
+@RunWith(RobolectricTestRunner.class)
+public class DarkModeObserverTest {
+ private Context mContext;
+ private ContentObserver mContentObserver;
+ private DarkModeObserver mDarkModeObserver;
+ private Runnable mCallback;
+ private Uri mUri = Settings.Secure.getUriFor(Settings.Secure.UI_NIGHT_MODE);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mDarkModeObserver = new DarkModeObserver(mContext);
+ mContentObserver = mDarkModeObserver.getContentObserver();
+ mCallback = mock(Runnable.class);
+ }
+
+ @Test
+ public void callbackTest_subscribedCallbackCalled() {
+ mDarkModeObserver.subscribe(mCallback);
+ mContentObserver.onChange(false, mUri);
+ Mockito.verify(mCallback, times(2)).run();
+ }
+
+ @Test
+ public void callbackTest_unsubscribedCallNotbackCalled() {
+ mDarkModeObserver.subscribe(mCallback);
+ mContentObserver.onChange(false, mUri);
+ mDarkModeObserver.unsubscribe();
+ mContentObserver.onChange(false, mUri);
+ Mockito.verify(mCallback, times(2)).run();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java
new file mode 100644
index 00000000000..53be42caddf
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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.display.darkmode;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.os.PowerManager;
+import androidx.preference.DropDownPreference;
+import androidx.preference.PreferenceScreen;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import com.android.settings.R;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+public class DarkModeScheduleSelectorControllerTest {
+ private DarkModeScheduleSelectorController mController;
+ private String mPreferenceKey = "key";
+ @Mock
+ private DropDownPreference mPreference;
+ @Mock
+ private PreferenceScreen mScreen;
+ private Context mContext;
+ @Mock
+ private UiModeManager mUiService;
+ @Mock
+ private PowerManager mPM;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getSystemService(UiModeManager.class)).thenReturn(mUiService);
+ when(mContext.getSystemService(PowerManager.class)).thenReturn(mPM);
+ when(mContext.getString(R.string.dark_ui_auto_mode_never)).thenReturn("never");
+ when(mContext.getString(R.string.dark_ui_auto_mode_auto)).thenReturn("auto");
+ mPreference = spy(new DropDownPreference(mContext));
+ when(mScreen.findPreference(anyString())).thenReturn(mPreference);
+ when(mUiService.setNightModeActivated(anyBoolean())).thenReturn(true);
+ mController = new DarkModeScheduleSelectorController(mContext, mPreferenceKey);
+ }
+
+ @Test
+ public void nightMode_preferenceChange_preferenceChangeTrueWhenChangedOnly() {
+ when(mUiService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);
+ mController.displayPreference(mScreen);
+ boolean changed = mController
+ .onPreferenceChange(mScreen, mContext.getString(R.string.dark_ui_auto_mode_auto));
+ assertTrue(changed);
+ changed = mController
+ .onPreferenceChange(mScreen, mContext.getString(R.string.dark_ui_auto_mode_auto));
+ assertFalse(changed);
+ }
+
+ @Test
+ public void nightMode_updateStateNone_dropDownValueChangedToNone() {
+ when(mUiService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);
+ mController.displayPreference(mScreen);
+ mController.updateState(mPreference);
+ verify(mPreference).setValue(mContext.getString(R.string.dark_ui_auto_mode_never));
+ }
+
+ @Test
+ public void nightMode_updateStateNone_dropDownValueChangedToAuto() {
+ when(mUiService.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_AUTO);
+ mController.displayPreference(mScreen);
+ mController.updateState(mPreference);
+ verify(mPreference).setValue(mContext.getString(R.string.dark_ui_auto_mode_auto));
+ }
+
+ @Test
+ public void batterySaver_dropDown_disabledSelector() {
+ when(mPM.isPowerSaveMode()).thenReturn(true);
+ mController.displayPreference(mScreen);
+ mController.updateState(mPreference);
+ verify(mPreference).setEnabled(eq(false));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
index 5be72744f5c..63e8a80ccec 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
@@ -85,7 +85,6 @@ public class BatteryHeaderPreferenceControllerTest {
private BatteryMeterView mBatteryMeterView;
private TextView mBatteryPercentText;
private TextView mSummary;
- private TextView mSummary2;
private LayoutPreference mBatteryLayoutPref;
private Intent mBatteryIntent;
private LifecycleOwner mLifecycleOwner;
@@ -102,7 +101,6 @@ public class BatteryHeaderPreferenceControllerTest {
mBatteryPercentText = new TextView(mContext);
mSummary = new TextView(mContext);
ShadowEntityHeaderController.setUseMock(mEntityHeaderController);
- mSummary2 = new TextView(mContext);
mBatteryIntent = new Intent();
mBatteryIntent.putExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL);
@@ -126,7 +124,6 @@ public class BatteryHeaderPreferenceControllerTest {
mController.mBatteryMeterView = mBatteryMeterView;
mController.mBatteryPercentText = mBatteryPercentText;
mController.mSummary1 = mSummary;
- mController.mSummary2 = mSummary2;
}
@After
@@ -190,7 +187,6 @@ public class BatteryHeaderPreferenceControllerTest {
@Test
public void quickUpdateHeaderPreference_onlyUpdateBatteryLevelAndChargingState() {
mSummary.setText(BATTERY_STATUS);
- mSummary2.setText(BATTERY_STATUS);
mController.quickUpdateHeaderPreference();
@@ -198,7 +194,6 @@ public class BatteryHeaderPreferenceControllerTest {
assertThat(mBatteryMeterView.getCharging()).isTrue();
assertThat(mBatteryPercentText.getText().toString()).isEqualTo("60 %");
assertThat(mSummary.getText()).isEqualTo(BATTERY_STATUS);
- assertThat(mSummary2.getText()).isEqualTo(BATTERY_STATUS);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
index 4d77bddc45f..7839e1af3d8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
@@ -265,20 +265,17 @@ public class PowerUsageSummaryTest {
}
}).when(mFeatureFactory.powerUsageFeatureProvider).getEnhancedEstimateDebugString(any());
- doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary2);
doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary1);
mFragment.onLongClick(new View(mRealContext));
TextView summary1 = mFragment.mBatteryLayoutPref.findViewById(R.id.summary1);
- TextView summary2 = mFragment.mBatteryLayoutPref.findViewById(R.id.summary2);
Robolectric.flushBackgroundThreadScheduler();
- assertThat(summary2.getText().toString()).contains(NEW_ML_EST_SUFFIX);
+ assertThat(summary1.getText().toString()).contains(NEW_ML_EST_SUFFIX);
assertThat(summary1.getText().toString()).contains(OLD_EST_SUFFIX);
}
@Test
public void debugMode() {
doReturn(true).when(mFeatureFactory.powerUsageFeatureProvider).isEstimateDebugEnabled();
- doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary2);
mFragment.restartBatteryInfoLoader();
ArgumentCaptor listener = ArgumentCaptor.forClass(
diff --git a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java
index dcb32c4c475..db12580e169 100644
--- a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java
@@ -16,20 +16,42 @@
package com.android.settings.homepage;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Build;
import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.settings.R;
+import com.android.settings.core.HideNonSystemOverlayMixin;
+import com.android.settings.homepage.contextualcards.slices.BatteryFixSliceTest;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public class SettingsHomepageActivityTest {
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
@Test
public void setHomepageContainerPaddingTop_shouldBeSetPaddingTop() {
@@ -55,4 +77,55 @@ public class SettingsHomepageActivityTest {
assertThat(frameLayout.getLayoutTransition()).isNotNull();
}
+
+ @Test
+ @Config(shadows = {
+ BatteryFixSliceTest.ShadowBatteryStatsHelperLoader.class,
+ BatteryFixSliceTest.ShadowBatteryTipLoader.class
+ })
+ public void onStart_isNotDebuggable_shouldHideSystemOverlay() {
+ ReflectionHelpers.setStaticField(Build.class, "IS_DEBUGGABLE", false);
+
+ final ActivityController activityController =
+ Robolectric.buildActivity(SettingsHomepageActivity.class).create();
+ final SettingsHomepageActivity activity = spy(activityController.get());
+ final Window window = mock(Window.class);
+ when(activity.getWindow()).thenReturn(window);
+ activity.getLifecycle().addObserver(new HideNonSystemOverlayMixin(activity));
+
+ activityController.start();
+
+ verify(window).addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ }
+
+ @Test
+ @Config(shadows = {
+ BatteryFixSliceTest.ShadowBatteryStatsHelperLoader.class,
+ BatteryFixSliceTest.ShadowBatteryTipLoader.class,
+ })
+ public void onStop_isNotDebuggable_shouldRemoveHideSystemOverlay() {
+ ReflectionHelpers.setStaticField(Build.class, "IS_DEBUGGABLE", false);
+
+ final ActivityController activityController =
+ Robolectric.buildActivity(SettingsHomepageActivity.class).create();
+ final SettingsHomepageActivity activity = spy(activityController.get());
+ final Window window = mock(Window.class);
+ when(activity.getWindow()).thenReturn(window);
+ activity.getLifecycle().addObserver(new HideNonSystemOverlayMixin(activity));
+
+ activityController.start();
+
+ verify(window).addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ when(window.getAttributes()).thenReturn(layoutParams);
+
+ activityController.stop();
+ final ArgumentCaptor paramCaptor = ArgumentCaptor.forClass(
+ WindowManager.LayoutParams.class);
+
+ verify(window).setAttributes(paramCaptor.capture());
+ assertThat(paramCaptor.getValue().privateFlags
+ & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(0);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSliceTest.java
new file mode 100644
index 00000000000..1af7b2bfacd
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSliceTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 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.homepage.contextualcards.slices;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.PowerManager;
+
+import androidx.slice.Slice;
+import androidx.slice.SliceMetadata;
+import androidx.slice.SliceProvider;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.settings.R;
+import com.android.settings.slices.CustomSliceRegistry;
+import com.android.settings.slices.SlicesFeatureProviderImpl;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class DarkThemeSliceTest {
+ @Mock
+ private BatteryManager mBatteryManager;
+ @Mock
+ private PowerManager mPowerManager;
+
+ private Context mContext;
+ private DarkThemeSlice mDarkThemeSlice;
+ private FakeFeatureFactory mFeatureFactory;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mFeatureFactory.slicesFeatureProvider = new SlicesFeatureProviderImpl();
+ mFeatureFactory.slicesFeatureProvider.newUiSession();
+ doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
+ when(mPowerManager.isPowerSaveMode()).thenReturn(false);
+
+ // Set-up specs for SliceMetadata.
+ SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+ mDarkThemeSlice = spy(new DarkThemeSlice(mContext));
+ mDarkThemeSlice.sKeepSliceShow = false;
+ }
+
+ @Test
+ public void getUri_shouldBeDarkThemeSliceUri() {
+ final Uri uri = mDarkThemeSlice.getUri();
+
+ assertThat(uri).isEqualTo(CustomSliceRegistry.DARK_THEME_SLICE_URI);
+ }
+
+ @Test
+ public void isAvailable_inDarkThemeMode_returnFalse() {
+ doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext);
+
+ assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAvailable_nonDarkThemeBatteryCapacityEq100_returnFalse() {
+ setBatteryCapacityLevel(100);
+
+ assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
+ }
+
+ @Test
+ public void isAvailable_nonDarkThemeBatteryCapacityLt50_returnTrue() {
+ setBatteryCapacityLevel(40);
+
+ assertThat(mDarkThemeSlice.isAvailable(mContext)).isTrue();
+ }
+
+ @Test
+ public void getSlice_batterySaver_returnErrorSlice() {
+ when(mPowerManager.isPowerSaveMode()).thenReturn(true);
+
+ final Slice mediaSlice = mDarkThemeSlice.getSlice();
+ final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
+ assertThat(metadata.isErrorSlice()).isTrue();
+ }
+
+ @Test
+ public void getSlice_notAvailable_returnErrorSlice() {
+ doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext);
+
+ final Slice mediaSlice = mDarkThemeSlice.getSlice();
+ final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
+ assertThat(metadata.isErrorSlice()).isTrue();
+ }
+
+ @Test
+ public void getSlice_newSession_notAvailable_returnErrorSlice() {
+ // previous displayed: yes
+ mDarkThemeSlice.sKeepSliceShow = true;
+ // Session: use original value + 1 to become a new session
+ mDarkThemeSlice.sActiveUiSession =
+ mFeatureFactory.slicesFeatureProvider.getUiSessionToken() + 1;
+
+ doReturn(true).when(mDarkThemeSlice).isDarkThemeMode(mContext);
+
+ final Slice mediaSlice = mDarkThemeSlice.getSlice();
+ final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
+ assertThat(metadata.isErrorSlice()).isTrue();
+ }
+
+ @Test
+ public void getSlice_previouslyDisplayed_isAvailable_returnSlice() {
+ mDarkThemeSlice.sActiveUiSession =
+ mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
+ mDarkThemeSlice.sKeepSliceShow = true;
+ setBatteryCapacityLevel(40);
+
+ assertThat(mDarkThemeSlice.getSlice()).isNotNull();
+ }
+
+ @Test
+ public void getSlice_isAvailable_returnSlice() {
+ setBatteryCapacityLevel(40);
+
+ assertThat(mDarkThemeSlice.getSlice()).isNotNull();
+ }
+
+ @Test
+ public void getSlice_isAvailable_showTitleSubtitle() {
+ setBatteryCapacityLevel(40);
+
+ final Slice slice = mDarkThemeSlice.getSlice();
+ final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
+ assertThat(metadata.getTitle()).isEqualTo(
+ mContext.getString(R.string.dark_theme_slice_title));
+ assertThat(metadata.getSubtitle()).isEqualTo(
+ mContext.getString(R.string.dark_theme_slice_subtitle));
+ }
+
+ private void setBatteryCapacityLevel(int power_level) {
+ doReturn(false).when(mDarkThemeSlice).isDarkThemeMode(mContext);
+ doReturn(mBatteryManager).when(mContext).getSystemService(BatteryManager.class);
+ when(mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY))
+ .thenReturn(power_level);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java
index 8ad21569401..a4b7aa551bb 100644
--- a/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java
+++ b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java
@@ -16,6 +16,8 @@
package com.android.settings.panel;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import static com.android.settings.panel.SettingsPanelActivity.KEY_MEDIA_PACKAGE_NAME;
import static com.android.settings.panel.SettingsPanelActivity.KEY_PANEL_TYPE_ARGUMENT;
@@ -28,17 +30,23 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.settings.SettingsEnums;
import android.content.Intent;
-import android.view.MotionEvent;
+import android.os.Build;
+import android.view.Window;
+import android.view.WindowManager;
+import com.android.settings.core.HideNonSystemOverlayMixin;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public class SettingsPanelActivityTest {
@@ -50,6 +58,7 @@ public class SettingsPanelActivityTest {
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
mSettingsPanelActivity = spy(
Robolectric.buildActivity(FakeSettingsPanelActivity.class).create().get());
@@ -87,4 +96,47 @@ public class SettingsPanelActivityTest {
assertThat(activity.mBundle.getString(KEY_PANEL_TYPE_ARGUMENT))
.isEqualTo("com.android.settings.panel.action.MEDIA_OUTPUT");
}
+
+ @Test
+ public void onStart_isNotDebuggable_shouldHideSystemOverlay() {
+ ReflectionHelpers.setStaticField(Build.class, "IS_DEBUGGABLE", false);
+
+ final ActivityController activityController =
+ Robolectric.buildActivity(SettingsPanelActivity.class).create();
+ final SettingsPanelActivity activity = spy(activityController.get());
+ final Window window = mock(Window.class);
+ when(activity.getWindow()).thenReturn(window);
+ activity.getLifecycle().addObserver(new HideNonSystemOverlayMixin(activity));
+
+ activityController.start();
+
+ verify(window).addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ }
+
+ @Test
+ public void onStop_isNotDebuggable_shouldRemoveHideSystemOverlay() {
+ ReflectionHelpers.setStaticField(Build.class, "IS_DEBUGGABLE", false);
+
+ final ActivityController activityController =
+ Robolectric.buildActivity(SettingsPanelActivity.class).create();
+ final SettingsPanelActivity activity = spy(activityController.get());
+ final Window window = mock(Window.class);
+ when(activity.getWindow()).thenReturn(window);
+ activity.getLifecycle().addObserver(new HideNonSystemOverlayMixin(activity));
+
+ activityController.start();
+
+ verify(window).addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ when(window.getAttributes()).thenReturn(layoutParams);
+
+ activityController.stop();
+ final ArgumentCaptor paramCaptor = ArgumentCaptor.forClass(
+ WindowManager.LayoutParams.class);
+
+ verify(window).setAttributes(paramCaptor.capture());
+ assertThat(paramCaptor.getValue().privateFlags
+ & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(0);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java
deleted file mode 100644
index 1335db5c5ed..00000000000
--- a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2019 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.privacy;
-
-import static android.Manifest.permission_group.CALENDAR;
-import static android.Manifest.permission_group.CAMERA;
-import static android.Manifest.permission_group.CONTACTS;
-import static android.Manifest.permission_group.LOCATION;
-import static android.Manifest.permission_group.MICROPHONE;
-import static android.Manifest.permission_group.PHONE;
-import static android.Manifest.permission_group.SMS;
-
-import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
-import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-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.Context;
-import android.content.pm.UserInfo;
-import android.os.UserManager;
-import android.permission.RuntimePermissionUsageInfo;
-import android.provider.DeviceConfig;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.preference.PreferenceScreen;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.settings.Utils;
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settings.testutils.shadow.ShadowDeviceConfig;
-import com.android.settings.testutils.shadow.ShadowPermissionControllerManager;
-import com.android.settings.testutils.shadow.ShadowUserManager;
-import com.android.settingslib.widget.BarChartInfo;
-import com.android.settingslib.widget.BarChartPreference;
-import com.android.settingslib.widget.BarViewInfo;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowAccessibilityManager;
-import org.robolectric.shadows.androidx.fragment.FragmentController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowDeviceConfig.class, ShadowUserManager.class,
- ShadowPermissionControllerManager.class})
-public class PermissionBarChartPreferenceControllerTest {
-
- @Mock
- private PreferenceScreen mScreen;
- @Mock
- private LockPatternUtils mLockPatternUtils;
-
- private PermissionBarChartPreferenceController mController;
- private BarChartPreference mPreference;
- private PrivacyDashboardFragment mFragment;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- final Context context = RuntimeEnvironment.application;
- final UserManager userManager = context.getSystemService(UserManager.class);
- final ShadowUserManager shadowUserManager = Shadow.extract(userManager);
- final ShadowAccessibilityManager accessibilityManager = Shadow.extract(
- AccessibilityManager.getInstance(context));
- accessibilityManager.setEnabledAccessibilityServiceList(new ArrayList<>());
- shadowUserManager.addProfile(new UserInfo(123, null, 0));
- when(FakeFeatureFactory.setupForTest().securityFeatureProvider.getLockPatternUtils(
- any(Context.class))).thenReturn(mLockPatternUtils);
-
- mController = spy(new PermissionBarChartPreferenceController(context, "test_key"));
- mFragment = spy(FragmentController.of(new PrivacyDashboardFragment())
- .create().start().get());
- mController.setFragment(mFragment);
- mPreference = spy(new BarChartPreference(context));
- when(mScreen.findPreference(mController.getPreferenceKey()))
- .thenReturn((BarChartPreference) mPreference);
- }
-
- @After
- public void tearDown() {
- ShadowDeviceConfig.reset();
- }
-
- @Test
- public void getAvailabilityStatus_permissionHubNotSet_shouldReturnUnsupported() {
- // We have not yet set the property to show the Permissions Hub.
- assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/privacy/PrivacyDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/privacy/PrivacyDashboardFragmentTest.java
deleted file mode 100644
index 80f3900e00d..00000000000
--- a/tests/robotests/src/com/android/settings/privacy/PrivacyDashboardFragmentTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2019 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.privacy;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.Bundle;
-import android.os.UserManager;
-import android.permission.PermissionControllerManager;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settings.testutils.shadow.ShadowPermissionControllerManager;
-import com.android.settings.testutils.shadow.ShadowUserManager;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.shadows.ShadowAccessibilityManager;
-import org.robolectric.shadows.androidx.fragment.FragmentController;
-
-import java.util.ArrayList;
-
-
-@RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowUserManager.class, ShadowPermissionControllerManager.class})
-public class PrivacyDashboardFragmentTest {
-
- @Mock
- private LockPatternUtils mLockPatternUtils;
- @Mock
- private PermissionControllerManager mPCM;
-
- private Context mContext;
- private PrivacyDashboardFragment mFragment;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
- final UserManager userManager = mContext.getSystemService(UserManager.class);
- final ShadowUserManager shadowUserManager = Shadow.extract(userManager);
- final ShadowAccessibilityManager accessibilityManager = Shadow.extract(
- AccessibilityManager.getInstance(mContext));
- accessibilityManager.setEnabledAccessibilityServiceList(new ArrayList<>());
- shadowUserManager.addProfile(new UserInfo(123, null, 0));
- when(FakeFeatureFactory.setupForTest().securityFeatureProvider.getLockPatternUtils(
- any(Context.class))).thenReturn(mLockPatternUtils);
- mFragment = spy(FragmentController.of(new PrivacyDashboardFragment())
- .create().start().get());
- }
-
- @Test
- public void onViewCreated_shouldSetActionBarShadowAnimation() {
- mFragment.onViewCreated(new View(mContext), new Bundle());
-
- assertThat(mFragment.getActivity().getActionBar().getElevation()).isEqualTo(0.f);
- }
-
- @Test
- public void onViewCreated_shouldInitLinearProgressBar() {
- mFragment.onViewCreated(new View(mContext), new Bundle());
-
- verify(mFragment).initLoadingBar();
- }
-
- @Test
- public void updateLinearProgressbar_isVisible_shouldShowProgressBar() {
- mFragment.setLoadingEnabled(true /* enabled */);
-
- assertThat(mFragment.mProgressHeader.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mFragment.mProgressAnimation.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void updateLinearProgressbar_isInVisible_shouldHideProgressBar() {
- mFragment.setLoadingEnabled(false /* enabled */);
-
- assertThat(mFragment.mProgressHeader.getVisibility()).isEqualTo(View.INVISIBLE);
- assertThat(mFragment.mProgressAnimation.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
index 2a12680d979..220209cbf2a 100644
--- a/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -24,6 +24,7 @@ import android.content.Context;
import com.android.settings.accounts.AccountFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.aware.AwareFeatureProvider;
+import com.android.settings.biometrics.face.FaceFeatureProvider;
import com.android.settings.bluetooth.BluetoothFeatureProvider;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
@@ -68,6 +69,7 @@ public class FakeFeatureFactory extends FeatureFactory {
public final ContextualCardFeatureProvider mContextualCardFeatureProvider;
public final BluetoothFeatureProvider mBluetoothFeatureProvider;
public final AwareFeatureProvider mAwareFeatureProvider;
+ public final FaceFeatureProvider mFaceFeatureProvider;
public PanelFeatureProvider panelFeatureProvider;
public SlicesFeatureProvider slicesFeatureProvider;
@@ -114,6 +116,7 @@ public class FakeFeatureFactory extends FeatureFactory {
panelFeatureProvider = mock(PanelFeatureProvider.class);
mBluetoothFeatureProvider = mock(BluetoothFeatureProvider.class);
mAwareFeatureProvider = mock(AwareFeatureProvider.class);
+ mFaceFeatureProvider = mock(FaceFeatureProvider.class);
}
@Override
@@ -215,4 +218,9 @@ public class FakeFeatureFactory extends FeatureFactory {
public AwareFeatureProvider getAwareFeatureProvider() {
return mAwareFeatureProvider;
}
+
+ @Override
+ public FaceFeatureProvider getFaceFeatureProvider() {
+ return mFaceFeatureProvider;
+ }
}