Snap for 11869550 from a24c7183d2 to 24Q3-release
Change-Id: I8c19ffeb13b2e3936e041c985887daf626eb39c5
This commit is contained in:
@@ -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="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>
|
||||
<!-- 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>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -26,4 +26,4 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
</FrameLayout>
|
||||
</ScrollView>
|
||||
|
||||
37
res/xml/modes_calls_settings.xml
Normal file
37
res/xml/modes_calls_settings.xml
Normal 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>
|
||||
29
res/xml/modes_messages_settings.xml
Normal file
29
res/xml/modes_messages_settings.xml
Normal 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>
|
||||
49
res/xml/modes_other_settings.xml
Normal file
49
res/xml/modes_other_settings.xml
Normal 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>
|
||||
35
res/xml/modes_people_settings.xml
Normal file
35
res/xml/modes_people_settings.xml
Normal 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>
|
||||
@@ -22,4 +22,12 @@
|
||||
android:key="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>
|
||||
@@ -34,7 +34,6 @@ import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
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.events.OnPause;
|
||||
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
|
||||
* to 100%.
|
||||
*/
|
||||
public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceController
|
||||
implements LifecycleObserver, OnResume, OnPause {
|
||||
public class MagnificationAlwaysOnPreferenceController extends
|
||||
MagnificationFeaturePreferenceController implements LifecycleObserver, OnResume, OnPause {
|
||||
|
||||
private static final String TAG =
|
||||
MagnificationAlwaysOnPreferenceController.class.getSimpleName();
|
||||
@@ -89,7 +88,7 @@ public class MagnificationAlwaysOnPreferenceController extends TogglePreferenceC
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -23,10 +23,10 @@ import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.TogglePreferenceController;
|
||||
|
||||
/** 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 =
|
||||
MagnificationFollowTypingPreferenceController.class.getSimpleName();
|
||||
@@ -38,7 +38,7 @@ public class MagnificationFollowTypingPreferenceController extends TogglePrefere
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,12 +23,12 @@ import android.content.Context;
|
||||
import android.provider.Settings;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.TogglePreferenceController;
|
||||
|
||||
/**
|
||||
* 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 =
|
||||
MagnificationJoystickPreferenceController.class.getSimpleName();
|
||||
@@ -40,7 +40,7 @@ public class MagnificationJoystickPreferenceController extends TogglePreferenceC
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,13 +35,12 @@ import androidx.preference.TwoStatePreference;
|
||||
|
||||
import com.android.settings.R;
|
||||
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.events.OnPause;
|
||||
import com.android.settingslib.core.lifecycle.events.OnResume;
|
||||
|
||||
public class MagnificationOneFingerPanningPreferenceController
|
||||
extends TogglePreferenceController implements LifecycleObserver, OnResume, OnPause {
|
||||
public class MagnificationOneFingerPanningPreferenceController extends
|
||||
MagnificationFeaturePreferenceController implements LifecycleObserver, OnResume, OnPause {
|
||||
static final String PREF_KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED;
|
||||
|
||||
private TwoStatePreference mSwitchPreference;
|
||||
@@ -82,7 +81,7 @@ public class MagnificationOneFingerPanningPreferenceController
|
||||
|
||||
@Override
|
||||
public int getAvailabilityStatus() {
|
||||
return AVAILABLE;
|
||||
return isInSetupWizard() ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,5 +15,5 @@ menghanli@google.com #{LAST_RESORT_SUGGESTION}
|
||||
cipson@google.com #{LAST_RESORT_SUGGESTION}
|
||||
|
||||
# Partner-team files
|
||||
per-file HapticFeedbackIntensityPreferenceController.java = 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 *Haptic* = 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
|
||||
|
||||
@@ -82,8 +82,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
|
||||
new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
|
||||
|
||||
protected TwoStatePreference mFollowingTypingSwitchPreference;
|
||||
|
||||
// TODO(b/147021230): Move duplicated functions with android/internal/accessibility into util.
|
||||
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
|
||||
private CheckBox mSoftwareTypeCheckBox;
|
||||
@@ -92,10 +90,13 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
@Nullable private CheckBox mTwoFingerTripleTapTypeCheckBox;
|
||||
private DialogCreatable mDialogDelegate;
|
||||
|
||||
private boolean mInSetupWizard;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getActivity().setTitle(R.string.accessibility_screen_magnification_title);
|
||||
mInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -169,7 +170,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
.showAccessibilityGestureTutorialDialog(getPrefContext());
|
||||
case DialogEnums.MAGNIFICATION_EDIT_SHORTCUT:
|
||||
final CharSequence dialogTitle = getShortcutTitle();
|
||||
final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent())
|
||||
final int dialogType = mInSetupWizard
|
||||
? DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW
|
||||
: DialogType.EDIT_SHORTCUT_MAGNIFICATION;
|
||||
mDialog = AccessibilityDialogUtils.showEditShortcutDialog(getPrefContext(),
|
||||
@@ -240,17 +241,18 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
}
|
||||
|
||||
private void addFollowTypingSetting(PreferenceCategory generalCategory) {
|
||||
mFollowingTypingSwitchPreference = new SwitchPreferenceCompat(getPrefContext());
|
||||
mFollowingTypingSwitchPreference.setTitle(
|
||||
var followingTypingSwitchPreference = new SwitchPreferenceCompat(getPrefContext());
|
||||
followingTypingSwitchPreference.setTitle(
|
||||
R.string.accessibility_screen_magnification_follow_typing_title);
|
||||
mFollowingTypingSwitchPreference.setSummary(
|
||||
followingTypingSwitchPreference.setSummary(
|
||||
R.string.accessibility_screen_magnification_follow_typing_summary);
|
||||
mFollowingTypingSwitchPreference.setKey(
|
||||
followingTypingSwitchPreference.setKey(
|
||||
MagnificationFollowTypingPreferenceController.PREF_KEY);
|
||||
generalCategory.addPreference(mFollowingTypingSwitchPreference);
|
||||
generalCategory.addPreference(followingTypingSwitchPreference);
|
||||
|
||||
var followTypingPreferenceController = new MagnificationFollowTypingPreferenceController(
|
||||
getContext(), MagnificationFollowTypingPreferenceController.PREF_KEY);
|
||||
followTypingPreferenceController.setInSetupWizard(mInSetupWizard);
|
||||
followTypingPreferenceController.displayPreference(getPreferenceScreen());
|
||||
addPreferenceController(followTypingPreferenceController);
|
||||
}
|
||||
@@ -282,6 +284,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
|
||||
var alwaysOnPreferenceController = new MagnificationAlwaysOnPreferenceController(
|
||||
getContext(), MagnificationAlwaysOnPreferenceController.PREF_KEY);
|
||||
alwaysOnPreferenceController.setInSetupWizard(mInSetupWizard);
|
||||
getSettingsLifecycle().addObserver(alwaysOnPreferenceController);
|
||||
alwaysOnPreferenceController.displayPreference(getPreferenceScreen());
|
||||
addPreferenceController(alwaysOnPreferenceController);
|
||||
@@ -301,6 +304,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
|
||||
var oneFingerPanningPreferenceController =
|
||||
new MagnificationOneFingerPanningPreferenceController(getContext());
|
||||
oneFingerPanningPreferenceController.setInSetupWizard(mInSetupWizard);
|
||||
getSettingsLifecycle().addObserver(oneFingerPanningPreferenceController);
|
||||
oneFingerPanningPreferenceController.displayPreference(getPreferenceScreen());
|
||||
addPreferenceController(oneFingerPanningPreferenceController);
|
||||
@@ -329,6 +333,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends
|
||||
getContext(),
|
||||
MagnificationJoystickPreferenceController.PREF_KEY
|
||||
);
|
||||
joystickPreferenceController.setInSetupWizard(mInSetupWizard);
|
||||
joystickPreferenceController.displayPreference(getPreferenceScreen());
|
||||
addPreferenceController(joystickPreferenceController);
|
||||
}
|
||||
|
||||
@@ -72,10 +72,6 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizard
|
||||
if (mSettingsPreference != null) {
|
||||
mSettingsPreference.setVisible(false);
|
||||
}
|
||||
// Setting of following typing
|
||||
if (mFollowingTypingSwitchPreference != null) {
|
||||
mFollowingTypingSwitchPreference.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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_UNAWARE;
|
||||
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.annotation.UserIdInt;
|
||||
@@ -35,6 +37,7 @@ import android.app.ActionBar;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.admin.DevicePolicyEventLogger;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.flags.Flags;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -49,6 +52,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.stats.devicepolicy.DevicePolicyEnums;
|
||||
import android.util.IconDrawableFactory;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -68,6 +72,10 @@ import com.android.settingslib.RestrictedLockUtils;
|
||||
import com.android.settingslib.RestrictedSwitchPreference;
|
||||
import com.android.settingslib.widget.LayoutPreference;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class InteractAcrossProfilesDetails extends AppInfoBase
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
@@ -381,6 +389,7 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
|
||||
private void enableInteractAcrossProfiles(boolean newState) {
|
||||
mCrossProfileApps.setInteractAcrossProfilesAppOp(
|
||||
mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
|
||||
setUserPreferenceForPackage(newState, mPackageName);
|
||||
}
|
||||
|
||||
private void handleInstallBannerClick() {
|
||||
@@ -552,4 +561,40 @@ public class InteractAcrossProfilesDetails extends AppInfoBase
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,38 +14,38 @@
|
||||
* 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
|
||||
* 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
|
||||
*/
|
||||
sealed class StageViewModel {
|
||||
sealed class EnrollStageModel {
|
||||
/** Unknown stage */
|
||||
data object Unknown : StageViewModel()
|
||||
data object Unknown : EnrollStageModel()
|
||||
|
||||
/** 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. */
|
||||
data object Center : StageViewModel()
|
||||
data object Center : EnrollStageModel()
|
||||
|
||||
/**
|
||||
* Fingerprint stage of enrollment. Typically there is some sort of indication that a user should
|
||||
* 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 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
|
||||
* the right edge of their fingerprint.
|
||||
*/
|
||||
data object RightEdge : StageViewModel()
|
||||
data object RightEdge : EnrollStageModel()
|
||||
}
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
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.flowOf
|
||||
|
||||
typealias EnrollStageThresholds = Map<Float, StageViewModel>
|
||||
typealias EnrollStageThresholds = Map<Float, EnrollStageModel>
|
||||
|
||||
/** Interactor that provides enroll stages for enrollment. */
|
||||
interface EnrollStageInteractor {
|
||||
@@ -33,11 +33,11 @@ class EnrollStageInteractorImpl() : EnrollStageInteractor {
|
||||
override val enrollStageThresholds: Flow<EnrollStageThresholds> =
|
||||
flowOf(
|
||||
mapOf(
|
||||
0.0f to StageViewModel.Center,
|
||||
0.25f to StageViewModel.Guided,
|
||||
0.5f to StageViewModel.Fingertip,
|
||||
0.75f to StageViewModel.LeftEdge,
|
||||
0.875f to StageViewModel.RightEdge,
|
||||
0.0f to EnrollStageModel.Center,
|
||||
0.25f to EnrollStageModel.Guided,
|
||||
0.5f to EnrollStageModel.Fingertip,
|
||||
0.75f to EnrollStageModel.LeftEdge,
|
||||
0.875f to EnrollStageModel.RightEdge,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ interface OrientationInteractor {
|
||||
* A flow that contains the rotation info matched against the def [config_reverseDefaultRotation]
|
||||
*/
|
||||
val rotationFromDefault: Flow<Int>
|
||||
|
||||
/**
|
||||
* A Helper function that computes rotation if device is in
|
||||
* [R.bool.config_reverseDefaultConfigRotation]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Bundle
|
||||
import android.os.Vibrator
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
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.OrientationInteractor
|
||||
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.VibrationInteractorImpl
|
||||
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.FingerprintScrollViewModel
|
||||
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.password.ChooseLockGeneric
|
||||
import com.android.settings.password.ChooseLockSettingsHelper
|
||||
@@ -116,6 +120,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
private lateinit var foldStateInteractor: FoldStateInteractor
|
||||
private lateinit var orientationInteractor: OrientationInteractor
|
||||
private lateinit var displayDensityInteractor: DisplayDensityInteractor
|
||||
private lateinit var udfpsEnrollInteractor: UdfpsEnrollInteractor
|
||||
private lateinit var fingerprintScrollViewModel: FingerprintScrollViewModel
|
||||
private lateinit var backgroundViewModel: BackgroundViewModel
|
||||
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
|
||||
@@ -256,6 +261,15 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
fingerprintManager,
|
||||
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 =
|
||||
FingerprintManagerInteractorImpl(
|
||||
@@ -273,12 +287,6 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
|
||||
val hasConfirmedDeviceCredential = gatekeeperInfo is GatekeeperInfo.GatekeeperPasswordInfo
|
||||
|
||||
val accessibilityInteractor =
|
||||
AccessibilityInteractorImpl(
|
||||
getSystemService(AccessibilityManager::class.java)!!,
|
||||
lifecycleScope,
|
||||
)
|
||||
|
||||
navigationViewModel =
|
||||
ViewModelProvider(
|
||||
this,
|
||||
@@ -384,6 +392,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
orientationInteractor,
|
||||
backgroundViewModel,
|
||||
fingerprintSensorRepo,
|
||||
udfpsEnrollInteractor,
|
||||
),
|
||||
)[UdfpsViewModel::class.java]
|
||||
|
||||
@@ -435,17 +444,17 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
|
||||
else -> FingerprintEnrollEnrollingV2Fragment()
|
||||
}
|
||||
}
|
||||
Introduction -> FingerprintEnrollIntroV2Fragment()
|
||||
is Introduction -> FingerprintEnrollIntroV2Fragment()
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (theClass != null) {
|
||||
supportFragmentManager.fragments.onEach { fragment ->
|
||||
supportFragmentManager.beginTransaction().remove(fragment).commit()
|
||||
}
|
||||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.setCustomAnimations(
|
||||
step.enterTransition.toAnimation(),
|
||||
step.exitTransition.toAnimation(),
|
||||
)
|
||||
.setReorderingAllowed(true)
|
||||
.add(R.id.fragment_container_view, theClass::class.java, null)
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.airbnb.lottie.LottieCompositionFactory
|
||||
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.StageViewModel
|
||||
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.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.widget.UdfpsEnrollViewV2
|
||||
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
|
||||
view.setBackgroundColor(color)
|
||||
|
||||
udfpsEnrollView.setFinishAnimationCompleted { viewModel.finishedSuccessfully() }
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
launch {
|
||||
@@ -159,7 +161,14 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
|
||||
}
|
||||
|
||||
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 {
|
||||
return when (this.stageViewModel) {
|
||||
StageViewModel.Center,
|
||||
StageViewModel.Guided,
|
||||
StageViewModel.Fingertip,
|
||||
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
|
||||
StageViewModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
|
||||
StageViewModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
|
||||
return when (this.enrollStageModel) {
|
||||
EnrollStageModel.Center,
|
||||
EnrollStageModel.Guided,
|
||||
EnrollStageModel.Fingertip,
|
||||
EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
|
||||
EnrollStageModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
|
||||
EnrollStageModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
|
||||
}
|
||||
}
|
||||
|
||||
private fun DescriptionText.toResource(): Int? {
|
||||
return when (this.stageViewModel) {
|
||||
StageViewModel.Center,
|
||||
StageViewModel.Guided,
|
||||
StageViewModel.Fingertip,
|
||||
StageViewModel.LeftEdge,
|
||||
StageViewModel.RightEdge -> null
|
||||
StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
|
||||
return when (this.enrollStageModel) {
|
||||
EnrollStageModel.Center,
|
||||
EnrollStageModel.Guided,
|
||||
EnrollStageModel.Fingertip,
|
||||
EnrollStageModel.LeftEdge,
|
||||
EnrollStageModel.RightEdge -> null
|
||||
EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
|
||||
}
|
||||
}
|
||||
|
||||
private fun EducationAnimationModel.toResource(): Int? {
|
||||
return when (this.stageViewModel) {
|
||||
StageViewModel.Center,
|
||||
StageViewModel.Guided -> R.raw.udfps_center_hint_lottie
|
||||
StageViewModel.Fingertip -> R.raw.udfps_tip_hint_lottie
|
||||
StageViewModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
|
||||
StageViewModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
|
||||
StageViewModel.Unknown -> null
|
||||
return when (this.enrollStageModel) {
|
||||
EnrollStageModel.Center,
|
||||
EnrollStageModel.Guided -> R.raw.udfps_center_hint_lottie
|
||||
EnrollStageModel.Fingertip -> R.raw.udfps_tip_hint_lottie
|
||||
EnrollStageModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
|
||||
EnrollStageModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
|
||||
EnrollStageModel.Unknown -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* 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 */
|
||||
data class DescriptionText(
|
||||
val isSuw: Boolean,
|
||||
val isAccessibility: Boolean,
|
||||
val stageViewModel: StageViewModel,
|
||||
val enrollStageModel: EnrollStageModel,
|
||||
)
|
||||
@@ -14,13 +14,13 @@
|
||||
* 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 */
|
||||
data class HeaderText(
|
||||
val isSuw: Boolean,
|
||||
val isAccessibility: Boolean,
|
||||
val stageViewModel: StageViewModel,
|
||||
val enrollStageModel: EnrollStageModel,
|
||||
)
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
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 */
|
||||
data class EducationAnimationModel(
|
||||
val isSuw: Boolean,
|
||||
val isAccessibility: Boolean,
|
||||
val stageViewModel: StageViewModel,
|
||||
val enrollStageModel: EnrollStageModel,
|
||||
)
|
||||
|
||||
@@ -17,20 +17,24 @@
|
||||
package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
|
||||
|
||||
import android.graphics.Point
|
||||
import android.graphics.PointF
|
||||
import android.view.Surface
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.domain.interactor.DebuggingInteractor
|
||||
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.FingerprintVibrationEffects
|
||||
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.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.FingerprintAction
|
||||
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
|
||||
@@ -61,9 +65,11 @@ class UdfpsViewModel(
|
||||
orientationInteractor: OrientationInteractor,
|
||||
backgroundViewModel: BackgroundViewModel,
|
||||
sensorRepository: FingerprintSensorRepository,
|
||||
udfpsEnrollInteractor: UdfpsEnrollInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
private val isSetupWizard = flowOf(false)
|
||||
private var shouldResetErollment = false
|
||||
|
||||
private var _enrollState: Flow<FingerEnrollState?> =
|
||||
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
|
||||
* first time.
|
||||
@@ -132,13 +149,13 @@ class UdfpsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines the current [StageViewModel] enrollment is in */
|
||||
val enrollStage: Flow<StageViewModel> =
|
||||
/** Determines the current [EnrollStageModel] enrollment is in */
|
||||
private val enrollStage: Flow<EnrollStageModel> =
|
||||
combine(enrollStageInteractor.enrollStageThresholds, enrollState) { thresholds, event ->
|
||||
if (event is FingerEnrollState.EnrollProgress) {
|
||||
val progress =
|
||||
(event.totalStepsRequired - event.remainingSteps).toFloat() / event.totalStepsRequired
|
||||
var stageToReturn: StageViewModel = StageViewModel.Center
|
||||
var stageToReturn: EnrollStageModel = EnrollStageModel.Center
|
||||
thresholds.forEach { (threshold, stage) ->
|
||||
if (progress < threshold) {
|
||||
return@forEach
|
||||
@@ -153,6 +170,40 @@ class UdfpsViewModel(
|
||||
.filterNotNull()
|
||||
.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. */
|
||||
val shouldShowLottie: Flow<Boolean> =
|
||||
combine(
|
||||
@@ -183,7 +234,7 @@ class UdfpsViewModel(
|
||||
}
|
||||
.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 */
|
||||
val descriptionText: Flow<DescriptionText?> =
|
||||
@@ -202,6 +253,10 @@ class UdfpsViewModel(
|
||||
|
||||
/** Indicates if the consumer is ready for enrollment */
|
||||
fun readyForEnrollment() {
|
||||
if (shouldResetErollment) {
|
||||
shouldResetErollment = false
|
||||
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
|
||||
}
|
||||
fingerprintEnrollEnrollingViewModel.canEnroll()
|
||||
}
|
||||
|
||||
@@ -237,8 +292,12 @@ class UdfpsViewModel(
|
||||
}
|
||||
|
||||
private fun doReset() {
|
||||
/** Indicates if the icon should be animating or not */
|
||||
_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 */
|
||||
@@ -272,6 +331,7 @@ class UdfpsViewModel(
|
||||
private val orientationInteractor: OrientationInteractor,
|
||||
private val backgroundViewModel: BackgroundViewModel,
|
||||
private val sensorRepository: FingerprintSensorRepository,
|
||||
private val udfpsEnrollInteractor: UdfpsEnrollInteractor,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -287,6 +347,7 @@ class UdfpsViewModel(
|
||||
orientationInteractor,
|
||||
backgroundViewModel,
|
||||
sensorRepository,
|
||||
udfpsEnrollInteractor,
|
||||
)
|
||||
as T
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
@@ -37,7 +38,6 @@ import androidx.core.animation.addListener
|
||||
import androidx.core.graphics.toRect
|
||||
import androidx.core.graphics.toRectF
|
||||
import com.android.settings.R
|
||||
import com.android.settings.biometrics.fingerprint2.lib.model.StageViewModel
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
@@ -51,11 +51,11 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
private val fingerprintDrawable: ShapeDrawable
|
||||
private val sensorOutlinePaint: Paint
|
||||
private val blueFill: Paint
|
||||
private val helper = UdfpsEnrollHelperV2(context)
|
||||
@ColorInt private var enrollIconColor = 0
|
||||
@ColorInt private var movingTargetFill = 0
|
||||
private var currentScale = 1.0f
|
||||
private var alpha = 0
|
||||
private var guidedEnrollmentOffset: PointF? = null
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
fun stopDrawing() {
|
||||
alpha = 0
|
||||
@@ -211,6 +172,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
if (currentBounds.equals(offsetRect)) {
|
||||
return
|
||||
}
|
||||
|
||||
val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left)
|
||||
xAnimator.addUpdateListener {
|
||||
currX = it.animatedValue as Float
|
||||
@@ -260,6 +222,40 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
|
||||
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 {
|
||||
private const val TAG = "UdfpsEnrollDrawableV2"
|
||||
private const val DEFAULT_STROKE_WIDTH = 3f
|
||||
|
||||
@@ -27,10 +27,12 @@ import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.animation.Interpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.graphics.toRectF
|
||||
import com.android.internal.annotations.VisibleForTesting
|
||||
@@ -46,6 +48,7 @@ import kotlin.math.sin
|
||||
class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: AttributeSet?) :
|
||||
Drawable() {
|
||||
private val sensorRect: Rect = Rect()
|
||||
private var onFinishedCompletionAnimation: (() -> Unit)? = null
|
||||
private var rotation: Int = 0
|
||||
private val strokeWidthPx: Float
|
||||
|
||||
@@ -287,6 +290,12 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
|
||||
checkMarkDrawable.bounds = newBounds
|
||||
checkMarkDrawable.setVisible(true, false)
|
||||
}
|
||||
doOnEnd {
|
||||
onFinishedCompletionAnimation?.let{
|
||||
it()
|
||||
}
|
||||
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
@@ -380,6 +389,13 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
|
||||
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 {
|
||||
private const val TAG = "UdfpsProgressBar"
|
||||
private const val FILL_COLOR_ANIMATION_DURATION_MS = 350L
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrol
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
@@ -31,7 +32,6 @@ import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import com.android.settings.R
|
||||
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.shared.model.FingerprintSensorType
|
||||
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 lateinit var touchExplorationAnnouncer: TouchExplorationAnnouncer
|
||||
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
|
||||
@@ -112,11 +119,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils)
|
||||
}
|
||||
|
||||
/** Updates the current enrollment stage. */
|
||||
fun updateStage(it: StageViewModel) {
|
||||
fingerprintIcon.updateStage(it)
|
||||
}
|
||||
|
||||
/** Receive enroll progress event */
|
||||
fun onUdfpsEvent(event: FingerEnrollState) {
|
||||
when (event) {
|
||||
@@ -174,7 +176,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
|
||||
|
||||
/** Receive enroll progress event */
|
||||
private fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
|
||||
fingerprintIcon.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. */
|
||||
fun onEnrollProgressSaved(it: FingerEnrollState.EnrollProgress) {
|
||||
fingerprintIcon.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 {
|
||||
private const val TAG = "UdfpsEnrollView"
|
||||
}
|
||||
|
||||
@@ -88,7 +88,10 @@ sealed interface FingerprintNavigationStep {
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
data object Init : UiStep() {
|
||||
@@ -103,7 +106,7 @@ sealed interface FingerprintNavigationStep {
|
||||
} else if (state.flowType is FastEnroll) {
|
||||
TransitionStep(Enrollment(state.fingerprintSensor!!))
|
||||
} else {
|
||||
TransitionStep(Introduction)
|
||||
TransitionStep(Introduction())
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
@@ -118,7 +121,7 @@ sealed interface FingerprintNavigationStep {
|
||||
action: FingerprintAction,
|
||||
): FingerprintNavigationStep? {
|
||||
return when (action) {
|
||||
FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction)
|
||||
FingerprintAction.CONFIRM_DEVICE_SUCCESS -> TransitionStep(Introduction())
|
||||
FingerprintAction.CONFIRM_DEVICE_FAIL -> Finish(null)
|
||||
else -> null
|
||||
}
|
||||
@@ -126,7 +129,10 @@ sealed interface FingerprintNavigationStep {
|
||||
}
|
||||
|
||||
/** 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(
|
||||
state: NavigationState,
|
||||
action: FingerprintAction,
|
||||
@@ -141,7 +147,11 @@ sealed interface FingerprintNavigationStep {
|
||||
}
|
||||
|
||||
/** 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(
|
||||
state: NavigationState,
|
||||
action: FingerprintAction,
|
||||
@@ -149,7 +159,8 @@ sealed interface FingerprintNavigationStep {
|
||||
return when (action) {
|
||||
FingerprintAction.NEXT -> TransitionStep(Enrollment(state.fingerprintSensor!!))
|
||||
FingerprintAction.NEGATIVE_BUTTON_PRESSED,
|
||||
FingerprintAction.PREV -> TransitionStep(Introduction)
|
||||
FingerprintAction.PREV ->
|
||||
TransitionStep(Introduction(Transition.EnterFromLeft, Transition.ExitToRight))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -179,7 +190,10 @@ sealed interface FingerprintNavigationStep {
|
||||
): FingerprintNavigationStep? {
|
||||
return when (action) {
|
||||
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!!))
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -54,9 +54,6 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!AudioSharingUtils.isFeatureEnabled()) {
|
||||
return;
|
||||
}
|
||||
setShowsDialog(true);
|
||||
mActivity = getActivity();
|
||||
if (mActivity == null) {
|
||||
@@ -84,6 +81,9 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
if (!AudioSharingUtils.isFeatureEnabled()) {
|
||||
return getUnsupporteDialog();
|
||||
}
|
||||
if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
|
||||
CachedBluetoothDevice connectedLeDevice =
|
||||
AudioStreamsHelper.getCachedBluetoothDeviceInSharingOrLeConnected(
|
||||
@@ -137,6 +137,21 @@ public class AudioStreamConfirmDialog extends InstrumentedDialogFragment {
|
||||
.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) {
|
||||
return new AudioStreamsDialogFragment.DialogBuilder(getActivity())
|
||||
.setTitle(getString(R.string.audio_streams_dialog_cannot_listen))
|
||||
|
||||
@@ -19,16 +19,12 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
|
||||
|
||||
public class AudioStreamConfirmDialogActivity extends SettingsActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
if (!AudioSharingUtils.isFeatureEnabled()) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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_DARK_MODE = 0.65f;
|
||||
|
||||
private final int mTitleColorNormal;
|
||||
|
||||
private BatteryEntry mInfo;
|
||||
private BatteryDiffEntry mBatteryDiffEntry;
|
||||
private CharSequence mContentDescription;
|
||||
@@ -78,6 +80,8 @@ public class PowerGaugePreference extends AppPreference {
|
||||
mInfo = info;
|
||||
mContentDescription = contentDescription;
|
||||
mShowAnomalyIcon = false;
|
||||
mTitleColorNormal =
|
||||
Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
|
||||
}
|
||||
|
||||
/** Sets the content description. */
|
||||
@@ -155,6 +159,13 @@ public class PowerGaugePreference extends AppPreference {
|
||||
final TextView titleView = (TextView) view.findViewById(android.R.id.title);
|
||||
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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,6 @@ import android.app.AutomaticZenRule;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.core.AbstractPreferenceController;
|
||||
|
||||
@@ -42,6 +39,10 @@ public class ZenModeFragment extends ZenModeFragmentBase {
|
||||
// {@link AbstractZenModePreferenceController}.
|
||||
List<AbstractPreferenceController> prefControllers = new ArrayList<>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,22 @@ import static java.util.Objects.requireNonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AutomaticZenRule;
|
||||
import android.app.INotificationManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ServiceManager;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.Condition;
|
||||
import android.service.notification.ConversationChannelWrapper;
|
||||
import android.service.notification.ZenAdapters;
|
||||
import android.service.notification.ZenModeConfig;
|
||||
|
||||
import android.util.Log;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.android.settings.R;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -51,6 +59,8 @@ class ZenModesBackend {
|
||||
private static ZenModesBackend sInstance;
|
||||
|
||||
private final NotificationManager mNotificationManager;
|
||||
static INotificationManager sINM = INotificationManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
|
||||
|
||||
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) {
|
||||
// TODO: b/333530553 - Read ZenDeviceEffects of manual DND.
|
||||
// TODO: b/333682392 - Replace with final strings for name & trigger description
|
||||
|
||||
@@ -57,9 +57,9 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mContext = context;
|
||||
mBackend = ZenModesBackend.getInstance(context);
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,6 +77,12 @@ abstract class ZenModesFragmentBase extends RestrictedDashboardFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateZenModeState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
@@ -29,7 +29,7 @@ import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
|
||||
import com.android.settingslib.R
|
||||
import com.android.settingslib.spa.lifecycle.collectAsCallbackWithLifecycle
|
||||
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.IPackageManagers
|
||||
import com.android.settingslib.spaprivileged.model.app.PackageManagers
|
||||
@@ -49,7 +49,7 @@ data class AlarmsAndRemindersAppRecord(
|
||||
override val app: ApplicationInfo,
|
||||
val isTrumped: Boolean,
|
||||
val isChangeable: Boolean,
|
||||
var controller: AppOpsController,
|
||||
val controller: AppOpsPermissionController,
|
||||
) : AppRecord
|
||||
|
||||
class AlarmsAndRemindersAppListModel(
|
||||
@@ -84,7 +84,7 @@ class AlarmsAndRemindersAppListModel(
|
||||
@Composable
|
||||
override fun isAllowed(record: AlarmsAndRemindersAppRecord): () -> Boolean? = when {
|
||||
record.isTrumped -> ({ true })
|
||||
else -> record.controller.isAllowed.collectAsCallbackWithLifecycle()
|
||||
else -> record.controller.isAllowedFlow.collectAsCallbackWithLifecycle()
|
||||
}
|
||||
|
||||
override fun isChangeable(record: AlarmsAndRemindersAppRecord) = record.isChangeable
|
||||
@@ -114,10 +114,11 @@ class AlarmsAndRemindersAppListModel(
|
||||
app = app,
|
||||
isTrumped = isTrumped,
|
||||
isChangeable = hasRequestPermission && !isTrumped,
|
||||
controller = AppOpsController(
|
||||
controller = AppOpsPermissionController(
|
||||
context = context,
|
||||
app = app,
|
||||
appOps = APP_OPS,
|
||||
permission = PERMISSION,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ public class FactoryResetPreferenceController extends BasePreferenceController {
|
||||
String packageName = resolution.activityInfo.packageName;
|
||||
PackageInfo factoryResetWizardPackageInfo;
|
||||
try {
|
||||
factoryResetWizardPackageInfo = pm.getPackageInfo(packageName, 0);
|
||||
factoryResetWizardPackageInfo = pm.getPackageInfo(packageName,
|
||||
PackageManager.GET_PERMISSIONS);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "Unable to resolve a Factory Reset Handler Application");
|
||||
return null;
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.android.settings.accessibility;
|
||||
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
|
||||
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;
|
||||
|
||||
@@ -148,4 +150,15 @@ public class MagnificationAlwaysOnPreferenceControllerTest {
|
||||
mController.updateState(mSwitchPreference);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.settings.accessibility;
|
||||
|
||||
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;
|
||||
|
||||
@@ -61,6 +63,17 @@ public class MagnificationFollowTypingPreferenceControllerTest {
|
||||
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
|
||||
public void performClick_switchDefaultStateForFollowTyping_shouldReturnFalse() {
|
||||
mSwitchPreference.performClick();
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.settings.accessibility;
|
||||
|
||||
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;
|
||||
|
||||
@@ -80,4 +82,15 @@ public class MagnificationJoystickPreferenceControllerTest {
|
||||
assertThat(mController.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.ON;
|
||||
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;
|
||||
|
||||
@@ -186,6 +188,17 @@ public class MagnificationOneFingerPanningPreferenceControllerTest {
|
||||
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() {
|
||||
return mContext.getString(
|
||||
R.string.accessibility_magnification_one_finger_panning_summary_on);
|
||||
|
||||
@@ -32,7 +32,6 @@ import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
@@ -97,7 +96,6 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest {
|
||||
verify(mFooterBarMixin).setPrimaryButton(any());
|
||||
assertThat(mFragment.mTopIntroPreference.isVisible()).isFalse();
|
||||
assertThat(mFragment.mSettingsPreference.isVisible()).isFalse();
|
||||
assertThat(mFragment.mFollowingTypingSwitchPreference.isVisible()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,7 +122,6 @@ public class ToggleScreenMagnificationPreferenceFragmentForSetupWizardTest {
|
||||
mPreferenceManager.setPreferences(mPreferenceManager.createPreferenceScreen(context));
|
||||
mTopIntroPreference = new TopIntroPreference(context);
|
||||
mSettingsPreference = new Preference(context);
|
||||
mFollowingTypingSwitchPreference = new SwitchPreferenceCompat(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -90,7 +90,7 @@ class FingerprintEnrollIntroFragmentTest {
|
||||
|
||||
private val navigationViewModel =
|
||||
FingerprintNavigationViewModel(
|
||||
Introduction,
|
||||
Introduction(),
|
||||
false,
|
||||
flowViewModel,
|
||||
interactor
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -145,10 +145,13 @@ public class FactoryResetPreferenceControllerTest {
|
||||
|
||||
@Test
|
||||
@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);
|
||||
|
||||
assertThat(mController.handlePreferenceTreeClick(mPreference)).isTrue();
|
||||
verify(mPackageManager).getPackageInfo(eq(FACTORY_RESET_APP_PACKAGE),
|
||||
eq(PackageManager.GET_PERMISSIONS));
|
||||
verify(mFactoryResetLauncher).launch(intentArgumentCaptor.capture());
|
||||
assertThat(intentArgumentCaptor.getValue()).isNotNull();
|
||||
assertThat(intentArgumentCaptor.getValue().getAction())
|
||||
|
||||
@@ -28,7 +28,7 @@ import platform.test.screenshot.ViewScreenshotTestRule.Mode
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class FingerprintEnrollIntroScreenshotTest {
|
||||
private val injector: Injector = Injector(FingerprintNavigationStep.Introduction)
|
||||
private val injector: Injector = Injector(FingerprintNavigationStep.Introduction())
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
|
||||
@@ -17,14 +17,13 @@
|
||||
package com.android.settings.spa.app.specialaccess
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AppOpsManager
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
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.template.app.AppOpPermissionRecord
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
@@ -117,14 +116,14 @@ class WifiControlAppListModelTest {
|
||||
app = APP_NOT_REQUEST_PERMISSION,
|
||||
hasRequestPermission = false,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
val appRequestedNetworkSettingsRecord =
|
||||
AppOpPermissionRecord(
|
||||
app = APP_REQUESTED_NETWORK_SETTINGS,
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
|
||||
val recordListFlow =
|
||||
@@ -144,7 +143,7 @@ class WifiControlAppListModelTest {
|
||||
app = APP,
|
||||
hasRequestPermission = false,
|
||||
hasRequestBroaderPermission = true,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
|
||||
val isAllowed = getIsAllowed(record)
|
||||
@@ -159,7 +158,7 @@ class WifiControlAppListModelTest {
|
||||
app = APP,
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(true),
|
||||
)
|
||||
|
||||
val isAllowed = getIsAllowed(record)
|
||||
@@ -174,7 +173,7 @@ class WifiControlAppListModelTest {
|
||||
app = APP,
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_IGNORED),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
|
||||
val isAllowed = getIsAllowed(record)
|
||||
@@ -189,7 +188,7 @@ class WifiControlAppListModelTest {
|
||||
app = APP,
|
||||
hasRequestPermission = false,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
|
||||
val isChangeable = listModel.isChangeable(record)
|
||||
@@ -198,13 +197,13 @@ class WifiControlAppListModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isChangeable_notChangableWhenRequestedNetworkSettingPermissions() {
|
||||
fun isChangeable_notChangeableWhenRequestedNetworkSettingPermissions() {
|
||||
val record =
|
||||
AppOpPermissionRecord(
|
||||
app = APP,
|
||||
hasRequestPermission = false,
|
||||
hasRequestBroaderPermission = true,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
|
||||
val isChangeable = listModel.isChangeable(record)
|
||||
@@ -213,13 +212,13 @@ class WifiControlAppListModelTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isChangeable_changableWhenRequestedChangeWifiStatePermission() {
|
||||
fun isChangeable_changeableWhenRequestedChangeWifiStatePermission() {
|
||||
val record =
|
||||
AppOpPermissionRecord(
|
||||
app = APP,
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
|
||||
val isChangeable = listModel.isChangeable(record)
|
||||
@@ -229,18 +228,18 @@ class WifiControlAppListModelTest {
|
||||
|
||||
@Test
|
||||
fun setAllowed_shouldCallController() {
|
||||
val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
|
||||
val appOpsPermissionController = FakeAppOpsPermissionController(false)
|
||||
val record =
|
||||
AppOpPermissionRecord(
|
||||
app = APP,
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = appOpsController,
|
||||
appOpsPermissionController = appOpsPermissionController,
|
||||
)
|
||||
|
||||
listModel.setAllowed(record = record, newAllowed = true)
|
||||
|
||||
assertThat(appOpsController.setAllowedCalledWith).isTrue()
|
||||
assertThat(appOpsPermissionController.setAllowedCalledWith).isTrue()
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
override val modeFlow = flowOf(fakeMode)
|
||||
override val isAllowedFlow = flowOf(allowed)
|
||||
|
||||
override fun setAllowed(allowed: Boolean) {
|
||||
setAllowedCalledWith = allowed
|
||||
}
|
||||
|
||||
override fun getMode() = fakeMode
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ import com.android.media.flags.Flags
|
||||
import com.android.settings.R
|
||||
import com.android.settings.testutils.FakeFeatureFactory
|
||||
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.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -50,7 +50,7 @@ class MediaRoutingControlTest {
|
||||
@get:Rule
|
||||
val mockito: MockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule();
|
||||
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
|
||||
|
||||
@Spy
|
||||
private val context: Context = ApplicationProvider.getApplicationContext()
|
||||
@@ -86,29 +86,29 @@ class MediaRoutingControlTest {
|
||||
|
||||
@Test
|
||||
fun setAllowed_callWithNewStatusAsTrue_shouldChangeAppControllerModeToAllowed() {
|
||||
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
|
||||
val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
|
||||
val permissionRequestedRecord =
|
||||
AppOpPermissionRecord(
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = fakeAppOpController,
|
||||
appOpsPermissionController = fakeAppOpsPermissionController,
|
||||
)
|
||||
|
||||
listModel.setAllowed(permissionRequestedRecord, true)
|
||||
|
||||
assertThat(fakeAppOpController.getMode()).isEqualTo(AppOpsManager.MODE_ALLOWED)
|
||||
assertThat(fakeAppOpsPermissionController.setAllowedCalledWith).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setAllowed_callWithNewStatusAsTrue_shouldLogPermissionToggleActionAsAllowed() {
|
||||
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
|
||||
val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
|
||||
val permissionRequestedRecord =
|
||||
AppOpPermissionRecord(
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = fakeAppOpController,
|
||||
appOpsPermissionController = fakeAppOpsPermissionController,
|
||||
)
|
||||
|
||||
listModel.setAllowed(permissionRequestedRecord, true)
|
||||
@@ -119,29 +119,29 @@ class MediaRoutingControlTest {
|
||||
|
||||
@Test
|
||||
fun setAllowed_callWithNewStatusAsFalse_shouldChangeAppControllerModeToErrored() {
|
||||
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
|
||||
val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
|
||||
val permissionRequestedRecord =
|
||||
AppOpPermissionRecord(
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = fakeAppOpController,
|
||||
appOpsPermissionController = fakeAppOpsPermissionController,
|
||||
)
|
||||
|
||||
listModel.setAllowed(permissionRequestedRecord, false)
|
||||
|
||||
assertThat(fakeAppOpController.getMode()).isEqualTo(AppOpsManager.MODE_ERRORED)
|
||||
assertThat(fakeAppOpsPermissionController.setAllowedCalledWith).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setAllowed_callWithNewStatusAsFalse_shouldLogPermissionToggleActionAsDenied() {
|
||||
val fakeAppOpController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
|
||||
val fakeAppOpsPermissionController = FakeAppOpsPermissionController(false)
|
||||
val permissionRequestedRecord =
|
||||
AppOpPermissionRecord(
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController = fakeAppOpController,
|
||||
appOpsPermissionController = fakeAppOpsPermissionController,
|
||||
)
|
||||
|
||||
listModel.setAllowed(permissionRequestedRecord, false)
|
||||
@@ -158,8 +158,7 @@ class MediaRoutingControlTest {
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController =
|
||||
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
|
||||
.thenReturn(listOf(PACKAGE_NAME))
|
||||
@@ -177,8 +176,7 @@ class MediaRoutingControlTest {
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = false,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController =
|
||||
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
|
||||
.thenReturn(listOf(PACKAGE_NAME))
|
||||
@@ -196,8 +194,7 @@ class MediaRoutingControlTest {
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController =
|
||||
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
|
||||
.thenReturn(listOf("other.package.name"))
|
||||
@@ -215,8 +212,7 @@ class MediaRoutingControlTest {
|
||||
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
|
||||
hasRequestPermission = true,
|
||||
hasRequestBroaderPermission = false,
|
||||
appOpsController =
|
||||
FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
|
||||
appOpsPermissionController = FakeAppOpsPermissionController(false),
|
||||
)
|
||||
whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
|
||||
.thenReturn(listOf(PACKAGE_NAME))
|
||||
@@ -226,15 +222,14 @@ class MediaRoutingControlTest {
|
||||
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) {
|
||||
modeFlow.value = if (allowed) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED
|
||||
setAllowedCalledWith = allowed
|
||||
}
|
||||
|
||||
override fun getMode(): Int = modeFlow.value
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -41,7 +41,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -754,12 +753,11 @@ public class UiccSlotUtilTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("b/337417975")
|
||||
public void onReceiveSimCardStateChangeReceiver_receiveAction_timerCountDown() {
|
||||
CountDownLatch latch = spy(new CountDownLatch(1));
|
||||
UiccSlotUtil.SimCardStateChangeReceiver receive =
|
||||
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);
|
||||
|
||||
receive.onReceive(mContext, intent);
|
||||
|
||||
Reference in New Issue
Block a user