Snap for 11869550 from a24c7183d2 to 24Q3-release

Change-Id: I8c19ffeb13b2e3936e041c985887daf626eb39c5
This commit is contained in:
Android Build Coastguard Worker
2024-05-21 23:21:58 +00:00
71 changed files with 3605 additions and 310 deletions

View File

@@ -758,4 +758,8 @@
<string name="audio_streams_dialog_no_le_device_subtitle" product="default">To listen to an audio stream, first connect headphones that support LE Audio to this phone.</string> <string name="audio_streams_dialog_no_le_device_subtitle" product="default">To listen to an audio stream, first connect headphones that support LE Audio to this phone.</string>
<string name="audio_streams_dialog_no_le_device_subtitle" product="tablet">To listen to an audio stream, first connect headphones that support LE Audio to this tablet.</string> <string name="audio_streams_dialog_no_le_device_subtitle" product="tablet">To listen to an audio stream, first connect headphones that support LE Audio to this tablet.</string>
<string name="audio_streams_dialog_no_le_device_subtitle" product="device">To listen to an audio stream, first connect headphones that support LE Audio to this device.</string> <string name="audio_streams_dialog_no_le_device_subtitle" product="device">To listen to an audio stream, first connect headphones that support LE Audio to this device.</string>
<!-- Le audio streams unsupported device subtitle [CHAR LIMIT=NONE] -->
<string name="audio_streams_dialog_unsupported_device_subtitle" product="default">This phone doesn\'t support LE Audio, which is needed to listen to audio streams.</string>
<string name="audio_streams_dialog_unsupported_device_subtitle" product="tablet">This tablet doesn\'t support LE Audio, which is needed to listen to audio streams.</string>
<string name="audio_streams_dialog_unsupported_device_subtitle" product="device">This device doesn\'t support LE Audio, which is needed to listen to audio streams.</string>
</resources> </resources>

View File

@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<FrameLayout <ScrollView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -26,4 +26,4 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"/> android:layout_gravity="center"/>
</FrameLayout> </ScrollView>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="zen_mode_calls_settings_page"
settings:searchable="false"
android:title="@string/zen_mode_calls_title">
<PreferenceCategory
android:key="zen_mode_settings_category_calls"
android:title="@string/zen_mode_calls_header"
settings:allowDividerBelow="true">
</PreferenceCategory>
<!-- Repeat callers -->
<SwitchPreferenceCompat
android:key="zen_mode_repeat_callers"
android:title="@string/zen_mode_repeat_callers_title"
settings:allowDividerAbove="true"/>
</PreferenceScreen>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="zen_mode_messages_settings_page"
settings:searchable="false"
android:title="@string/zen_mode_messages_title" >
<PreferenceCategory
android:key="zen_mode_settings_category_messages"
android:title="@string/zen_mode_messages_header">
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/zen_category_exceptions" >
<!-- Alarms -->
<SwitchPreferenceCompat
android:key="modes_category_alarm"
android:title="@string/zen_mode_alarms"/>
<!-- Media -->
<SwitchPreferenceCompat
android:key="modes_category_media"
android:title="@string/zen_mode_media"
android:summary="@string/zen_mode_media_summary"/>
<!-- System -->
<SwitchPreferenceCompat
android:key="modes_category_system"
android:title="@string/zen_mode_system"
android:summary="@string/zen_mode_system_summary"/>
<!-- Reminders -->
<SwitchPreferenceCompat
android:key="modes_category_reminders"
android:title="@string/zen_mode_reminders"/>
<!-- Events -->
<SwitchPreferenceCompat
android:key="modes_category_events"
android:title="@string/zen_mode_events"/>
</PreferenceScreen>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2024 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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/zen_category_people" >
<!-- Calls & Messages -->
<PreferenceCategory
android:key="zen_mode_people_calls_messages_section"
android:title="@string/zen_mode_people_calls_messages_section_title">
<Preference
android:key="zen_mode_people_messages"
android:title="@string/zen_mode_messages_title"/>
<Preference
android:key="zen_mode_people_calls"
android:title="@string/zen_mode_calls_title"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -22,4 +22,12 @@
android:key="header" android:key="header"
android:layout="@layout/settings_entity_header" /> android:layout="@layout/settings_entity_header" />
<Preference
android:key="zen_mode_people"
android:title="@string/zen_category_people"/>
<Preference
android:key="zen_other_settings"
android:title="@string/zen_category_exceptions" />
</PreferenceScreen> </PreferenceScreen>

View File

@@ -34,7 +34,6 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnResume;
@@ -44,8 +43,8 @@ import com.android.settingslib.core.lifecycle.events.OnResume;
* feature, where the magnifier will not deactivate on Activity transitions; it will only zoom out * feature, where the magnifier will not deactivate on Activity transitions; it will only zoom out
* to 100%. * to 100%.
*/ */
public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceController public class MagnificationAlwaysOnPreferenceController extends
implements LifecycleObserver, OnResume, OnPause { MagnificationFeaturePreferenceController implements LifecycleObserver, OnResume, OnPause {
private static final String TAG = private static final String TAG =
MagnificationAlwaysOnPreferenceController.class.getSimpleName(); MagnificationAlwaysOnPreferenceController.class.getSimpleName();
@@ -89,7 +88,7 @@ public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceC
@Override @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
return AVAILABLE; return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
} }
@Override @Override

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2024 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.accessibility;
import android.content.Context;
import com.android.settings.core.TogglePreferenceController;
/**
* A base preference controller of magnification feature with common methods.
*/
public abstract class MagnificationFeaturePreferenceController extends TogglePreferenceController {
private boolean mInSetupWizard;
protected MagnificationFeaturePreferenceController(Context context,
String preferenceKey) {
super(context, preferenceKey);
}
protected final boolean isInSetupWizard() {
return mInSetupWizard;
}
protected final void setInSetupWizard(boolean inSetupWizard) {
mInSetupWizard = inSetupWizard;
}
}

View File

@@ -23,10 +23,10 @@ import android.content.Context;
import android.provider.Settings; import android.provider.Settings;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/** Controller that accesses and switches the preference status of following typing feature */ /** Controller that accesses and switches the preference status of following typing feature */
public class MagnificationFollowTypingPreferenceController extends TogglePreferenceController { public class MagnificationFollowTypingPreferenceController extends
MagnificationFeaturePreferenceController {
private static final String TAG = private static final String TAG =
MagnificationFollowTypingPreferenceController.class.getSimpleName(); MagnificationFollowTypingPreferenceController.class.getSimpleName();
@@ -38,7 +38,7 @@ public class MagnificationFollowTypingPreferenceController extends TogglePrefere
@Override @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
return AVAILABLE; return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
} }
@Override @Override

View File

@@ -23,12 +23,12 @@ import android.content.Context;
import android.provider.Settings; import android.provider.Settings;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/** /**
* Controller that accesses and switches the preference status of the magnification joystick feature * Controller that accesses and switches the preference status of the magnification joystick feature
*/ */
public class MagnificationJoystickPreferenceController extends TogglePreferenceController { public class MagnificationJoystickPreferenceController extends
MagnificationFeaturePreferenceController {
private static final String TAG = private static final String TAG =
MagnificationJoystickPreferenceController.class.getSimpleName(); MagnificationJoystickPreferenceController.class.getSimpleName();
@@ -40,7 +40,7 @@ public class MagnificationJoystickPreferenceController extends TogglePreferenceC
@Override @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
return AVAILABLE; return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
} }
@Override @Override

View File

@@ -35,13 +35,12 @@ import androidx.preference.TwoStatePreference;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.core.lifecycle.events.OnResume;
public class MagnificationOneFingerPanningPreferenceController public class MagnificationOneFingerPanningPreferenceController extends
extends TogglePreferenceController implements LifecycleObserver, OnResume, OnPause { MagnificationFeaturePreferenceController implements LifecycleObserver, OnResume, OnPause {
static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED; static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED;
private TwoStatePreference mSwitchPreference; private TwoStatePreference mSwitchPreference;
@@ -82,7 +81,7 @@ public class MagnificationOneFingerPanningPreferenceController
@Override @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
return AVAILABLE; return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
} }
@Override @Override

View File

@@ -15,5 +15,5 @@ menghanli@google.com #{LAST_RESORT_SUGGESTION}
cipson@google.com #{LAST_RESORT_SUGGESTION} cipson@google.com #{LAST_RESORT_SUGGESTION}
# Partner-team files # Partner-team files
per-file HapticFeedbackIntensityPreferenceController.java = file:platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS per-file *Haptic* = file:platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
per-file *Vibration* = file:platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS per-file *Vibrat* = file:platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS

View File

@@ -82,8 +82,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
private static final TextUtils.SimpleStringSplitter sStringColonSplitter = private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
protected TwoStatePreference mFollowingTypingSwitchPreference;
// TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util. // TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util.
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private CheckBox mSoftwareTypeCheckBox; private CheckBox mSoftwareTypeCheckBox;
@@ -92,10 +90,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Nullable private CheckBox mTwoFingerTripleTapTypeCheckBox; @Nullable private CheckBox mTwoFingerTripleTapTypeCheckBox;
private DialogCreatable mDialogDelegate; private DialogCreatable mDialogDelegate;
private boolean mInSetupWizard;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.accessibility_screen_magnification_title); getActivity().setTitle(R.string.accessibility_screen_magnification_title);
mInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
} }
@Override @Override
@@ -169,7 +170,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
.showAccessibilityGestureTutorialDialog(getPrefContext()); .showAccessibilityGestureTutorialDialog(getPrefContext());
case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT: case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
final CharSequence dialogTitle = getShortcutTitle(); final CharSequence dialogTitle = getShortcutTitle();
final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent()) final int dialogType = mInSetupWizard
? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW ? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW
: DialogType.EDIT_SHORTCUT_MAGNIFICATION; : DialogType.EDIT_SHORTCUT_MAGNIFICATION;
mDialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(), mDialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(),
@@ -240,17 +241,18 @@ public class ToggleScreenMagnificationPreferenceFragment extends
} }
private void addFollowTypingSetting(PreferenceCategory generalCategory) { private void addFollowTypingSetting(PreferenceCategory generalCategory) {
mFollowingTypingSwitchPreference = new SwitchPreferenceCompat(getPrefContext()); var followingTypingSwitchPreference = new SwitchPreferenceCompat(getPrefContext());
mFollowingTypingSwitchPreference.setTitle( followingTypingSwitchPreference.setTitle(
R.string.accessibility_screen_magnification_follow_typing_title); R.string.accessibility_screen_magnification_follow_typing_title);
mFollowingTypingSwitchPreference.setSummary( followingTypingSwitchPreference.setSummary(
R.string.accessibility_screen_magnification_follow_typing_summary); R.string.accessibility_screen_magnification_follow_typing_summary);
mFollowingTypingSwitchPreference.setKey( followingTypingSwitchPreference.setKey(
MagnificationFollowTypingPreferenceController.PREF_KEY); MagnificationFollowTypingPreferenceController.PREF_KEY);
generalCategory.addPreference(mFollowingTypingSwitchPreference); generalCategory.addPreference(followingTypingSwitchPreference);
var followTypingPreferenceController = new MagnificationFollowTypingPreferenceController( var followTypingPreferenceController = new MagnificationFollowTypingPreferenceController(
getContext(), MagnificationFollowTypingPreferenceController.PREF_KEY); getContext(), MagnificationFollowTypingPreferenceController.PREF_KEY);
followTypingPreferenceController.setInSetupWizard(mInSetupWizard);
followTypingPreferenceController.displayPreference(getPreferenceScreen()); followTypingPreferenceController.displayPreference(getPreferenceScreen());
addPreferenceController(followTypingPreferenceController); addPreferenceController(followTypingPreferenceController);
} }
@@ -282,6 +284,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController( var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController(
getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY); getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY);
alwaysOnPreferenceController.setInSetupWizard(mInSetupWizard);
getSettingsLifecycle().addObserver(alwaysOnPreferenceController); getSettingsLifecycle().addObserver(alwaysOnPreferenceController);
alwaysOnPreferenceController.displayPreference(getPreferenceScreen()); alwaysOnPreferenceController.displayPreference(getPreferenceScreen());
addPreferenceController(alwaysOnPreferenceController); addPreferenceController(alwaysOnPreferenceController);
@@ -301,6 +304,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
var oneFingerPanningPreferenceController = var oneFingerPanningPreferenceController =
new MagnificationOneFingerPanningPreferenceController(getContext()); new MagnificationOneFingerPanningPreferenceController(getContext());
oneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard);
getSettingsLifecycle().addObserver(oneFingerPanningPreferenceController); getSettingsLifecycle().addObserver(oneFingerPanningPreferenceController);
oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen()); oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen());
addPreferenceController(oneFingerPanningPreferenceController); addPreferenceController(oneFingerPanningPreferenceController);
@@ -329,6 +333,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
getContext(), getContext(),
MagnificationJoystickPreferenceController.PREF_KEY MagnificationJoystickPreferenceController.PREF_KEY
); );
joystickPreferenceController.setInSetupWizard(mInSetupWizard);
joystickPreferenceController.displayPreference(getPreferenceScreen()); joystickPreferenceController.displayPreference(getPreferenceScreen());
addPreferenceController(joystickPreferenceController); addPreferenceController(joystickPreferenceController);
} }

View File

@@ -72,10 +72,6 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard
if (mSettingsPreference != null) { if (mSettingsPreference != null) {
mSettingsPreference.setVisible(false); mSettingsPreference.setVisible(false);
} }
// Setting of following typing
if (mFollowingTypingSwitchPreference != null) {
mFollowingTypingSwitchPreference.setVisible(false);
}
} }
@Override @Override

View File

@@ -28,6 +28,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.ONLY_CONN
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.provider.Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS; import static android.provider.Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS;
import static android.provider.Settings.Global.CONNECTED_APPS_ALLOWED_PACKAGES;
import static android.provider.Settings.Global.CONNECTED_APPS_DISALLOWED_PACKAGES;
import android.Manifest; import android.Manifest;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
@@ -35,6 +37,7 @@ import android.app.ActionBar;
import android.app.AppOpsManager; import android.app.AppOpsManager;
import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.admin.flags.Flags;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -49,6 +52,7 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.provider.Settings;
import android.stats.devicepolicy.DevicePolicyEnums; import android.stats.devicepolicy.DevicePolicyEnums;
import android.util.IconDrawableFactory; import android.util.IconDrawableFactory;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -68,6 +72,10 @@ import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.widget.LayoutPreference; import com.android.settingslib.widget.LayoutPreference;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
public class InteractAcrossProfilesDetails extends AppInfoBase public class InteractAcrossProfilesDetails extends AppInfoBase
implements Preference.OnPreferenceClickListener { implements Preference.OnPreferenceClickListener {
@@ -381,6 +389,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
private void enableInteractAcrossProfiles(boolean newState) { private void enableInteractAcrossProfiles(boolean newState) {
mCrossProfileApps.setInteractAcrossProfilesAppOp( mCrossProfileApps.setInteractAcrossProfilesAppOp(
mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
setUserPreferenceForPackage(newState, mPackageName);
} }
private void handleInstallBannerClick() { private void handleInstallBannerClick() {
@@ -552,4 +561,40 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
} }
return ACTION_MANAGE_CROSS_PROFILE_ACCESS.equals(intent.getAction()); return ACTION_MANAGE_CROSS_PROFILE_ACCESS.equals(intent.getAction());
} }
private void setUserPreferenceForPackage(boolean enabled, String crossProfilePackage) {
if (!Flags.backupConnectedAppsSettings()) {
return;
}
String allowedPackagesString = Settings.Global.getString(getContentResolver(),
CONNECTED_APPS_ALLOWED_PACKAGES);
String disallowedPackagesString = Settings.Global.getString(getContentResolver(),
CONNECTED_APPS_DISALLOWED_PACKAGES);
Set<String> allowedPackagesSet = getSetFromString(allowedPackagesString);
Set<String> disallowedPackagesSet = getSetFromString(disallowedPackagesString);
if (enabled) {
allowedPackagesSet.add(crossProfilePackage);
disallowedPackagesSet.remove(crossProfilePackage);
} else {
allowedPackagesSet.remove(crossProfilePackage);
disallowedPackagesSet.add(crossProfilePackage);
}
Settings.Global.putString(getContentResolver(),
CONNECTED_APPS_ALLOWED_PACKAGES,
String.join(",", allowedPackagesSet));
Settings.Global.putString(getContentResolver(),
CONNECTED_APPS_DISALLOWED_PACKAGES,
String.join(",", disallowedPackagesSet));
}
private Set<String> getSetFromString(String packages) {
return Optional.ofNullable(packages)
.map(pkg -> Set.of(pkg.split(",")))
.orElse(Collections.emptySet());
}
} }

View File

@@ -14,38 +14,38 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.settings.biometrics.fingerprint2.lib.model package com.android.settings.biometrics.fingerprint2.data.model
/** /**
* A view model that describes the various stages of UDFPS Enrollment. This stages typically update * A view model that describes the various stages of UDFPS Enrollment. This stages typically update
* the enrollment UI in a major way, such as changing the lottie animation or changing the location * the enrollment UI in a major way, such as changing the lottie animation or changing the location
* of the where the user should press their fingerprint * of the where the user should press their fingerprint
*/ */
sealed class StageViewModel { sealed class EnrollStageModel {
/** Unknown stage */ /** Unknown stage */
data object Unknown : StageViewModel() data object Unknown : EnrollStageModel()
/** This is the stage that moves the fingerprint icon around during enrollment. */ /** This is the stage that moves the fingerprint icon around during enrollment. */
data object Guided : StageViewModel() data object Guided : EnrollStageModel()
/** The center stage is the initial stage of enrollment. */ /** The center stage is the initial stage of enrollment. */
data object Center : StageViewModel() data object Center : EnrollStageModel()
/** /**
* Fingerprint stage of enrollment. Typically there is some sort of indication that a user should * Fingerprint stage of enrollment. Typically there is some sort of indication that a user should
* be using their finger tip to enroll. * be using their finger tip to enroll.
*/ */
data object Fingertip : StageViewModel() data object Fingertip : EnrollStageModel()
/** /**
* Left edge stage of enrollment. Typically there is an indication that a user should be using the * Left edge stage of enrollment. Typically there is an indication that a user should be using the
* left edge of their fingerprint. * left edge of their fingerprint.
*/ */
data object LeftEdge : StageViewModel() data object LeftEdge : EnrollStageModel()
/** /**
* Right edge stage of enrollment. Typically there is an indication that a user should be using * Right edge stage of enrollment. Typically there is an indication that a user should be using
* the right edge of their fingerprint. * the right edge of their fingerprint.
*/ */
data object RightEdge : StageViewModel() data object RightEdge : EnrollStageModel()
} }

View File

@@ -16,11 +16,11 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor package com.android.settings.biometrics.fingerprint2.domain.interactor
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
typealias EnrollStageThresholds = Map<Float, StageViewModel> typealias EnrollStageThresholds = Map<Float, EnrollStageModel>
/** Interactor that provides enroll stages for enrollment. */ /** Interactor that provides enroll stages for enrollment. */
interface EnrollStageInteractor { interface EnrollStageInteractor {
@@ -33,11 +33,11 @@ class EnrollStageInteractorImpl() : EnrollStageInteractor {
override val enrollStageThresholds: Flow<EnrollStageThresholds> = override val enrollStageThresholds: Flow<EnrollStageThresholds> =
flowOf( flowOf(
mapOf( mapOf(
0.0f to StageViewModel.Center, 0.0f to EnrollStageModel.Center,
0.25f to StageViewModel.Guided, 0.25f to EnrollStageModel.Guided,
0.5f to StageViewModel.Fingertip, 0.5f to EnrollStageModel.Fingertip,
0.75f to StageViewModel.LeftEdge, 0.75f to EnrollStageModel.LeftEdge,
0.875f to StageViewModel.RightEdge, 0.875f to EnrollStageModel.RightEdge,
) )
) )
} }

View File

@@ -42,6 +42,7 @@ interface OrientationInteractor {
* A flow that contains the rotation info matched against the def [config_reverseDefaultRotation] * A flow that contains the rotation info matched against the def [config_reverseDefaultRotation]
*/ */
val rotationFromDefault: Flow<Int> val rotationFromDefault: Flow<Int>
/** /**
* A Helper function that computes rotation if device is in * A Helper function that computes rotation if device is in
* [R.bool.config_reverseDefaultConfigRotation] * [R.bool.config_reverseDefaultConfigRotation]

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2024 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.biometrics.fingerprint2.domain.interactor
import android.graphics.PointF
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
/**
* This interactor provides information about the current offset of the sensor for guided enrollment
* on UDFPS devices.
*/
interface UdfpsEnrollInteractor {
/** Indicates at which step a UDFPS enrollment is in. */
fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int)
/** Indicates if guided enrollment should be enabled or not. */
fun updateGuidedEnrollment(enabled: Boolean)
/**
* A flow indicating how much the sensor image drawable should be offset for guided enrollment. A
* null point indicates that the icon should be in its default position.
*/
val guidedEnrollmentOffset: Flow<PointF>
}
/** Keeps track of which guided enrollment point we should be using */
class UdfpsEnrollInteractorImpl(
pixelsPerMillimeter: Float,
accessibilityInteractor: AccessibilityInteractor,
) : UdfpsEnrollInteractor {
private var isGuidedEnrollment = MutableStateFlow(false)
// Number of pixels per mm
val px = pixelsPerMillimeter
private val guidedEnrollmentPoints: MutableList<PointF> =
mutableListOf(
PointF(2.00f * px, 0.00f * px),
PointF(0.87f * px, -2.70f * px),
PointF(-1.80f * px, -1.31f * px),
PointF(-1.80f * px, 1.31f * px),
PointF(0.88f * px, 2.70f * px),
PointF(3.94f * px, -1.06f * px),
PointF(2.90f * px, -4.14f * px),
PointF(-0.52f * px, -5.95f * px),
PointF(-3.33f * px, -3.33f * px),
PointF(-3.99f * px, -0.35f * px),
PointF(-3.62f * px, 2.54f * px),
PointF(-1.49f * px, 5.57f * px),
PointF(2.29f * px, 4.92f * px),
PointF(3.82f * px, 1.78f * px),
)
override fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int) {
val index = (totalStep - stepsRemaining) % guidedEnrollmentPoints.size
_guidedEnrollment.update { guidedEnrollmentPoints[index] }
}
override fun updateGuidedEnrollment(enabled: Boolean) {
isGuidedEnrollment.update { enabled }
}
private val _guidedEnrollment = MutableStateFlow(PointF(0f, 0f))
override val guidedEnrollmentOffset: Flow<PointF> =
combine(
_guidedEnrollment,
accessibilityInteractor.isAccessibilityEnabled,
isGuidedEnrollment,
) { point, accessibilityEnabled, guidedEnrollmentEnabled ->
if (accessibilityEnabled || !guidedEnrollmentEnabled) {
return@combine PointF(0f, 0f)
} else {
return@combine PointF(point.x * SCALE, point.y * SCALE)
}
}
companion object {
private const val SCALE = 0.5f
}
}

View File

@@ -24,6 +24,7 @@ import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle import android.os.Bundle
import android.os.Vibrator import android.os.Vibrator
import android.util.Log import android.util.Log
import android.util.TypedValue
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@@ -54,6 +55,8 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateI
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.model.Default import com.android.settings.biometrics.fingerprint2.lib.model.Default
@@ -89,6 +92,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Fing
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintScrollViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.GatekeeperInfo
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Transition
import com.android.settings.flags.Flags import com.android.settings.flags.Flags
import com.android.settings.password.ChooseLockGeneric import com.android.settings.password.ChooseLockGeneric
import com.android.settings.password.ChooseLockSettingsHelper import com.android.settings.password.ChooseLockSettingsHelper
@@ -116,6 +120,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var foldStateInteractor: FoldStateInteractor private lateinit var foldStateInteractor: FoldStateInteractor
private lateinit var orientationInteractor: OrientationInteractor private lateinit var orientationInteractor: OrientationInteractor
private lateinit var displayDensityInteractor: DisplayDensityInteractor private lateinit var displayDensityInteractor: DisplayDensityInteractor
private lateinit var udfpsEnrollInteractor: UdfpsEnrollInteractor
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
private lateinit var backgroundViewModel: BackgroundViewModel private lateinit var backgroundViewModel: BackgroundViewModel
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
@@ -256,6 +261,15 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
fingerprintManager, fingerprintManager,
Settings, Settings,
) )
val accessibilityInteractor =
AccessibilityInteractorImpl(
getSystemService(AccessibilityManager::class.java)!!,
lifecycleScope,
)
val pixelsPerMillimeter =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, context.resources.displayMetrics)
udfpsEnrollInteractor = UdfpsEnrollInteractorImpl(pixelsPerMillimeter, accessibilityInteractor)
val fingerprintManagerInteractor = val fingerprintManagerInteractor =
FingerprintManagerInteractorImpl( FingerprintManagerInteractorImpl(
@@ -273,12 +287,6 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo
val accessibilityInteractor =
AccessibilityInteractorImpl(
getSystemService(AccessibilityManager::class.java)!!,
lifecycleScope,
)
navigationViewModel = navigationViewModel =
ViewModelProvider( ViewModelProvider(
this, this,
@@ -384,6 +392,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
orientationInteractor, orientationInteractor,
backgroundViewModel, backgroundViewModel,
fingerprintSensorRepo, fingerprintSensorRepo,
udfpsEnrollInteractor,
), ),
)[UdfpsViewModel::class.java] )[UdfpsViewModel::class.java]
@@ -435,17 +444,17 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
else -> FingerprintEnrollEnrollingV2Fragment() else -> FingerprintEnrollEnrollingV2Fragment()
} }
} }
Introduction -> FingerprintEnrollIntroV2Fragment() is Introduction -> FingerprintEnrollIntroV2Fragment()
else -> null else -> null
} }
if (theClass != null) { if (theClass != null) {
supportFragmentManager.fragments.onEach { fragment ->
supportFragmentManager.beginTransaction().remove(fragment).commit()
}
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
.setCustomAnimations(
step.enterTransition.toAnimation(),
step.exitTransition.toAnimation(),
)
.setReorderingAllowed(true) .setReorderingAllowed(true)
.add(R.id.fragment_container_view, theClass::class.java, null) .add(R.id.fragment_container_view, theClass::class.java, null)
.commit() .commit()
@@ -512,3 +521,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
} }
} }
} }
private fun Transition.toAnimation(): Int {
return when (this) {
Transition.EnterFromLeft -> com.google.android.setupdesign.R.anim.sud_slide_back_in
Transition.EnterFromRight -> com.google.android.setupdesign.R.anim.sud_slide_next_in
Transition.ExitToLeft -> com.google.android.setupdesign.R.anim.sud_slide_next_out
Transition.ExitToRight -> com.google.android.setupdesign.R.anim.sud_slide_back_out
}
}

View File

@@ -32,12 +32,12 @@ import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieCompositionFactory import com.airbnb.lottie.LottieCompositionFactory
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.common.widget.FingerprintErrorDialog
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.DescriptionText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.HeaderText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2 import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
@@ -83,6 +83,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
window.statusBarColor = color window.statusBarColor = color
view.setBackgroundColor(color) view.setBackgroundColor(color)
udfpsEnrollView.setFinishAnimationCompleted { viewModel.finishedSuccessfully() }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) { repeatOnLifecycle(Lifecycle.State.RESUMED) {
launch { launch {
@@ -159,7 +161,14 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
} }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewModel.enrollStage.collect { udfpsEnrollView.updateStage(it) } viewModel.guidedEnrollment.collect {
glifLayout.post { udfpsEnrollView.updateGuidedEnrollment(it) }
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.guidedEnrollmentSaved.collect {
glifLayout.post { udfpsEnrollView.onGuidedPointSaved(it) }
}
} }
} }
} }
@@ -175,35 +184,35 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
} }
private fun HeaderText.toResource(): Int { private fun HeaderText.toResource(): Int {
return when (this.stageViewModel) { return when (this.enrollStageModel) {
StageViewModel.Center, EnrollStageModel.Center,
StageViewModel.Guided, EnrollStageModel.Guided,
StageViewModel.Fingertip, EnrollStageModel.Fingertip,
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
StageViewModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title EnrollStageModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
StageViewModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title EnrollStageModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
} }
} }
private fun DescriptionText.toResource(): Int? { private fun DescriptionText.toResource(): Int? {
return when (this.stageViewModel) { return when (this.enrollStageModel) {
StageViewModel.Center, EnrollStageModel.Center,
StageViewModel.Guided, EnrollStageModel.Guided,
StageViewModel.Fingertip, EnrollStageModel.Fingertip,
StageViewModel.LeftEdge, EnrollStageModel.LeftEdge,
StageViewModel.RightEdge -> null EnrollStageModel.RightEdge -> null
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_start_message EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
} }
} }
private fun EducationAnimationModel.toResource(): Int? { private fun EducationAnimationModel.toResource(): Int? {
return when (this.stageViewModel) { return when (this.enrollStageModel) {
StageViewModel.Center, EnrollStageModel.Center,
StageViewModel.Guided -> R.raw.udfps_center_hint_lottie EnrollStageModel.Guided -> R.raw.udfps_center_hint_lottie
StageViewModel.Fingertip -> R.raw.udfps_tip_hint_lottie EnrollStageModel.Fingertip -> R.raw.udfps_tip_hint_lottie
StageViewModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie EnrollStageModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
StageViewModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie EnrollStageModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
StageViewModel.Unknown -> null EnrollStageModel.Unknown -> null
} }
} }

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
/** Represents the description text for UDFPS enrollment */ /** Represents the description text for UDFPS enrollment */
data class DescriptionText( data class DescriptionText(
val isSuw: Boolean, val isSuw: Boolean,
val isAccessibility: Boolean, val isAccessibility: Boolean,
val stageViewModel: StageViewModel, val enrollStageModel: EnrollStageModel,
) )

View File

@@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
/** Represents the header text for UDFPS enrollment */ /** Represents the header text for UDFPS enrollment */
data class HeaderText( data class HeaderText(
val isSuw: Boolean, val isSuw: Boolean,
val isAccessibility: Boolean, val isAccessibility: Boolean,
val stageViewModel: StageViewModel, val enrollStageModel: EnrollStageModel,
) )

View File

@@ -16,11 +16,11 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
/** Represents the lottie for UDFPS enrollment */ /** Represents the lottie for UDFPS enrollment */
data class EducationAnimationModel( data class EducationAnimationModel(
val isSuw: Boolean, val isSuw: Boolean,
val isAccessibility: Boolean, val isAccessibility: Boolean,
val stageViewModel: StageViewModel, val enrollStageModel: EnrollStageModel,
) )

View File

@@ -17,20 +17,24 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
import android.graphics.Point import android.graphics.Point
import android.graphics.PointF
import android.view.Surface import android.view.Surface
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
@@ -61,9 +65,11 @@ class UdfpsViewModel(
orientationInteractor: OrientationInteractor, orientationInteractor: OrientationInteractor,
backgroundViewModel: BackgroundViewModel, backgroundViewModel: BackgroundViewModel,
sensorRepository: FingerprintSensorRepository, sensorRepository: FingerprintSensorRepository,
udfpsEnrollInteractor: UdfpsEnrollInteractor,
) : ViewModel() { ) : ViewModel() {
private val isSetupWizard = flowOf(false) private val isSetupWizard = flowOf(false)
private var shouldResetErollment = false
private var _enrollState: Flow<FingerEnrollState?> = private var _enrollState: Flow<FingerEnrollState?> =
fingerprintEnrollEnrollingViewModel.enrollFlow fingerprintEnrollEnrollingViewModel.enrollFlow
@@ -112,6 +118,17 @@ class UdfpsViewModel(
} }
} }
/**
* This indicates at which point the UI should offset the fingerprint sensor icon for guided
* enrollment.
*/
val guidedEnrollment: Flow<PointF> =
udfpsEnrollInteractor.guidedEnrollmentOffset.distinctUntilChanged()
/** The saved version of [guidedEnrollment] */
val guidedEnrollmentSaved: Flow<PointF> =
guidedEnrollment.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
/** /**
* This is the saved progress, this is for when views are recreated and need saved state for the * This is the saved progress, this is for when views are recreated and need saved state for the
* first time. * first time.
@@ -132,13 +149,13 @@ class UdfpsViewModel(
} }
} }
/** Determines the current [StageViewModel] enrollment is in */ /** Determines the current [EnrollStageModel] enrollment is in */
val enrollStage: Flow<StageViewModel> = private val enrollStage: Flow<EnrollStageModel> =
combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event -> combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event ->
if (event is FingerEnrollState.EnrollProgress) { if (event is FingerEnrollState.EnrollProgress) {
val progress = val progress =
(event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired (event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired
var stageToReturn: StageViewModel = StageViewModel.Center var stageToReturn: EnrollStageModel = EnrollStageModel.Center
thresholds.forEach { (threshold, stage) -> thresholds.forEach { (threshold, stage) ->
if (progress < threshold) { if (progress < threshold) {
return@forEach return@forEach
@@ -153,6 +170,40 @@ class UdfpsViewModel(
.filterNotNull() .filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
init {
viewModelScope.launch {
enrollState
.combine(accessibilityEnabled) { event, isEnabled -> Pair(event, isEnabled) }
.collect {
if (
when (it.first) {
is FingerEnrollState.EnrollError -> true
is FingerEnrollState.EnrollHelp -> it.second
is FingerEnrollState.EnrollProgress -> true
else -> false
}
) {
vibrate(it.first)
}
}
}
viewModelScope.launch {
enrollStage.collect {
udfpsEnrollInteractor.updateGuidedEnrollment(it is EnrollStageModel.Guided)
}
}
viewModelScope.launch {
enrollState.filterIsInstance<FingerEnrollState.EnrollProgress>().collect {
udfpsEnrollInteractor.onEnrollmentStep(it.remainingSteps, it.totalStepsRequired)
}
}
viewModelScope.launch {
backgroundViewModel.background.filter { true }.collect { didGoToBackground() }
}
}
/** Indicates if we should show the lottie. */ /** Indicates if we should show the lottie. */
val shouldShowLottie: Flow<Boolean> = val shouldShowLottie: Flow<Boolean> =
combine( combine(
@@ -183,7 +234,7 @@ class UdfpsViewModel(
} }
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
private val shouldClearDescriptionText = enrollStage.map { it is StageViewModel.Unknown } private val shouldClearDescriptionText = enrollStage.map { it is EnrollStageModel.Unknown }
/** The description text for UDFPS enrollment */ /** The description text for UDFPS enrollment */
val descriptionText: Flow<DescriptionText?> = val descriptionText: Flow<DescriptionText?> =
@@ -202,6 +253,10 @@ class UdfpsViewModel(
/** Indicates if the consumer is ready for enrollment */ /** Indicates if the consumer is ready for enrollment */
fun readyForEnrollment() { fun readyForEnrollment() {
if (shouldResetErollment) {
shouldResetErollment = false
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
}
fingerprintEnrollEnrollingViewModel.canEnroll() fingerprintEnrollEnrollingViewModel.canEnroll()
} }
@@ -237,8 +292,12 @@ class UdfpsViewModel(
} }
private fun doReset() { private fun doReset() {
/** Indicates if the icon should be animating or not */
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow _enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
progressSaved =
enrollState
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
} }
/** The lottie that should be shown for UDFPS Enrollment */ /** The lottie that should be shown for UDFPS Enrollment */
@@ -272,6 +331,7 @@ class UdfpsViewModel(
private val orientationInteractor: OrientationInteractor, private val orientationInteractor: OrientationInteractor,
private val backgroundViewModel: BackgroundViewModel, private val backgroundViewModel: BackgroundViewModel,
private val sensorRepository: FingerprintSensorRepository, private val sensorRepository: FingerprintSensorRepository,
private val udfpsEnrollInteractor: UdfpsEnrollInteractor,
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -287,6 +347,7 @@ class UdfpsViewModel(
orientationInteractor, orientationInteractor,
backgroundViewModel, backgroundViewModel,
sensorRepository, sensorRepository,
udfpsEnrollInteractor,
) )
as T as T
} }

View File

@@ -1,89 +0,0 @@
/*
* Copyright (C) 2024 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.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget
import android.content.Context
import android.graphics.PointF
import android.util.TypedValue
import android.view.accessibility.AccessibilityManager
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
/** Keeps track of which guided enrollment point we should be using */
class UdfpsEnrollHelperV2(private val mContext: Context) {
private var isGuidedEnrollment: Boolean = false
private val accessibilityEnabled: Boolean
private val guidedEnrollmentPoints: MutableList<PointF>
/** The current index of [guidedEnrollmentPoints] for the guided enrollment. */
private var index = 0
init {
val am = mContext.getSystemService(AccessibilityManager::class.java)
accessibilityEnabled = am!!.isEnabled
guidedEnrollmentPoints = ArrayList()
// Number of pixels per mm
val px =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, mContext.resources.displayMetrics)
guidedEnrollmentPoints.add(PointF(2.00f * px, 0.00f * px))
guidedEnrollmentPoints.add(PointF(0.87f * px, -2.70f * px))
guidedEnrollmentPoints.add(PointF(-1.80f * px, -1.31f * px))
guidedEnrollmentPoints.add(PointF(-1.80f * px, 1.31f * px))
guidedEnrollmentPoints.add(PointF(0.88f * px, 2.70f * px))
guidedEnrollmentPoints.add(PointF(3.94f * px, -1.06f * px))
guidedEnrollmentPoints.add(PointF(2.90f * px, -4.14f * px))
guidedEnrollmentPoints.add(PointF(-0.52f * px, -5.95f * px))
guidedEnrollmentPoints.add(PointF(-3.33f * px, -3.33f * px))
guidedEnrollmentPoints.add(PointF(-3.99f * px, -0.35f * px))
guidedEnrollmentPoints.add(PointF(-3.62f * px, 2.54f * px))
guidedEnrollmentPoints.add(PointF(-1.49f * px, 5.57f * px))
guidedEnrollmentPoints.add(PointF(2.29f * px, 4.92f * px))
guidedEnrollmentPoints.add(PointF(3.82f * px, 1.78f * px))
}
/**
* This indicates whether we should be offsetting the enrollment icon based on
* [guidedEnrollmentPoints]
*/
fun onUpdateStage(stage: StageViewModel) {
this.isGuidedEnrollment = stage is StageViewModel.Guided
}
/** Updates [index] to be used by [guidedEnrollmentPoints] */
fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
index = totalSteps - remaining
}
/**
* Returns the current guided enrollment point, or (0,0) if we are not in guided enrollment or are
* in accessibility.
*/
val guidedEnrollmentLocation: PointF?
get() {
if (accessibilityEnabled || !isGuidedEnrollment) {
return null
}
val scale = SCALE
val originalPoint = guidedEnrollmentPoints[index % guidedEnrollmentPoints.size]
return PointF(originalPoint.x * scale, originalPoint.y * scale)
}
companion object {
private const val TAG = "UdfpsEnrollHelperV2"
private const val SCALE = 0.5f
}
}

View File

@@ -24,6 +24,7 @@ import android.graphics.Canvas
import android.graphics.ColorFilter import android.graphics.ColorFilter
import android.graphics.Paint import android.graphics.Paint
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@@ -37,7 +38,6 @@ import androidx.core.animation.addListener
import androidx.core.graphics.toRect import androidx.core.graphics.toRect
import androidx.core.graphics.toRectF import androidx.core.graphics.toRectF
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
import kotlin.math.sin import kotlin.math.sin
/** /**
@@ -51,11 +51,11 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
private val fingerprintDrawable: ShapeDrawable private val fingerprintDrawable: ShapeDrawable
private val sensorOutlinePaint: Paint private val sensorOutlinePaint: Paint
private val blueFill: Paint private val blueFill: Paint
private val helper = UdfpsEnrollHelperV2(context)
@ColorInt private var enrollIconColor = 0 @ColorInt private var enrollIconColor = 0
@ColorInt private var movingTargetFill = 0 @ColorInt private var movingTargetFill = 0
private var currentScale = 1.0f private var currentScale = 1.0f
private var alpha = 0 private var alpha = 0
private var guidedEnrollmentOffset: PointF? = null
/** /**
* This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt] * This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt]
@@ -143,45 +143,6 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
invalidateSelf() invalidateSelf()
} }
/** Update the progress of the icon */
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
restoreAnimationTime()
// If we are restoring this view from a saved state, set animation duration to 0 to avoid
// animating progress that has already occurred.
if (isRecreating) {
setAnimationTimeToZero()
} else {
restoreAnimationTime()
}
helper.onEnrollmentProgress(remaining, totalSteps)
val offset = helper.guidedEnrollmentLocation
val currentBounds = getCurrLocation().toRect()
if (offset != null) {
// This is the desired location of the sensor rect, the [EnrollHelper]
// offsets the initial sensor rect by a bit to get the user to move their finger a bit more.
val targetRect = Rect(sensorRectBounds).toRectF()
targetRect.offset(offset.x, offset.y)
val shouldAnimateMovement =
!currentBounds.equals(targetRect) && offset.x != 0f && offset.y != 0f
if (shouldAnimateMovement) {
targetAnimatorSet?.cancel()
animateMovement(currentBounds, targetRect, true)
}
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
invalidateSelf()
}
/** Update the stage of the icon */
fun updateStage(it: StageViewModel) {
helper.onUpdateStage(it)
invalidateSelf()
}
/** Stop drawing the fingerprint icon. */ /** Stop drawing the fingerprint icon. */
fun stopDrawing() { fun stopDrawing() {
alpha = 0 alpha = 0
@@ -211,6 +172,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
if (currentBounds.equals(offsetRect)) { if (currentBounds.equals(offsetRect)) {
return return
} }
val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left) val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left)
xAnimator.addUpdateListener { xAnimator.addUpdateListener {
currX = it.animatedValue as Float currX = it.animatedValue as Float
@@ -260,6 +222,40 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
targetAnimationDuration = TARGET_ANIM_DURATION_LONG targetAnimationDuration = TARGET_ANIM_DURATION_LONG
} }
/**
* Indicates a change to guided enrollment has occurred. Also indicates if we are recreating the
* view, in which case their is no need to animate the icon to whatever position it was in.
*/
fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) {
guidedEnrollmentOffset = point
if (isRecreating) {
setAnimationTimeToZero()
} else {
restoreAnimationTime()
}
val currentBounds = getCurrLocation().toRect()
val offset = guidedEnrollmentOffset
if (offset?.x != 0f && offset?.y != 0f) {
val targetRect = Rect(sensorRectBounds).toRectF()
// This is the desired location of the sensor rect, the [EnrollHelper]
// offsets the initial sensor rect by a bit to get the user to move their finger a bit more.
targetRect.offset(offset!!.x, offset!!.y)
val shouldAnimateMovement = !currentBounds.equals(targetRect)
if (shouldAnimateMovement) {
targetAnimatorSet?.cancel()
animateMovement(currentBounds, targetRect, true)
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
invalidateSelf()
}
companion object { companion object {
private const val TAG = "UdfpsEnrollDrawableV2" private const val TAG = "UdfpsEnrollDrawableV2"
private const val DEFAULT_STROKE_WIDTH = 3f private const val DEFAULT_STROKE_WIDTH = 3f

View File

@@ -27,10 +27,12 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator import android.view.animation.Interpolator
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.animation.addListener
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.graphics.toRectF import androidx.core.graphics.toRectF
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
@@ -46,6 +48,7 @@ import kotlin.math.sin
class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) : class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) :
Drawable() { Drawable() {
private val sensorRect: Rect = Rect() private val sensorRect: Rect = Rect()
private var onFinishedCompletionAnimation: (() -> Unit)? = null
private var rotation: Int = 0 private var rotation: Int = 0
private val strokeWidthPx: Float private val strokeWidthPx: Float
@@ -287,6 +290,12 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
checkMarkDrawable.bounds = newBounds checkMarkDrawable.bounds = newBounds
checkMarkDrawable.setVisible(true, false) checkMarkDrawable.setVisible(true, false)
} }
doOnEnd {
onFinishedCompletionAnimation?.let{
it()
}
}
start() start()
} }
} }
@@ -380,6 +389,13 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS
} }
/**
* Indicates that the finish animation has completed, and enrollment can proceed to the next stage
*/
fun setFinishAnimationCompleted(onFinishedAnimation: () -> Unit) {
this.onFinishedCompletionAnimation = onFinishedAnimation
}
companion object { companion object {
private const val TAG = "UdfpsProgressBar" private const val TAG = "UdfpsProgressBar"
private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L

View File

@@ -18,6 +18,7 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol
import android.content.Context import android.content.Context
import android.graphics.Point import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect import android.graphics.Rect
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log import android.util.Log
@@ -31,7 +32,6 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import com.android.settings.R import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -53,6 +53,13 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
private val udfpsUtils: UdfpsUtils = UdfpsUtils() private val udfpsUtils: UdfpsUtils = UdfpsUtils()
private lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer private lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer
private var isRecreating = false private var isRecreating = false
private var onFinishedCompletionAnimation: (() -> Unit)? = null
init {
fingerprintProgressDrawable.setFinishAnimationCompleted {
onFinishedCompletionAnimation?.let { it() }
}
}
/** /**
* This function computes the center (x,y) location with respect to the parent [FrameLayout] for * This function computes the center (x,y) location with respect to the parent [FrameLayout] for
@@ -112,11 +119,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils) touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils)
} }
/** Updates the current enrollment stage. */
fun updateStage(it: StageViewModel) {
fingerprintIcon.updateStage(it)
}
/** Receive enroll progress event */ /** Receive enroll progress event */
fun onUdfpsEvent(event: FingerEnrollState) { fun onUdfpsEvent(event: FingerEnrollState) {
when (event) { when (event) {
@@ -174,7 +176,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
/** Receive enroll progress event */ /** Receive enroll progress event */
private fun onEnrollmentProgress(remaining: Int, totalSteps: Int) { private fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
fingerprintIcon.onEnrollmentProgress(remaining, totalSteps)
fingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps) fingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps)
} }
@@ -241,10 +242,25 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
/** Indicates we should should restore the views saved state. */ /** Indicates we should should restore the views saved state. */
fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) { fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) {
fingerprintIcon.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true) fingerprintProgressDrawable.onEnrollmentProgress(it.remainingSteps, it.totalStepsRequired, true)
} }
/** Indicates we are recreating the UI from a saved state. */
fun onGuidedPointSaved(it: PointF) {
fingerprintIcon.updateGuidedEnrollment(it, true)
}
/**
* Indicates that the finish animation has completed, and enrollment can proceed to the next stage
*/
fun setFinishAnimationCompleted(onFinishedAnimation: () -> Unit) {
this.onFinishedCompletionAnimation = onFinishedAnimation
}
fun updateGuidedEnrollment(point: PointF) {
fingerprintIcon.updateGuidedEnrollment(point, false)
}
companion object { companion object {
private const val TAG = "UdfpsEnrollView" private const val TAG = "UdfpsEnrollView"
} }

View File

@@ -88,7 +88,10 @@ sealed interface FingerprintNavigationStep {
} }
/** UiSteps should have a 1 to 1 mapping between each screen of FingerprintEnrollment */ /** UiSteps should have a 1 to 1 mapping between each screen of FingerprintEnrollment */
sealed class UiStep : FingerprintNavigationStep sealed class UiStep(
val enterTransition: Transition = Transition.EnterFromRight,
val exitTransition: Transition = Transition.ExitToLeft,
) : FingerprintNavigationStep
/** This is the landing page for enrollment, where no content is shown. */ /** This is the landing page for enrollment, where no content is shown. */
data object Init : UiStep() { data object Init : UiStep() {
@@ -103,7 +106,7 @@ sealed interface FingerprintNavigationStep {
} else if (state.flowType is FastEnroll) { } else if (state.flowType is FastEnroll) {
TransitionStep(Enrollment(state.fingerprintSensor!!)) TransitionStep(Enrollment(state.fingerprintSensor!!))
} else { } else {
TransitionStep(Introduction) TransitionStep(Introduction())
} }
} }
else -> null else -> null
@@ -118,7 +121,7 @@ sealed interface FingerprintNavigationStep {
action: FingerprintAction, action: FingerprintAction,
): FingerprintNavigationStep? { ): FingerprintNavigationStep? {
return when (action) { return when (action) {
FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction) FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction())
FingerprintAction.CONFIRM_DEVICE_FAIL -> Finish(null) FingerprintAction.CONFIRM_DEVICE_FAIL -> Finish(null)
else -> null else -> null
} }
@@ -126,7 +129,10 @@ sealed interface FingerprintNavigationStep {
} }
/** Indicates the FingerprintIntroduction screen is being presented to the user */ /** Indicates the FingerprintIntroduction screen is being presented to the user */
data object Introduction : UiStep() { class Introduction(
enterTransition: Transition = Transition.EnterFromRight,
exitTransition: Transition = Transition.ExitToLeft,
) : UiStep(enterTransition, exitTransition) {
override fun update( override fun update(
state: NavigationState, state: NavigationState,
action: FingerprintAction, action: FingerprintAction,
@@ -141,7 +147,11 @@ sealed interface FingerprintNavigationStep {
} }
/** Indicates the FingerprintEducation screen is being presented to the user */ /** Indicates the FingerprintEducation screen is being presented to the user */
data class Education(val sensor: FingerprintSensor) : UiStep() { class Education(
val sensor: FingerprintSensor,
enterTransition: Transition = Transition.EnterFromRight,
exitTransition: Transition = Transition.ExitToLeft,
) : UiStep(enterTransition, exitTransition) {
override fun update( override fun update(
state: NavigationState, state: NavigationState,
action: FingerprintAction, action: FingerprintAction,
@@ -149,7 +159,8 @@ sealed interface FingerprintNavigationStep {
return when (action) { return when (action) {
FingerprintAction.NEXT -> TransitionStep(Enrollment(state.fingerprintSensor!!)) FingerprintAction.NEXT -> TransitionStep(Enrollment(state.fingerprintSensor!!))
FingerprintAction.NEGATIVE_BUTTON_PRESSED, FingerprintAction.NEGATIVE_BUTTON_PRESSED,
FingerprintAction.PREV -> TransitionStep(Introduction) FingerprintAction.PREV ->
TransitionStep(Introduction(Transition.EnterFromLeft, Transition.ExitToRight))
else -> null else -> null
} }
} }
@@ -179,7 +190,10 @@ sealed interface FingerprintNavigationStep {
): FingerprintNavigationStep? { ): FingerprintNavigationStep? {
return when (action) { return when (action) {
FingerprintAction.NEXT -> Finish(null) FingerprintAction.NEXT -> Finish(null)
FingerprintAction.PREV -> TransitionStep(Education(state.fingerprintSensor!!)) FingerprintAction.PREV ->
TransitionStep(
Education(state.fingerprintSensor!!, Transition.EnterFromLeft, Transition.ExitToRight)
)
FingerprintAction.ADD_ANOTHER -> TransitionStep(Enrollment(state.fingerprintSensor!!)) FingerprintAction.ADD_ANOTHER -> TransitionStep(Enrollment(state.fingerprintSensor!!))
else -> null else -> null
} }

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2024 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.biometrics.fingerprint2.ui.enrollment.viewmodel
/** Indicates the type of transitions that can occur between fragments */
sealed class Transition {
/**
* Indicates the new fragment should slide in from the left side
*/
data object EnterFromLeft : Transition()
/**
* Indicates the new fragment should slide in from the right side
*/
data object EnterFromRight : Transition()
/**
* Indicates the old fragment should slide out to the left side
*/
data object ExitToLeft : Transition()
/**
* Indicates the old fragment should slide out to the right side
*/
data object ExitToRight : Transition()
}

View File

@@ -54,9 +54,6 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (!AudioSharingUtils.isFeatureEnabled()) {
return;
}
setShowsDialog(true); setShowsDialog(true);
mActivity = getActivity(); mActivity = getActivity();
if (mActivity == null) { if (mActivity == null) {
@@ -84,6 +81,9 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
if (!AudioSharingUtils.isFeatureEnabled()) {
return getUnsupporteDialog();
}
if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) { if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
CachedBluetoothDevice connectedLeDevice = CachedBluetoothDevice connectedLeDevice =
AudioStreamsHelper.getCachedBluetoothDeviceInSharingOrLeConnected( AudioStreamsHelper.getCachedBluetoothDeviceInSharingOrLeConnected(
@@ -137,6 +137,21 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
.build(); .build();
} }
private Dialog getUnsupporteDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
.setTitle(getString(R.string.audio_streams_dialog_cannot_listen))
.setSubTitle2(getString(R.string.audio_streams_dialog_unsupported_device_subtitle))
.setRightButtonText(getString(R.string.audio_streams_dialog_close))
.setRightButtonOnClickListener(
unused -> {
dismiss();
if (mActivity != null) {
mActivity.finish();
}
})
.build();
}
private Dialog getErrorDialog(String name) { private Dialog getErrorDialog(String name) {
return new AudioStreamsDialogFragment.DialogBuilder(getActivity()) return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
.setTitle(getString(R.string.audio_streams_dialog_cannot_listen)) .setTitle(getString(R.string.audio_streams_dialog_cannot_listen))

View File

@@ -19,16 +19,12 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.os.Bundle; import android.os.Bundle;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
public class AudioStreamConfirmDialogActivity extends SettingsActivity { public class AudioStreamConfirmDialogActivity extends SettingsActivity {
@Override @Override
protected void onCreate(Bundle savedState) { protected void onCreate(Bundle savedState) {
super.onCreate(savedState); super.onCreate(savedState);
if (!AudioSharingUtils.isFeatureEnabled()) {
finish();
}
} }
@Override @Override

View File

@@ -44,6 +44,8 @@ public class PowerGaugePreference extends AppPreference {
private static final float UNSELECTABLE_ALPHA_LIGHT_MODE = 0.65f; private static final float UNSELECTABLE_ALPHA_LIGHT_MODE = 0.65f;
private static final float UNSELECTABLE_ALPHA_DARK_MODE = 0.65f; private static final float UNSELECTABLE_ALPHA_DARK_MODE = 0.65f;
private final int mTitleColorNormal;
private BatteryEntry mInfo; private BatteryEntry mInfo;
private BatteryDiffEntry mBatteryDiffEntry; private BatteryDiffEntry mBatteryDiffEntry;
private CharSequence mContentDescription; private CharSequence mContentDescription;
@@ -78,6 +80,8 @@ public class PowerGaugePreference extends AppPreference {
mInfo = info; mInfo = info;
mContentDescription = contentDescription; mContentDescription = contentDescription;
mShowAnomalyIcon = false; mShowAnomalyIcon = false;
mTitleColorNormal =
Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
} }
/** Sets the content description. */ /** Sets the content description. */
@@ -155,6 +159,13 @@ public class PowerGaugePreference extends AppPreference {
final TextView titleView = (TextView) view.findViewById(android.R.id.title); final TextView titleView = (TextView) view.findViewById(android.R.id.title);
titleView.setContentDescription(mContentDescription); titleView.setContentDescription(mContentDescription);
} }
if (!isSelectable()) {
// Set colors consistently to meet contrast requirements for non-selectable items
((TextView) view.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal);
((TextView) view.findViewById(android.R.id.summary)).setTextColor(mTitleColorNormal);
subtitle.setTextColor(mTitleColorNormal);
}
} }
private static void setViewAlpha(View view, float alpha) { private static void setViewAlpha(View view, float alpha) {

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 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.notification.modes;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* DND Calls Settings page to determine which priority senders can bypass DND when this mode is
* activated.
*/
public class ZenModeCallsFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new ZenModePrioritySendersPreferenceController(context,
"zen_mode_settings_category_calls", false, mBackend));
controllers.add(new ZenModeRepeatCallersPreferenceController(context,
"zen_mode_repeat_callers", mBackend,
context.getResources().getInteger(com.android.internal.R.integer
.config_zen_repeat_callers_threshold)));
return controllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_calls_settings;
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_CALLS;
}
@Override
public void onResume() {
super.onResume();
use(ZenModePrioritySendersPreferenceController.class).onResume();
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
private ZenModeSummaryHelper mSummaryHelper;
public ZenModeCallsLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(context, backend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeCallsFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getCallsSettingSummary(getMode()));
}
}

View File

@@ -20,9 +20,6 @@ import android.app.AutomaticZenRule;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.AbstractPreferenceController;
@@ -42,6 +39,10 @@ public class ZenModeFragment extends ZenModeFragmentBase {
// {@link AbstractZenModePreferenceController}. // {@link AbstractZenModePreferenceController}.
List<AbstractPreferenceController> prefControllers = new ArrayList<>(); List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend)); prefControllers.add(new ZenModeHeaderController(context, "header", this, mBackend));
prefControllers.add(new ZenModePeopleLinkPreferenceController(
context, "zen_mode_people", mBackend));
prefControllers.add(new ZenModeOtherLinkPreferenceController(
context, "zen_other_settings", mBackend));
return prefControllers; return prefControllers;
} }

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2024 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.notification.modes;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* DND Messages Settings page to determine which priority senders can bypass DND.
* "Messages" include SMS, MMS, and messaging apps.
*/
public class ZenModeMessagesFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new ZenModePrioritySendersPreferenceController(context,
"zen_mode_settings_category_messages", true, mBackend));
return controllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_messages_settings;
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_MESSAGES;
}
@Override
public void onResume() {
super.onResume();
use(ZenModePrioritySendersPreferenceController.class).onResume();
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
public class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
public ZenModeMessagesLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(context, backend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeMessagesFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setEnabled(true);
preference.setSummary(mSummaryHelper.getMessagesSettingSummary(getMode().getPolicy()));
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Mode > Alarms & Other Interruptions
*/
public class ZenModeOtherFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new ZenModeOtherPreferenceController(
context, "modes_category_alarm", mBackend));
controllers.add(new ZenModeOtherPreferenceController(
context, "modes_category_media", mBackend));
controllers.add(new ZenModeOtherPreferenceController(
context, "modes_category_system", mBackend));
controllers.add(new ZenModeOtherPreferenceController(
context, "modes_category_reminders", mBackend));
controllers.add(new ZenModeOtherPreferenceController(
context, "modes_category_events", mBackend));
return controllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_other_settings;
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.NOTIFICATION_ZEN_MODE_PRIORITY;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
/**
* Preference with a link and summary about what other sounds can break through the mode
*/
public class ZenModeOtherLinkPreferenceController extends AbstractZenModePreferenceController {
ZenModeSummaryHelper mSummaryHelper;
public ZenModeOtherLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeOtherFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getOtherSoundCategoriesSummary(getMode()));
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.content.Context;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
public class ZenModeOtherPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
public ZenModeOtherPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
TwoStatePreference pref = (TwoStatePreference) preference;
pref.setChecked(getMode().getPolicy().isCategoryAllowed(getCategory(), true));
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean allow = (Boolean) newValue;
ZenPolicy diffPolicy = new ZenPolicy.Builder()
.allowCategory(getCategory(), allow)
.build();
getMode().setPolicy(diffPolicy);
mBackend.updateMode(getMode());
return true;
}
private int getCategory() {
switch (getPreferenceKey()) {
case "modes_category_alarm":
return PRIORITY_CATEGORY_ALARMS;
case "modes_category_media":
return PRIORITY_CATEGORY_MEDIA;
case "modes_category_system":
return PRIORITY_CATEGORY_SYSTEM;
case "modes_category_reminders":
return PRIORITY_CATEGORY_REMINDERS;
case "modes_category_events":
return PRIORITY_CATEGORY_EVENTS;
}
return -1;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 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.notification.modes;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/**
* Settings page that shows what calls and messages will break through the mode and links to the
* configuration pages for both.
*/
public class ZenModePeopleFragment extends ZenModeFragmentBase {
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
prefControllers.add(new ZenModeCallsLinkPreferenceController(
context, "zen_mode_people_calls", mBackend));
prefControllers.add(new ZenModeMessagesLinkPreferenceController(
context, "zen_mode_people_messages", mBackend));
return prefControllers;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_people_settings;
}
@Override
public int getMetricsCategory() {
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.DND_PEOPLE;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
import android.content.Context;
import android.os.Bundle;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
/**
* Preference with a link and summary about what calls and messages can break through the mode
*/
public class ZenModePeopleLinkPreferenceController extends AbstractZenModePreferenceController {
ZenModeSummaryHelper mSummaryHelper;
public ZenModePeopleLinkPreferenceController(Context context, String key,
ZenModesBackend backend) {
super(context, key, backend);
mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
Bundle bundle = new Bundle();
bundle.putString(MODE_ID, getMode().getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModePeopleFragment.class.getName())
.setSourceMetricsCategory(0)
.setArguments(bundle)
.toIntent());
preference.setSummary(mSummaryHelper.getPeopleSummary(getMode()));
}
}

View File

@@ -0,0 +1,447 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_UNSET;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_UNSET;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.icu.text.MessageFormat;
import android.provider.Contacts;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.ZenPolicy;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.app.ConversationListSettings;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Common preference controller functionality for zen mode priority senders preferences for both
* messages and calls.
*
* These controllers handle the settings regarding which priority senders that are allowed to
* bypass DND for calls or messages, which may be one of the following values: starred contacts, all
* contacts, priority conversations (for messages only), anyone, or no one.
*/
public class ZenModePrioritySendersPreferenceController
extends AbstractZenModePreferenceController {
private final boolean mIsMessages; // if this is false, then this preference is for calls
static final String KEY_ANY = "senders_anyone";
static final String KEY_CONTACTS = "senders_contacts";
static final String KEY_STARRED = "senders_starred_contacts";
static final String KEY_IMPORTANT = "conversations_important";
static final String KEY_NONE = "senders_none";
private int mNumImportantConversations = CONVERSATION_SENDERS_UNSET;
private static final Intent ALL_CONTACTS_INTENT =
new Intent(Contacts.Intents.UI.LIST_DEFAULT)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
private static final Intent STARRED_CONTACTS_INTENT =
new Intent(Contacts.Intents.UI.LIST_STARRED_ACTION)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
private static final Intent FALLBACK_INTENT = new Intent(Intent.ACTION_MAIN)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
private final PackageManager mPackageManager;
private PreferenceCategory mPreferenceCategory;
private List<SelectorWithWidgetPreference> mSelectorPreferences = new ArrayList<>();
private final ZenModeSummaryHelper mZenModeSummaryHelper;
public ZenModePrioritySendersPreferenceController(Context context, String key,
boolean isMessages, ZenModesBackend backend) {
super(context, key, backend);
mIsMessages = isMessages;
String contactsPackage = context.getString(R.string.config_contacts_package_name);
ALL_CONTACTS_INTENT.setPackage(contactsPackage);
STARRED_CONTACTS_INTENT.setPackage(contactsPackage);
FALLBACK_INTENT.setPackage(contactsPackage);
mPackageManager = mContext.getPackageManager();
if (!FALLBACK_INTENT.hasCategory(Intent.CATEGORY_APP_CONTACTS)) {
FALLBACK_INTENT.addCategory(Intent.CATEGORY_APP_CONTACTS);
}
mZenModeSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceCategory = screen.findPreference(getPreferenceKey());
if (mPreferenceCategory.getPreferenceCount() == 0) {
makeSelectorPreference(KEY_STARRED,
com.android.settings.R.string.zen_mode_from_starred, mIsMessages);
makeSelectorPreference(KEY_CONTACTS,
com.android.settings.R.string.zen_mode_from_contacts, mIsMessages);
if (mIsMessages) {
makeSelectorPreference(KEY_IMPORTANT,
com.android.settings.R.string.zen_mode_from_important_conversations, true);
}
makeSelectorPreference(KEY_ANY,
com.android.settings.R.string.zen_mode_from_anyone, mIsMessages);
makeSelectorPreference(KEY_NONE,
com.android.settings.R.string.zen_mode_none_messages, mIsMessages);
}
super.displayPreference(screen);
}
@Override
public void updateState(Preference preference) {
if (mIsMessages) {
updateChannelCounts();
}
final int currContactsSetting = getPrioritySenders();
final int currConversationsSetting = getPriorityConversationSenders();
for (SelectorWithWidgetPreference pref : mSelectorPreferences) {
// for each preference, check whether the current state matches what this state
// would look like if the button were checked.
final int[] checkedState = keyToSettingEndState(pref.getKey(), true);
final int checkedContactsSetting = checkedState[0];
final int checkedConversationsSetting = checkedState[1];
boolean match = checkedContactsSetting == currContactsSetting;
if (mIsMessages && checkedConversationsSetting != CONVERSATION_SENDERS_UNSET) {
// "CONVERSATION_SENDERS_UNSET" in checkedContactsSetting means this preference
// doesn't govern the priority senders setting, so the full match happens when
// either the priority senders setting matches or if it's CONVERSATION_SENDERS_UNSET
// so only the conversation setting needs to match.
match = (match || checkedContactsSetting == PEOPLE_TYPE_UNSET)
&& (checkedConversationsSetting == currConversationsSetting);
}
pref.setChecked(match);
}
updateSummaries();
}
public void onResume() {
if (mIsMessages) {
updateChannelCounts();
}
updateSummaries();
}
private void updateChannelCounts() {
ParceledListSlice<ConversationChannelWrapper> impConversations =
mBackend.getConversations(true);
int numImportantConversations = 0;
if (impConversations != null) {
for (ConversationChannelWrapper conversation : impConversations.getList()) {
if (!conversation.getNotificationChannel().isDemoted()) {
numImportantConversations++;
}
}
}
mNumImportantConversations = numImportantConversations;
}
private int getPrioritySenders() {
if (mIsMessages) {
return getMode().getPolicy().getPriorityMessageSenders();
} else {
return getMode().getPolicy().getPriorityCallSenders();
}
}
private int getPriorityConversationSenders() {
if (mIsMessages) {
return getMode().getPolicy().getPriorityConversationSenders();
}
return CONVERSATION_SENDERS_UNSET;
}
private SelectorWithWidgetPreference makeSelectorPreference(String key, int titleId,
boolean isCheckbox) {
final SelectorWithWidgetPreference pref =
new SelectorWithWidgetPreference(mPreferenceCategory.getContext(), isCheckbox);
pref.setKey(key);
pref.setTitle(titleId);
pref.setOnClickListener(mSelectorClickListener);
View.OnClickListener widgetClickListener = getWidgetClickListener(key);
if (widgetClickListener != null) {
pref.setExtraWidgetOnClickListener(widgetClickListener);
}
mPreferenceCategory.addPreference(pref);
mSelectorPreferences.add(pref);
return pref;
}
private View.OnClickListener getWidgetClickListener(String key) {
if (!KEY_CONTACTS.equals(key) && !KEY_STARRED.equals(key) && !KEY_IMPORTANT.equals(key)) {
return null;
}
if (KEY_STARRED.equals(key) && !isStarredIntentValid()) {
return null;
}
if (KEY_CONTACTS.equals(key) && !isContactsIntentValid()) {
return null;
}
return v -> {
if (KEY_STARRED.equals(key)
&& STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) {
mContext.startActivity(STARRED_CONTACTS_INTENT);
} else if (KEY_CONTACTS.equals(key)
&& ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) {
mContext.startActivity(ALL_CONTACTS_INTENT);
} else if (KEY_IMPORTANT.equals(key)) {
new SubSettingLauncher(mContext)
.setDestination(ConversationListSettings.class.getName())
.setSourceMetricsCategory(SettingsEnums.DND_CONVERSATIONS)
.launch();
} else {
mContext.startActivity(FALLBACK_INTENT);
}
};
}
private boolean isStarredIntentValid() {
return STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null
|| FALLBACK_INTENT.resolveActivity(mPackageManager) != null;
}
private boolean isContactsIntentValid() {
return ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null
|| FALLBACK_INTENT.resolveActivity(mPackageManager) != null;
}
void updateSummaries() {
for (SelectorWithWidgetPreference pref : mSelectorPreferences) {
pref.setSummary(getSummary(pref.getKey()));
}
}
// Gets the desired end state of the priority senders and conversations for the given key
// and whether it is being checked or unchecked. [type]_UNSET indicates no change in state.
//
// Returns an integer array with 2 entries. The first entry is the setting for priority senders
// and the second entry is for priority conversation senders; if isMessages is false, then
// no changes will ever be prescribed for conversation senders.
int[] keyToSettingEndState(String key, boolean checked) {
int[] endState = new int[]{ PEOPLE_TYPE_UNSET, CONVERSATION_SENDERS_UNSET };
if (!checked) {
// Unchecking any priority-senders-based state should reset the state to NONE.
// "Unchecking" the NONE state doesn't do anything, in practice.
switch (key) {
case KEY_STARRED:
case KEY_CONTACTS:
case KEY_ANY:
case KEY_NONE:
endState[0] = PEOPLE_TYPE_NONE;
}
// For messages, unchecking "priority conversations" and "any" should reset conversation
// state to "NONE" as well.
if (mIsMessages) {
switch (key) {
case KEY_IMPORTANT:
case KEY_ANY:
case KEY_NONE:
endState[1] = CONVERSATION_SENDERS_NONE;
}
}
} else {
// All below is for the enabling (checked) state.
switch (key) {
case KEY_STARRED:
endState[0] = PEOPLE_TYPE_STARRED;
break;
case KEY_CONTACTS:
endState[0] = PEOPLE_TYPE_CONTACTS;
break;
case KEY_ANY:
endState[0] = PEOPLE_TYPE_ANYONE;
break;
case KEY_NONE:
endState[0] = PEOPLE_TYPE_NONE;
}
// In the messages case *only*, also handle changing of conversation settings.
if (mIsMessages) {
switch (key) {
case KEY_IMPORTANT:
endState[1] = CONVERSATION_SENDERS_IMPORTANT;
break;
case KEY_ANY:
endState[1] = CONVERSATION_SENDERS_ANYONE;
break;
case KEY_NONE:
endState[1] = CONVERSATION_SENDERS_NONE;
}
}
}
// Error case check: if somehow, after all of that, endState is still
// {PEOPLE_TYPE_UNSET, CONVERSATION_SENDERS_UNSET}, something has gone wrong.
if (endState[0] == PEOPLE_TYPE_UNSET && endState[1] == CONVERSATION_SENDERS_UNSET) {
throw new IllegalArgumentException("invalid key " + key);
}
return endState;
}
// Returns the preferences, if any, that should be newly saved for the specified setting and
// checked state in an array where index 0 is the new senders setting and 1 the new
// conversations setting. A return value of [type]_UNSET indicates that nothing should
// change.
//
// The returned conversations setting will always be CONVERSATION_SENDERS_UNSET (not to change)
// in the calls case.
//
// Checking and unchecking is mostly an operation of setting or unsetting the relevant
// preference, except for some special handling where the conversation setting overlaps:
// - setting or unsetting "priority contacts" or "contacts" has no effect on the
// priority conversation setting, and vice versa
// - if "priority conversations" is selected, and the user checks "anyone", the conversation
// setting is also set to any conversations
// - if "anyone" is previously selected, and the user clicks "priority conversations", then
// the contacts setting is additionally reset to "none".
// - if "anyone" is previously selected, and the user clicks one of the contacts values,
// then the conversations setting is additionally reset to "none".
int[] settingsToSaveOnClick(SelectorWithWidgetPreference preference,
int currSendersSetting, int currConvosSetting) {
int[] savedSettings = new int[]{ PEOPLE_TYPE_UNSET, CONVERSATION_SENDERS_UNSET };
// If the preference isn't a checkbox, always consider this to be "checking" the setting.
// Otherwise, toggle.
final int[] endState = keyToSettingEndState(preference.getKey(),
preference.isCheckBox() ? preference.isChecked() : true);
final int prioritySendersSetting = endState[0];
final int priorityConvosSetting = endState[1];
if (prioritySendersSetting != PEOPLE_TYPE_UNSET
&& prioritySendersSetting != currSendersSetting) {
savedSettings[0] = prioritySendersSetting;
}
// Only handle conversation settings for the messages case. If not messages, there should
// never be any change to the conversation senders setting.
if (mIsMessages) {
if (priorityConvosSetting != CONVERSATION_SENDERS_UNSET
&& priorityConvosSetting != currConvosSetting) {
savedSettings[1] = priorityConvosSetting;
}
// Special-case handling for the "priority conversations" checkbox:
// If a specific selection exists for priority senders (starred, contacts), we leave
// it untouched. Otherwise (when the senders is set to "any"), set it to NONE.
if (preference.getKey() == KEY_IMPORTANT
&& currSendersSetting == PEOPLE_TYPE_ANYONE) {
savedSettings[0] = PEOPLE_TYPE_NONE;
}
// Flip-side special case for clicking either "contacts" option: if a specific selection
// exists for priority conversations, leave it untouched; otherwise, set to none.
if ((preference.getKey() == KEY_STARRED || preference.getKey() == KEY_CONTACTS)
&& currConvosSetting == CONVERSATION_SENDERS_ANYONE) {
savedSettings[1] = CONVERSATION_SENDERS_NONE;
}
}
return savedSettings;
}
private String getSummary(String key) {
switch (key) {
case KEY_STARRED:
return mZenModeSummaryHelper.getStarredContactsSummary();
case KEY_CONTACTS:
return mZenModeSummaryHelper.getContactsNumberSummary();
case KEY_IMPORTANT:
return getConversationSummary();
case KEY_ANY:
return mContext.getResources().getString(mIsMessages
? R.string.zen_mode_all_messages_summary
: R.string.zen_mode_all_calls_summary);
case KEY_NONE:
default:
return null;
}
}
private String getConversationSummary() {
final int numConversations = mNumImportantConversations;
if (numConversations == CONVERSATION_SENDERS_UNSET) {
return null;
} else {
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_conversations_count),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numConversations);
return msgFormat.format(args);
}
}
@VisibleForTesting
SelectorWithWidgetPreference.OnClickListener mSelectorClickListener =
new SelectorWithWidgetPreference.OnClickListener() {
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
// The settingsToSaveOnClick function takes whether the preference is a
// checkbox into account to determine whether this selection is checked or unchecked.
final int[] settingsToSave = settingsToSaveOnClick(preference,
getPrioritySenders(), getPriorityConversationSenders());
final int prioritySendersSetting = settingsToSave[0];
final int priorityConvosSetting = settingsToSave[1];
ZenPolicy.Builder diffPolicy = new ZenPolicy.Builder();
if (prioritySendersSetting != PEOPLE_TYPE_UNSET) {
if (mIsMessages) {
diffPolicy.allowMessages(prioritySendersSetting);
} else {
diffPolicy.allowCalls(prioritySendersSetting);
}
}
if (mIsMessages && priorityConvosSetting != CONVERSATION_SENDERS_UNSET) {
diffPolicy.allowConversations(priorityConvosSetting);
}
getMode().setPolicy(diffPolicy.build());
mBackend.updateMode(getMode());
}
};
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.Settings;
import android.service.notification.ZenPolicy;
import android.util.Log;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
public class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
private final int mRepeatCallersThreshold;
public ZenModeRepeatCallersPreferenceController(Context context,
String key, ZenModesBackend backend, int repeatCallersThreshold) {
super(context, key, backend);
mRepeatCallersThreshold = repeatCallersThreshold;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
TwoStatePreference pref = (TwoStatePreference) preference;
boolean anyCallersCanBypassDnd =
getMode().getPolicy().getPriorityCategoryCalls() == STATE_ALLOW
&& getMode().getPolicy().getPriorityCallSenders() == PEOPLE_TYPE_ANYONE;
// if any caller can bypass dnd then repeat callers preference is disabled
if (anyCallersCanBypassDnd) {
pref.setEnabled(false);
pref.setChecked(true);
} else {
pref.setEnabled(true);
pref.setChecked(
getMode().getPolicy().getPriorityCategoryRepeatCallers() == STATE_ALLOW);
}
setRepeatCallerSummary(preference);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final boolean allowRepeatCallers = (Boolean) newValue;
ZenPolicy diffPolicy = new ZenPolicy.Builder()
.allowRepeatCallers(allowRepeatCallers)
.build();
getMode().setPolicy(diffPolicy);
mBackend.updateMode(getMode());
return true;
}
private void setRepeatCallerSummary(Preference preference) {
preference.setSummary(mContext.getString(R.string.zen_mode_repeat_callers_summary,
mRepeatCallersThreshold));
}
}

View File

@@ -0,0 +1,295 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_ALARMS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CALLS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_EVENTS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MEDIA;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_MESSAGES;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REMINDERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.service.notification.ZenPolicy.PRIORITY_CATEGORY_SYSTEM;
import android.content.Context;
import android.icu.text.MessageFormat;
import android.service.notification.ZenPolicy;
import com.android.settings.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
public class ZenModeSummaryHelper {
private Context mContext;
private ZenModesBackend mBackend;
public ZenModeSummaryHelper(Context context, ZenModesBackend backend) {
mContext = context;
mBackend = backend;
}
private static final int[] ALL_PRIORITY_CATEGORIES = {
PRIORITY_CATEGORY_ALARMS,
PRIORITY_CATEGORY_MEDIA,
PRIORITY_CATEGORY_SYSTEM,
PRIORITY_CATEGORY_MESSAGES,
PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_CATEGORY_EVENTS,
PRIORITY_CATEGORY_REMINDERS,
PRIORITY_CATEGORY_CALLS,
PRIORITY_CATEGORY_REPEAT_CALLERS,
};
String getOtherSoundCategoriesSummary(ZenMode zenMode) {
List<String> enabledCategories = getEnabledCategories(
zenMode.getPolicy(),
category -> PRIORITY_CATEGORY_ALARMS == category
|| PRIORITY_CATEGORY_MEDIA == category
|| PRIORITY_CATEGORY_SYSTEM == category
|| PRIORITY_CATEGORY_REMINDERS == category
|| PRIORITY_CATEGORY_EVENTS == category,
true);
int numCategories = enabledCategories.size();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_other_sounds_summary),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numCategories);
if (numCategories >= 1) {
args.put("sound_category_1", enabledCategories.get(0));
if (numCategories >= 2) {
args.put("sound_category_2", enabledCategories.get(1));
if (numCategories == 3) {
args.put("sound_category_3", enabledCategories.get(2));
}
}
}
return msgFormat.format(args);
}
String getCallsSettingSummary(ZenMode zenMode) {
List<String> enabledCategories = getEnabledCategories(zenMode.getPolicy(),
category -> PRIORITY_CATEGORY_CALLS == category
|| PRIORITY_CATEGORY_REPEAT_CALLERS == category, true);
int numCategories = enabledCategories.size();
if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_none_calls);
} else if (numCategories == 1) {
return mContext.getString(R.string.zen_mode_calls_summary_one,
enabledCategories.get(0));
} else {
return mContext.getString(R.string.zen_mode_calls_summary_two,
enabledCategories.get(0),
enabledCategories.get(1));
}
}
String getMessagesSettingSummary(ZenPolicy policy) {
List<String> enabledCategories = getEnabledCategories(policy,
category -> PRIORITY_CATEGORY_MESSAGES == category
|| PRIORITY_CATEGORY_CONVERSATIONS == category, true);
int numCategories = enabledCategories.size();
if (numCategories == 0) {
return mContext.getString(R.string.zen_mode_none_messages);
} else if (numCategories == 1) {
return enabledCategories.get(0);
} else {
// While this string name seems like a slight misnomer: it's borrowing the analogous
// calls-summary functionality to combine two permissions.
return mContext.getString(R.string.zen_mode_calls_summary_two,
enabledCategories.get(0),
enabledCategories.get(1));
}
}
String getBlockedEffectsSummary(ZenMode zenMode) {
if (zenMode.getPolicy().shouldShowAllVisualEffects()) {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_muted);
} else if (zenMode.getPolicy().shouldHideAllVisualEffects()) {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_hidden);
} else {
return mContext.getResources().getString(
R.string.zen_mode_restrict_notifications_summary_custom);
}
}
private List<String> getEnabledCategories(ZenPolicy policy,
Predicate<Integer> filteredCategories, boolean capitalizeFirstInList) {
List<String> enabledCategories = new ArrayList<>();
for (int category : ALL_PRIORITY_CATEGORIES) {
boolean isFirst = capitalizeFirstInList && enabledCategories.isEmpty();
if (filteredCategories.test(category) && policy.isCategoryAllowed(category, false)) {
if (category == PRIORITY_CATEGORY_REPEAT_CALLERS
&& policy.isCategoryAllowed(PRIORITY_CATEGORY_CALLS, false)
&& policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
continue;
}
// For conversations, only the "priority conversations" setting is relevant; any
// other setting is subsumed by the messages-specific messaging.
if (category == PRIORITY_CATEGORY_CONVERSATIONS
&& policy.isCategoryAllowed(PRIORITY_CATEGORY_CONVERSATIONS, false)
&& policy.getPriorityConversationSenders()
!= CONVERSATION_SENDERS_IMPORTANT) {
continue;
}
enabledCategories.add(getCategory(category, policy, isFirst));
}
}
return enabledCategories;
}
private String getCategory(int category, ZenPolicy policy, boolean isFirst) {
if (category == PRIORITY_CATEGORY_ALARMS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_alarms_list_first);
} else {
return mContext.getString(R.string.zen_mode_alarms_list);
}
} else if (category == PRIORITY_CATEGORY_MEDIA) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_media_list_first);
} else {
return mContext.getString(R.string.zen_mode_media_list);
}
} else if (category == PRIORITY_CATEGORY_SYSTEM) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_system_list_first);
} else {
return mContext.getString(R.string.zen_mode_system_list);
}
} else if (category == PRIORITY_CATEGORY_MESSAGES) {
if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_ANYONE) {
return mContext.getString(R.string.zen_mode_from_anyone);
} else if (policy.getPriorityMessageSenders() == PEOPLE_TYPE_CONTACTS) {
return mContext.getString(R.string.zen_mode_from_contacts);
} else {
return mContext.getString(R.string.zen_mode_from_starred);
}
} else if (category == PRIORITY_CATEGORY_CONVERSATIONS
&& policy.getPriorityConversationSenders() == CONVERSATION_SENDERS_IMPORTANT) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_important_conversations);
} else {
return mContext.getString(
R.string.zen_mode_from_important_conversations_second);
}
} else if (category == PRIORITY_CATEGORY_EVENTS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_events_list_first);
} else {
return mContext.getString(R.string.zen_mode_events_list);
}
} else if (category == PRIORITY_CATEGORY_REMINDERS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_reminders_list_first);
} else {
return mContext.getString(R.string.zen_mode_reminders_list);
}
} else if (category == PRIORITY_CATEGORY_CALLS) {
if (policy.getPriorityCallSenders() == PEOPLE_TYPE_ANYONE) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_anyone);
}
return mContext.getString(R.string.zen_mode_all_callers);
} else if (policy.getPriorityCallSenders() == PEOPLE_TYPE_CONTACTS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_contacts);
}
return mContext.getString(R.string.zen_mode_contacts_callers);
} else {
if (isFirst) {
return mContext.getString(R.string.zen_mode_from_starred);
}
return mContext.getString(R.string.zen_mode_starred_callers);
}
} else if (category == PRIORITY_CATEGORY_REPEAT_CALLERS) {
if (isFirst) {
return mContext.getString(R.string.zen_mode_repeat_callers);
} else {
return mContext.getString(R.string.zen_mode_repeat_callers_list);
}
}
return "";
}
public String getStarredContactsSummary() {
List<String> starredContacts = mBackend.getStarredContacts();
int numStarredContacts = starredContacts.size();
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_starred_contacts_summary_contacts),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", numStarredContacts);
if (numStarredContacts >= 1) {
args.put("contact_1", starredContacts.get(0));
if (numStarredContacts >= 2) {
args.put("contact_2", starredContacts.get(1));
if (numStarredContacts == 3) {
args.put("contact_3", starredContacts.get(2));
}
}
}
return msgFormat.format(args);
}
public String getContactsNumberSummary() {
MessageFormat msgFormat = new MessageFormat(
mContext.getString(R.string.zen_mode_contacts_count),
Locale.getDefault());
Map<String, Object> args = new HashMap<>();
args.put("count", mBackend.queryAllContactsData().getCount());
return msgFormat.format(args);
}
public String getPeopleSummary(ZenMode zenMode) {
final int callersAllowed = zenMode.getPolicy().getPriorityCallSenders();
final int messagesAllowed = zenMode.getPolicy().getPriorityMessageSenders();
final int conversationsAllowed = zenMode.getPolicy().getPriorityConversationSenders();
final boolean areRepeatCallersAllowed =
zenMode.getPolicy().isCategoryAllowed(PRIORITY_CATEGORY_REPEAT_CALLERS, false);
if (callersAllowed == PEOPLE_TYPE_ANYONE
&& messagesAllowed == PEOPLE_TYPE_ANYONE
&& conversationsAllowed == CONVERSATION_SENDERS_ANYONE) {
return mContext.getResources().getString(R.string.zen_mode_people_all);
} else if (callersAllowed == PEOPLE_TYPE_NONE
&& messagesAllowed == PEOPLE_TYPE_NONE
&& conversationsAllowed == CONVERSATION_SENDERS_NONE
&& !areRepeatCallersAllowed) {
return mContext.getResources().getString(R.string.zen_mode_people_none);
} else {
return mContext.getResources().getString(R.string.zen_mode_people_some);
}
}
}

View File

@@ -21,14 +21,22 @@ import static java.util.Objects.requireNonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.AutomaticZenRule; import android.app.AutomaticZenRule;
import android.app.INotificationManager;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.ServiceManager;
import android.provider.ContactsContract;
import android.provider.Settings; import android.provider.Settings;
import android.service.notification.Condition; import android.service.notification.Condition;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.ZenAdapters; import android.service.notification.ZenAdapters;
import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import java.time.Duration; import java.time.Duration;
@@ -51,6 +59,8 @@ class ZenModesBackend {
private static ZenModesBackend sInstance; private static ZenModesBackend sInstance;
private final NotificationManager mNotificationManager; private final NotificationManager mNotificationManager;
static INotificationManager sINM = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
private final Context mContext; private final Context mContext;
@@ -105,6 +115,54 @@ class ZenModesBackend {
} }
} }
public ParceledListSlice<ConversationChannelWrapper> getConversations(boolean onlyImportant) {
try {
return sINM.getConversations(onlyImportant);
} catch (Exception e) {
Log.w(TAG, "Error calling NoMan", e);
return ParceledListSlice.emptyList();
}
}
public List<String> getStarredContacts() {
Cursor cursor = null;
try {
cursor = queryStarredContactsData();
return getStarredContacts(cursor);
} finally {
if (cursor != null) {
cursor.close();
}
}
}
@VisibleForTesting
List<String> getStarredContacts(Cursor cursor) {
List<String> starredContacts = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
String contact = cursor.getString(0);
starredContacts.add(contact != null ? contact :
mContext.getString(R.string.zen_mode_starred_contacts_empty_name));
} while (cursor.moveToNext());
}
return starredContacts;
}
private Cursor queryStarredContactsData() {
return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
ContactsContract.Data.STARRED + "=1", null,
ContactsContract.Data.TIMES_CONTACTED);
}
Cursor queryAllContactsData() {
return mContext.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
new String[]{ContactsContract.Contacts.DISPLAY_NAME_PRIMARY},
null, null, null);
}
private ZenMode getManualDndMode(ZenModeConfig config) { private ZenMode getManualDndMode(ZenModeConfig config) {
// TODO: b/333530553 - Read ZenDeviceEffects of manual DND. // TODO: b/333530553 - Read ZenDeviceEffects of manual DND.
// TODO: b/333682392 - Replace with final strings for name & trigger description // TODO: b/333682392 - Replace with final strings for name & trigger description

View File

@@ -57,9 +57,9 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
@Override @Override
public void onAttach(@NonNull Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context);
mContext = context; mContext = context;
mBackend = ZenModesBackend.getInstance(context); mBackend = ZenModesBackend.getInstance(context);
super.onAttach(context);
} }
@Override @Override
@@ -77,6 +77,12 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
} }
} }
@Override
public void onResume() {
super.onResume();
updateZenModeState();
}
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();

View File

@@ -29,7 +29,7 @@ import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settingslib.R import com.android.settingslib.R
import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle
import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.AppOps
import com.android.settingslib.spaprivileged.model.app.AppOpsController import com.android.settingslib.spaprivileged.model.app.AppOpsPermissionController
import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers
@@ -49,7 +49,7 @@ data class AlarmsAndRemindersAppRecord(
override val app: ApplicationInfo, override val app: ApplicationInfo,
val isTrumped: Boolean, val isTrumped: Boolean,
val isChangeable: Boolean, val isChangeable: Boolean,
var controller: AppOpsController, val controller: AppOpsPermissionController,
) : AppRecord ) : AppRecord
class AlarmsAndRemindersAppListModel( class AlarmsAndRemindersAppListModel(
@@ -84,7 +84,7 @@ class AlarmsAndRemindersAppListModel(
@Composable @Composable
override fun isAllowed(record: AlarmsAndRemindersAppRecord): () -> Boolean? = when { override fun isAllowed(record: AlarmsAndRemindersAppRecord): () -> Boolean? = when {
record.isTrumped -> ({ true }) record.isTrumped -> ({ true })
else -> record.controller.isAllowed.collectAsCallbackWithLifecycle() else -> record.controller.isAllowedFlow.collectAsCallbackWithLifecycle()
} }
override fun isChangeable(record: AlarmsAndRemindersAppRecord) = record.isChangeable override fun isChangeable(record: AlarmsAndRemindersAppRecord) = record.isChangeable
@@ -114,10 +114,11 @@ class AlarmsAndRemindersAppListModel(
app = app, app = app,
isTrumped = isTrumped, isTrumped = isTrumped,
isChangeable = hasRequestPermission && !isTrumped, isChangeable = hasRequestPermission && !isTrumped,
controller = AppOpsController( controller = AppOpsPermissionController(
context = context, context = context,
app = app, app = app,
appOps = APP_OPS, appOps = APP_OPS,
permission = PERMISSION,
), ),
) )
} }

View File

@@ -90,7 +90,8 @@ public class FactoryResetPreferenceController extends BasePreferenceController {
String packageName = resolution.activityInfo.packageName; String packageName = resolution.activityInfo.packageName;
PackageInfo factoryResetWizardPackageInfo; PackageInfo factoryResetWizardPackageInfo;
try { try {
factoryResetWizardPackageInfo = pm.getPackageInfo(packageName, 0); factoryResetWizardPackageInfo = pm.getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS);
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to resolve a Factory Reset Handler Application"); Log.e(TAG, "Unable to resolve a Factory Reset Handler Application");
return null; return null;

View File

@@ -18,6 +18,8 @@ package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import static com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -148,4 +150,15 @@ public class MagnificationAlwaysOnPreferenceControllerTest {
mController.updateState(mSwitchPreference); mController.updateState(mSwitchPreference);
assertThat(mSwitchPreference.isEnabled()).isTrue(); assertThat(mSwitchPreference.isEnabled()).isTrue();
} }
@Test
public void getAvailableStatus_notInSetupWizard_returnAvailable() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailableStatus_inSetupWizard_returnConditionallyUnavailable() {
mController.setInSetupWizard(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
}
} }

View File

@@ -17,6 +17,8 @@
package com.android.settings.accessibility; package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -61,6 +63,17 @@ public class MagnificationFollowTypingPreferenceControllerTest {
reset(mSwitchPreference); reset(mSwitchPreference);
} }
@Test
public void getAvailableStatus_notInSetupWizard_returnAvailable() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailableStatus_inSetupWizard_returnConditionallyUnavailable() {
mController.setInSetupWizard(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
}
@Test @Test
public void performClick_switchDefaultStateForFollowTyping_shouldReturnFalse() { public void performClick_switchDefaultStateForFollowTyping_shouldReturnFalse() {
mSwitchPreference.performClick(); mSwitchPreference.performClick();

View File

@@ -17,6 +17,8 @@
package com.android.settings.accessibility; package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -80,4 +82,15 @@ public class MagnificationJoystickPreferenceControllerTest {
assertThat(mController.isChecked()).isFalse(); assertThat(mController.isChecked()).isFalse();
assertThat(mSwitchPreference.isChecked()).isFalse(); assertThat(mSwitchPreference.isChecked()).isFalse();
} }
@Test
public void getAvailableStatus_notInSetupWizard_returnAvailable() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailableStatus_inSetupWizard_returnConditionallyUnavailable() {
mController.setInSetupWizard(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
}
} }

View File

@@ -19,6 +19,8 @@ package com.android.settings.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON; import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import static com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import static com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -186,6 +188,17 @@ public class MagnificationOneFingerPanningPreferenceControllerTest {
assertThat(mSwitchPreference.isChecked()).isFalse(); assertThat(mSwitchPreference.isChecked()).isFalse();
} }
@Test
public void getAvailableStatus_notInSetupWizard_returnAvailable() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailableStatus_inSetupWizard_returnConditionallyUnavailable() {
mController.setInSetupWizard(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE);
}
private String enabledSummary() { private String enabledSummary() {
return mContext.getString( return mContext.getString(
R.string.accessibility_magnification_one_finger_panning_summary_on); R.string.accessibility_magnification_one_finger_panning_summary_on);

View File

@@ -32,7 +32,6 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R; import com.android.settings.R;
@@ -97,7 +96,6 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest {
verify(mFooterBarMixin).setPrimaryButton(any()); verify(mFooterBarMixin).setPrimaryButton(any());
assertThat(mFragment.mTopIntroPreference.isVisible()).isFalse(); assertThat(mFragment.mTopIntroPreference.isVisible()).isFalse();
assertThat(mFragment.mSettingsPreference.isVisible()).isFalse(); assertThat(mFragment.mSettingsPreference.isVisible()).isFalse();
assertThat(mFragment.mFollowingTypingSwitchPreference.isVisible()).isFalse();
} }
@Test @Test
@@ -124,7 +122,6 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest {
mPreferenceManager.setPreferences(mPreferenceManager.createPreferenceScreen(context)); mPreferenceManager.setPreferences(mPreferenceManager.createPreferenceScreen(context));
mTopIntroPreference = new TopIntroPreference(context); mTopIntroPreference = new TopIntroPreference(context);
mSettingsPreference = new Preference(context); mSettingsPreference = new Preference(context);
mFollowingTypingSwitchPreference = new SwitchPreferenceCompat(context);
} }
@Override @Override

View File

@@ -90,7 +90,7 @@ class FingerprintEnrollIntroFragmentTest {
private val navigationViewModel = private val navigationViewModel =
FingerprintNavigationViewModel( FingerprintNavigationViewModel(
Introduction, Introduction(),
false, false,
flowViewModel, flowViewModel,
interactor interactor

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowApplication;
import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public final class ZenModeCallsLinkPreferenceControllerTest {
private ZenModeCallsLinkPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModeCallsLinkPreferenceController(
mContext, "something", mBackend);
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public final class ZenModeMessagesLinkPreferenceControllerTest {
private ZenModeMessagesLinkPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModeMessagesLinkPreferenceController(
mContext, "something", mBackend);
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI)
public final class ZenModeOtherLinkPreferenceControllerTest {
private ZenModeOtherLinkPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModeOtherLinkPreferenceController(
mContext, "something", mBackend);
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
}
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI)
public final class ZenModeOtherPreferenceControllerTest {
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
}
@Test
public void testUpdateState_alarms() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_alarm", mBackend);
controller.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
}
@Test
public void testOnPreferenceChange_alarms() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAlarms(false).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_alarm", mBackend);
controller.updateZenMode(preference, zenMode);
controller.onPreferenceChange(preference, true);
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
assertThat(captor.getValue().getPolicy().getPriorityCategoryAlarms())
.isEqualTo(STATE_ALLOW);
assertThat(captor.getValue().getPolicy().getPriorityCategoryEvents())
.isEqualTo(STATE_UNSET);
}
@Test
public void testUpdateState_media() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowMedia(true).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_media", mBackend);
controller.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
}
@Test
public void testOnPreferenceChange_media() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowMedia(false).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_media", mBackend);
controller.updateZenMode(preference, zenMode);
controller.onPreferenceChange(preference, true);
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
assertThat(captor.getValue().getPolicy().getPriorityCategoryMedia())
.isEqualTo(STATE_ALLOW);
assertThat(captor.getValue().getPolicy().getPriorityCategoryEvents())
.isEqualTo(STATE_UNSET);
}
@Test
public void testUpdateState_system() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowSystem(true).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_system", mBackend);
controller.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
}
@Test
public void testOnPreferenceChange_system() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowSystem(false).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_system", mBackend);
controller.updateZenMode(preference, zenMode);
controller.onPreferenceChange(preference, true);
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
assertThat(captor.getValue().getPolicy().getPriorityCategorySystem())
.isEqualTo(STATE_ALLOW);
assertThat(captor.getValue().getPolicy().getPriorityCategoryEvents())
.isEqualTo(STATE_UNSET);
}
@Test
public void testUpdateState_reminders() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowReminders(true).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_reminders",
mBackend);
controller.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
}
@Test
public void testOnPreferenceChange_reminders() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowReminders(false).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_reminders",
mBackend);
controller.updateZenMode(preference, zenMode);
controller.onPreferenceChange(preference, true);
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
assertThat(captor.getValue().getPolicy().getPriorityCategoryReminders())
.isEqualTo(STATE_ALLOW);
assertThat(captor.getValue().getPolicy().getPriorityCategoryEvents())
.isEqualTo(STATE_UNSET);
}
@Test
public void testUpdateState_events() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowEvents(true).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_events", mBackend);
controller.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
}
@Test
public void testOnPreferenceChange_events() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowEvents(false).build())
.build(), true);
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_events", mBackend);
controller.updateZenMode(preference, zenMode);
controller.onPreferenceChange(preference, true);
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
assertThat(captor.getValue().getPolicy().getPriorityCategoryEvents())
.isEqualTo(STATE_ALLOW);
assertThat(captor.getValue().getPolicy().getPriorityCategoryAlarms())
.isEqualTo(STATE_UNSET);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public final class ZenModePeopleLinkPreferenceControllerTest {
private ZenModePeopleLinkPreferenceController mController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mController = new ZenModePeopleLinkPreferenceController(
mContext, "something", mBackend);
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
.build(), true);
mController.updateZenMode(pref, zenMode);
verify(pref).setSummary(any());
}
}

View File

@@ -0,0 +1,509 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_UNSET;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_UNSET;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_ANY;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_CONTACTS;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_IMPORTANT;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_NONE;
import static com.android.settings.notification.modes.ZenModePrioritySendersPreferenceController.KEY_STARRED;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI)
public final class ZenModePrioritySendersPreferenceControllerTest {
private ZenModePrioritySendersPreferenceController mCallsController;
private ZenModePrioritySendersPreferenceController mMessagesController;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Mock
private PreferenceCategory mMockMessagesPrefCategory, mMockCallsPrefCategory;
@Mock
private PreferenceScreen mPreferenceScreen;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mMessagesController = new ZenModePrioritySendersPreferenceController(
mContext, "messages", true, mBackend);
mCallsController = new ZenModePrioritySendersPreferenceController(
mContext, "calls", false, mBackend);
when(mMockMessagesPrefCategory.getContext()).thenReturn(mContext);
when(mMockCallsPrefCategory.getContext()).thenReturn(mContext);
when(mPreferenceScreen.findPreference(mMessagesController.getPreferenceKey()))
.thenReturn(mMockMessagesPrefCategory);
when(mPreferenceScreen.findPreference(mCallsController.getPreferenceKey()))
.thenReturn(mMockCallsPrefCategory);
}
// Makes a preference with the provided key and whether it's a checkbox with
// mSelectorClickListener as the onClickListener set.
private SelectorWithWidgetPreference makePreference(
String key, boolean isCheckbox, boolean isMessages) {
final SelectorWithWidgetPreference pref =
new SelectorWithWidgetPreference(mContext, isCheckbox);
pref.setKey(key);
pref.setOnClickListener(
isMessages ? mMessagesController.mSelectorClickListener
: mCallsController.mSelectorClickListener);
return pref;
}
// Extension of ArgumentMatcher to check that a preference argument has the correct preference
// key, but doesn't check any other properties.
private class PrefKeyMatcher implements ArgumentMatcher<SelectorWithWidgetPreference> {
private String mKey;
PrefKeyMatcher(String key) {
mKey = key;
}
public boolean matches(SelectorWithWidgetPreference pref) {
return pref.getKey() != null && pref.getKey().equals(mKey);
}
public String toString() {
return "SelectorWithWidgetPreference matcher for key " + mKey;
}
}
@Test
public void testDisplayPreferences_makeMessagesPrefs() {
ArgumentCaptor<SelectorWithWidgetPreference> prefCaptor =
ArgumentCaptor.forClass(SelectorWithWidgetPreference.class);
when(mMockMessagesPrefCategory.getPreferenceCount()).thenReturn(0); // not yet created
mMessagesController.displayPreference(mPreferenceScreen);
// Starred contacts, Contacts, Priority Conversations, Any, None
verify(mMockMessagesPrefCategory, times(5)).addPreference(prefCaptor.capture());
}
@Test
public void testDisplayPreferences_makeCallsPrefs() {
ArgumentCaptor<SelectorWithWidgetPreference> prefCaptor =
ArgumentCaptor.forClass(SelectorWithWidgetPreference.class);
when(mMockCallsPrefCategory.getPreferenceCount()).thenReturn(0); // not yet created
mCallsController.displayPreference(mPreferenceScreen);
// Starred contacts, Contacts, Any, None
verify(mMockCallsPrefCategory, times(4)).addPreference(prefCaptor.capture());
// Make sure we never have the conversation one
verify(mMockCallsPrefCategory, never())
.addPreference(argThat(new PrefKeyMatcher(KEY_IMPORTANT)));
}
@Test
public void testDisplayPreferences_createdOnlyOnce() {
// Return a nonzero number of child preference when asked.
// Then when displayPreference is called, it should never make any new preferences.
when(mMockCallsPrefCategory.getPreferenceCount()).thenReturn(4); // already created
mCallsController.displayPreference(mPreferenceScreen);
mCallsController.displayPreference(mPreferenceScreen);
mCallsController.displayPreference(mPreferenceScreen);
// Even though we called display 3 times we shouldn't add more preferences here.
verify(mMockCallsPrefCategory, never())
.addPreference(any(SelectorWithWidgetPreference.class));
}
@Test
public void testKeyToSettingEndState_messagesCheck() {
int[] endState;
// For KEY_NONE everything should be none.
endState = mMessagesController.keyToSettingEndState(KEY_NONE, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For KEY_ANY everything should be allowed.
endState = mMessagesController.keyToSettingEndState(KEY_ANY, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_ANYONE);
// For [starred] contacts, we should set the priority senders, but not the conversations
endState = mMessagesController.keyToSettingEndState(KEY_STARRED, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mMessagesController.keyToSettingEndState(KEY_CONTACTS, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
endState = mMessagesController.keyToSettingEndState(KEY_IMPORTANT, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_IMPORTANT);
}
@Test
public void testKeyToSettingEndState_messagesUncheck() {
int[] endState;
// For KEY_NONE, "unchecking" still means "none".
endState = mMessagesController.keyToSettingEndState(KEY_NONE, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For KEY_ANY unchecking resets the state to "none".
endState = mMessagesController.keyToSettingEndState(KEY_ANY, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For [starred] contacts, we should unset the priority senders, but not the conversations
endState = mMessagesController.keyToSettingEndState(KEY_STARRED, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mMessagesController.keyToSettingEndState(KEY_CONTACTS, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
endState = mMessagesController.keyToSettingEndState(KEY_IMPORTANT, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
}
@Test
public void testKeyToSettingEndState_callsCheck() {
int[] endState;
// For calls: we should never set conversations, as this is unrelated to calls.
// For KEY_NONE senders should be none.
endState = mCallsController.keyToSettingEndState(KEY_NONE, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY senders should be ANY.
endState = mCallsController.keyToSettingEndState(KEY_ANY, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should set the priority senders accordingly
endState = mCallsController.keyToSettingEndState(KEY_STARRED, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mCallsController.keyToSettingEndState(KEY_CONTACTS, true);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testKeyToSettingEndState_callsUncheck() {
int[] endState;
// A calls setup should never set conversations settings.
// For KEY_NONE, "unchecking" still means "none".
endState = mCallsController.keyToSettingEndState(KEY_NONE, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY unchecking resets the state to "none".
endState = mCallsController.keyToSettingEndState(KEY_ANY, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should unset the priority senders, but not the conversations
endState = mCallsController.keyToSettingEndState(KEY_STARRED, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
endState = mCallsController.keyToSettingEndState(KEY_CONTACTS, false);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testSettingsToSaveOnClick_messagesCheck() {
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
SelectorWithWidgetPreference impPref = makePreference(KEY_IMPORTANT, true, true);
int[] endState;
// For KEY_NONE everything should be none.
nonePref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For KEY_ANY everything should be allowed.
anyPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_ANYONE);
// For [starred] contacts, we should set the priority senders, but not the conversations
starredPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
impPref.setChecked(true);
endState = mMessagesController.settingsToSaveOnClick(
impPref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_IMPORTANT);
}
@Test
public void testSettingsToSaveOnClick_messagesUncheck() {
int[] endState;
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
SelectorWithWidgetPreference impPref = makePreference(KEY_IMPORTANT, true, true);
// For KEY_NONE, "unchecking" still means "none".
nonePref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY unchecking resets the state to "none".
anyPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
// For [starred] contacts, we should unset the priority senders, but not the conversations
starredPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For priority conversations, we should set the conversations but not priority senders
impPref.setChecked(false);
endState = mMessagesController.settingsToSaveOnClick(
impPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_NONE);
}
@Test
public void testSettingsToSaveOnClick_callsCheck() {
int[] endState;
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
// For calls: we should never set conversations, as this is unrelated to calls.
// For KEY_NONE senders should be none.
nonePref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY senders should be ANY.
anyPref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_ANYONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should set the priority senders accordingly
starredPref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_STARRED);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(true);
endState = mCallsController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_CONTACTS);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testSettingsToSaveOnClick_callsUncheck() {
int[] endState;
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
// A calls setup should never set conversations settings.
// For KEY_NONE, "unchecking" still means "none".
nonePref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(endState[0]).isEqualTo(STATE_UNSET);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For KEY_ANY unchecking resets the state to "none".
anyPref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
// For [starred] contacts, we should unset the priority senders, but not the conversations
starredPref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
contactsPref.setChecked(false);
endState = mCallsController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(endState[0]).isEqualTo(PEOPLE_TYPE_NONE);
assertThat(endState[1]).isEqualTo(CONVERSATION_SENDERS_UNSET);
}
@Test
public void testSettingsToSave_messages_noChange() {
int[] savedSettings;
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, true, true);
nonePref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, true, true);
anyPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, true, true);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, true, true);
starredPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
contactsPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference impPref = makePreference(KEY_IMPORTANT, true, true);
impPref.setChecked(true);
savedSettings = mMessagesController.settingsToSaveOnClick(
impPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_IMPORTANT);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
}
@Test
public void testSettingsToSave_calls_noChange() {
int[] savedSettings;
SelectorWithWidgetPreference nonePref = makePreference(KEY_NONE, false, false);
savedSettings = mMessagesController.settingsToSaveOnClick(
nonePref, PEOPLE_TYPE_NONE, CONVERSATION_SENDERS_NONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference anyPref = makePreference(KEY_ANY, false, false);
savedSettings = mMessagesController.settingsToSaveOnClick(
anyPref, PEOPLE_TYPE_ANYONE, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
assertThat(savedSettings[1]).isEqualTo(STATE_UNSET);
SelectorWithWidgetPreference starredPref = makePreference(KEY_STARRED, false, false);
SelectorWithWidgetPreference contactsPref = makePreference(KEY_CONTACTS, false, false);
savedSettings = mMessagesController.settingsToSaveOnClick(
starredPref, PEOPLE_TYPE_STARRED, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
savedSettings = mMessagesController.settingsToSaveOnClick(
contactsPref, PEOPLE_TYPE_CONTACTS, CONVERSATION_SENDERS_ANYONE);
assertThat(savedSettings[0]).isEqualTo(STATE_UNSET);
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
@EnableFlags(Flags.FLAG_MODES_UI)
public final class ZenModeRepeatCallersPreferenceControllerTest {
private Context mContext;
@Mock
private ZenModesBackend mBackend;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
}
@Test
public void testUpdateState_allCalls() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder()
.allowCalls(PEOPLE_TYPE_ANYONE)
.build())
.build(), true);
ZenModeRepeatCallersPreferenceController controller =
new ZenModeRepeatCallersPreferenceController(mContext, "repeat", mBackend, 1);
controller.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
verify(preference).setEnabled(false);
}
@Test
public void testUpdateState_someCalls() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder()
.allowCalls(PEOPLE_TYPE_STARRED)
.allowRepeatCallers(true)
.build())
.build(), true);
ZenModeRepeatCallersPreferenceController controller =
new ZenModeRepeatCallersPreferenceController(mContext, "repeat", mBackend, 1);
controller.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
verify(preference).setEnabled(true);
}
@Test
public void testOnPreferenceChange() {
TwoStatePreference preference = mock(TwoStatePreference.class);
ZenMode zenMode = new ZenMode("id",
new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
.setType(AutomaticZenRule.TYPE_DRIVING)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
.build(), true);
ZenModeRepeatCallersPreferenceController controller =
new ZenModeRepeatCallersPreferenceController(mContext, "repeat", mBackend, 1);
controller.updateZenMode(preference, zenMode);
controller.onPreferenceChange(preference, false);
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
assertThat(captor.getValue().getPolicy().getPriorityCategoryRepeatCallers())
.isEqualTo(STATE_DISALLOW);
assertThat(captor.getValue().getPolicy().getPriorityCategoryEvents())
.isEqualTo(STATE_UNSET);
assertThat(captor.getValue().getPolicy().getPriorityCallSenders())
.isEqualTo(STATE_UNSET);
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright (C) 2024 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.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
import static com.google.common.truth.Truth.assertThat;
import android.app.AutomaticZenRule;
import android.content.Context;
import android.net.Uri;
import android.service.notification.ZenPolicy;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class ZenModesSummaryHelperTest {
private Context mContext;
private ZenModesBackend mBackend;
private ZenModeSummaryHelper mSummaryHelper;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mBackend = new ZenModesBackend(mContext);
mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
}
@Test
public void getPeopleSummary_noOne() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("No one can interrupt");
}
@Test
public void getPeopleSummary_some() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_CONTACTS).build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("Some people can interrupt");
}
@Test
public void getPeopleSummary_all() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_ANYONE).
allowConversations(CONVERSATION_SENDERS_ANYONE)
.allowMessages(PEOPLE_TYPE_ANYONE).build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("All people can interrupt");
}
@Test
public void getOtherSoundCategoriesSummary_single() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms can interrupt");
}
@Test
public void getOtherSoundCategoriesSummary_duo() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).allowMedia(true).build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms and media can interrupt");
}
@Test
public void getOtherSoundCategoriesSummary_trio() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.allowMedia(true)
.allowSystem(true)
.build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms, media, and touch sounds can interrupt");
}
@Test
public void getOtherSoundCategoriesSummary_quad() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.allowMedia(true)
.allowSystem(true)
.allowReminders(true)
.build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms, media, and 2 more can interrupt");
}
@Test
public void getOtherSoundCategoriesSummary_all() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.allowMedia(true)
.allowSystem(true)
.allowReminders(true)
.allowEvents(true)
.build())
.build();
ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms, media, and 3 more can interrupt");
}
}

View File

@@ -145,10 +145,13 @@ public class FactoryResetPreferenceControllerTest {
@Test @Test
@RequiresFlagsEnabled(com.android.settings.factory_reset.Flags.FLAG_ENABLE_FACTORY_RESET_WIZARD) @RequiresFlagsEnabled(com.android.settings.factory_reset.Flags.FLAG_ENABLE_FACTORY_RESET_WIZARD)
public void handlePreference_factoryResetWizardEnabled() { public void handlePreference_factoryResetWizardEnabled()
throws PackageManager.NameNotFoundException {
ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue(); assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue();
verify(mPackageManager).getPackageInfo(eq(FACTORY_RESET_APP_PACKAGE),
eq(PackageManager.GET_PERMISSIONS));
verify(mFactoryResetLauncher).launch(intentArgumentCaptor.capture()); verify(mFactoryResetLauncher).launch(intentArgumentCaptor.capture());
assertThat(intentArgumentCaptor.getValue()).isNotNull(); assertThat(intentArgumentCaptor.getValue()).isNotNull();
assertThat(intentArgumentCaptor.getValue().getAction()) assertThat(intentArgumentCaptor.getValue().getAction())

View File

@@ -28,7 +28,7 @@ import platform.test.screenshot.ViewScreenshotTestRule.Mode
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class FingerprintEnrollIntroScreenshotTest { class FingerprintEnrollIntroScreenshotTest {
private val injector: Injector = Injector(FingerprintNavigationStep.Introduction) private val injector: Injector = Injector(FingerprintNavigationStep.Introduction())
@Rule @Rule
@JvmField @JvmField

View File

@@ -17,14 +17,13 @@
package com.android.settings.spa.app.specialaccess package com.android.settings.spa.app.specialaccess
import android.Manifest import android.Manifest
import android.app.AppOpsManager
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.spaprivileged.model.app.IAppOpsController import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController
import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
@@ -117,14 +116,14 @@ class WifiControlAppListModelTest {
app = APP_NOT_REQUEST_PERMISSION, app = APP_NOT_REQUEST_PERMISSION,
hasRequestPermission = false, hasRequestPermission = false,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false),
) )
val appRequestedNetworkSettingsRecord = val appRequestedNetworkSettingsRecord =
AppOpPermissionRecord( AppOpPermissionRecord(
app = APP_REQUESTED_NETWORK_SETTINGS, app = APP_REQUESTED_NETWORK_SETTINGS,
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) appOpsPermissionController = FakeAppOpsPermissionController(false),
) )
val recordListFlow = val recordListFlow =
@@ -144,7 +143,7 @@ class WifiControlAppListModelTest {
app = APP, app = APP,
hasRequestPermission = false, hasRequestPermission = false,
hasRequestBroaderPermission = true, hasRequestBroaderPermission = true,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false),
) )
val isAllowed = getIsAllowed(record) val isAllowed = getIsAllowed(record)
@@ -159,7 +158,7 @@ class WifiControlAppListModelTest {
app = APP, app = APP,
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED), appOpsPermissionController = FakeAppOpsPermissionController(true),
) )
val isAllowed = getIsAllowed(record) val isAllowed = getIsAllowed(record)
@@ -174,7 +173,7 @@ class WifiControlAppListModelTest {
app = APP, app = APP,
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_IGNORED), appOpsPermissionController = FakeAppOpsPermissionController(false),
) )
val isAllowed = getIsAllowed(record) val isAllowed = getIsAllowed(record)
@@ -189,7 +188,7 @@ class WifiControlAppListModelTest {
app = APP, app = APP,
hasRequestPermission = false, hasRequestPermission = false,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false),
) )
val isChangeable = listModel.isChangeable(record) val isChangeable = listModel.isChangeable(record)
@@ -198,13 +197,13 @@ class WifiControlAppListModelTest {
} }
@Test @Test
fun isChangeable_notChangableWhenRequestedNetworkSettingPermissions() { fun isChangeable_notChangeableWhenRequestedNetworkSettingPermissions() {
val record = val record =
AppOpPermissionRecord( AppOpPermissionRecord(
app = APP, app = APP,
hasRequestPermission = false, hasRequestPermission = false,
hasRequestBroaderPermission = true, hasRequestBroaderPermission = true,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false),
) )
val isChangeable = listModel.isChangeable(record) val isChangeable = listModel.isChangeable(record)
@@ -213,13 +212,13 @@ class WifiControlAppListModelTest {
} }
@Test @Test
fun isChangeable_changableWhenRequestedChangeWifiStatePermission() { fun isChangeable_changeableWhenRequestedChangeWifiStatePermission() {
val record = val record =
AppOpPermissionRecord( AppOpPermissionRecord(
app = APP, app = APP,
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), appOpsPermissionController = FakeAppOpsPermissionController(false),
) )
val isChangeable = listModel.isChangeable(record) val isChangeable = listModel.isChangeable(record)
@@ -229,18 +228,18 @@ class WifiControlAppListModelTest {
@Test @Test
fun setAllowed_shouldCallController() { fun setAllowed_shouldCallController() {
val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) val appOpsPermissionController = FakeAppOpsPermissionController(false)
val record = val record =
AppOpPermissionRecord( AppOpPermissionRecord(
app = APP, app = APP,
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = appOpsController, appOpsPermissionController = appOpsPermissionController,
) )
listModel.setAllowed(record = record, newAllowed = true) listModel.setAllowed(record = record, newAllowed = true)
assertThat(appOpsController.setAllowedCalledWith).isTrue() assertThat(appOpsPermissionController.setAllowedCalledWith).isTrue()
} }
private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? { private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
@@ -266,14 +265,12 @@ class WifiControlAppListModelTest {
} }
} }
private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController { private class FakeAppOpsPermissionController(allowed: Boolean) : IAppOpsPermissionController {
var setAllowedCalledWith: Boolean? = null var setAllowedCalledWith: Boolean? = null
override val modeFlow = flowOf(fakeMode) override val isAllowedFlow = flowOf(allowed)
override fun setAllowed(allowed: Boolean) { override fun setAllowed(allowed: Boolean) {
setAllowedCalledWith = allowed setAllowedCalledWith = allowed
} }
override fun getMode() = fakeMode
} }

View File

@@ -30,10 +30,10 @@ import com.android.media.flags.Flags
import com.android.settings.R import com.android.settings.R
import com.android.settings.testutils.FakeFeatureFactory import com.android.settings.testutils.FakeFeatureFactory
import com.android.settingslib.spaprivileged.model.app.AppOps import com.android.settingslib.spaprivileged.model.app.AppOps
import com.android.settingslib.spaprivileged.model.app.IAppOpsController import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -50,7 +50,7 @@ class MediaRoutingControlTest {
@get:Rule @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule() val mockito: MockitoRule = MockitoJUnit.rule()
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule(); @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
@Spy @Spy
private val context: Context = ApplicationProvider.getApplicationContext() private val context: Context = ApplicationProvider.getApplicationContext()
@@ -86,29 +86,29 @@ class MediaRoutingControlTest {
@Test @Test
fun setAllowed_callWithNewStatusAsTrue_shouldChangeAppControllerModeToAllowed() { fun setAllowed_callWithNewStatusAsTrue_shouldChangeAppControllerModeToAllowed() {
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
val permissionRequestedRecord = val permissionRequestedRecord =
AppOpPermissionRecord( AppOpPermissionRecord(
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = fakeAppOpController, appOpsPermissionController = fakeAppOpsPermissionController,
) )
listModel.setAllowed(permissionRequestedRecord, true) listModel.setAllowed(permissionRequestedRecord, true)
assertThat(fakeAppOpController.getMode()).isEqualTo(AppOpsManager.MODE_ALLOWED) assertThat(fakeAppOpsPermissionController.setAllowedCalledWith).isTrue()
} }
@Test @Test
fun setAllowed_callWithNewStatusAsTrue_shouldLogPermissionToggleActionAsAllowed() { fun setAllowed_callWithNewStatusAsTrue_shouldLogPermissionToggleActionAsAllowed() {
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
val permissionRequestedRecord = val permissionRequestedRecord =
AppOpPermissionRecord( AppOpPermissionRecord(
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = fakeAppOpController, appOpsPermissionController = fakeAppOpsPermissionController,
) )
listModel.setAllowed(permissionRequestedRecord, true) listModel.setAllowed(permissionRequestedRecord, true)
@@ -119,29 +119,29 @@ class MediaRoutingControlTest {
@Test @Test
fun setAllowed_callWithNewStatusAsFalse_shouldChangeAppControllerModeToErrored() { fun setAllowed_callWithNewStatusAsFalse_shouldChangeAppControllerModeToErrored() {
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
val permissionRequestedRecord = val permissionRequestedRecord =
AppOpPermissionRecord( AppOpPermissionRecord(
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = fakeAppOpController, appOpsPermissionController = fakeAppOpsPermissionController,
) )
listModel.setAllowed(permissionRequestedRecord, false) listModel.setAllowed(permissionRequestedRecord, false)
assertThat(fakeAppOpController.getMode()).isEqualTo(AppOpsManager.MODE_ERRORED) assertThat(fakeAppOpsPermissionController.setAllowedCalledWith).isFalse()
} }
@Test @Test
fun setAllowed_callWithNewStatusAsFalse_shouldLogPermissionToggleActionAsDenied() { fun setAllowed_callWithNewStatusAsFalse_shouldLogPermissionToggleActionAsDenied() {
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
val permissionRequestedRecord = val permissionRequestedRecord =
AppOpPermissionRecord( AppOpPermissionRecord(
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = fakeAppOpController, appOpsPermissionController = fakeAppOpsPermissionController,
) )
listModel.setAllowed(permissionRequestedRecord, false) listModel.setAllowed(permissionRequestedRecord, false)
@@ -158,8 +158,7 @@ class MediaRoutingControlTest {
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = appOpsPermissionController = FakeAppOpsPermissionController(false),
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
) )
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH)) whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
.thenReturn(listOf(PACKAGE_NAME)) .thenReturn(listOf(PACKAGE_NAME))
@@ -177,8 +176,7 @@ class MediaRoutingControlTest {
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = false, hasRequestPermission = false,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = appOpsPermissionController = FakeAppOpsPermissionController(false),
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
) )
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH)) whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
.thenReturn(listOf(PACKAGE_NAME)) .thenReturn(listOf(PACKAGE_NAME))
@@ -196,8 +194,7 @@ class MediaRoutingControlTest {
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = appOpsPermissionController = FakeAppOpsPermissionController(false),
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
) )
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH)) whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
.thenReturn(listOf("other.package.name")) .thenReturn(listOf("other.package.name"))
@@ -215,8 +212,7 @@ class MediaRoutingControlTest {
app = ApplicationInfo().apply { packageName = PACKAGE_NAME }, app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
hasRequestPermission = true, hasRequestPermission = true,
hasRequestBroaderPermission = false, hasRequestBroaderPermission = false,
appOpsController = appOpsPermissionController = FakeAppOpsPermissionController(false),
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
) )
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH)) whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
.thenReturn(listOf(PACKAGE_NAME)) .thenReturn(listOf(PACKAGE_NAME))
@@ -226,15 +222,14 @@ class MediaRoutingControlTest {
assertThat(isSpecialAccessChangeable).isFalse() assertThat(isSpecialAccessChangeable).isFalse()
} }
private class FakeAppOpsController(fakeMode: Int) : IAppOpsController { private class FakeAppOpsPermissionController(allowed: Boolean) : IAppOpsPermissionController {
var setAllowedCalledWith: Boolean? = null
override val modeFlow = MutableStateFlow(fakeMode) override val isAllowedFlow = flowOf(allowed)
override fun setAllowed(allowed: Boolean) { override fun setAllowed(allowed: Boolean) {
modeFlow.value = if (allowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED setAllowedCalledWith = allowed
} }
override fun getMode(): Int = modeFlow.value
} }
companion object { companion object {

View File

@@ -41,7 +41,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
@@ -754,12 +753,11 @@ public class UiccSlotUtilTest {
} }
@Test @Test
@Ignore("b/337417975")
public void onReceiveSimCardStateChangeReceiver_receiveAction_timerCountDown() { public void onReceiveSimCardStateChangeReceiver_receiveAction_timerCountDown() {
CountDownLatch latch = spy(new CountDownLatch(1)); CountDownLatch latch = spy(new CountDownLatch(1));
UiccSlotUtil.SimCardStateChangeReceiver receive = UiccSlotUtil.SimCardStateChangeReceiver receive =
new UiccSlotUtil.SimCardStateChangeReceiver(latch); new UiccSlotUtil.SimCardStateChangeReceiver(latch);
Intent intent = new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED); Intent intent = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
intent.putExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_PRESENT); intent.putExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_PRESENT);
receive.onReceive(mContext, intent); receive.onReceive(mContext, intent);