1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib; 18 19 import android.content.Context; 20 import android.util.AttributeSet; 21 import android.view.Gravity; 22 import android.view.MotionEvent; 23 import android.view.View; 24 import android.widget.CompoundButton; 25 import android.widget.LinearLayout; 26 27 import androidx.annotation.Keep; 28 import androidx.annotation.Nullable; 29 import androidx.annotation.VisibleForTesting; 30 import androidx.preference.PreferenceViewHolder; 31 32 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 33 import com.android.settingslib.core.instrumentation.SettingsJankMonitor; 34 import com.android.settingslib.widget.SettingsThemeHelper; 35 import com.android.settingslib.widget.theme.R; 36 37 /** 38 * A custom preference that provides inline switch toggle. It has a mandatory field for title, and 39 * optional fields for icon and sub-text. And it can be restricted by admin state. 40 */ 41 public class PrimarySwitchPreference extends RestrictedPreference { 42 43 private CompoundButton mSwitch; 44 private boolean mChecked; 45 private boolean mCheckedSet; 46 private boolean mEnableSwitch = true; 47 PrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)48 public PrimarySwitchPreference(Context context, AttributeSet attrs, 49 int defStyleAttr, int defStyleRes) { 50 super(context, attrs, defStyleAttr, defStyleRes); 51 } 52 PrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr)53 public PrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { 54 super(context, attrs, defStyleAttr); 55 } 56 PrimarySwitchPreference(Context context, AttributeSet attrs)57 public PrimarySwitchPreference(Context context, AttributeSet attrs) { 58 super(context, attrs); 59 } 60 PrimarySwitchPreference(Context context)61 public PrimarySwitchPreference(Context context) { 62 super(context); 63 } 64 65 @Override getSecondTargetResId()66 protected int getSecondTargetResId() { 67 return SettingsThemeHelper.isExpressiveTheme(getContext()) 68 ? R.layout.settingslib_expressive_preference_switch 69 : androidx.preference.R.layout.preference_widget_switch_compat; 70 } 71 72 @Override onBindViewHolder(PreferenceViewHolder holder)73 public void onBindViewHolder(PreferenceViewHolder holder) { 74 super.onBindViewHolder(holder); 75 final View widgetFrame = holder.findViewById(android.R.id.widget_frame); 76 if (widgetFrame instanceof LinearLayout linearLayout) { 77 linearLayout.setGravity(Gravity.END | Gravity.CENTER_VERTICAL); 78 } 79 mSwitch = (CompoundButton) holder.findViewById(androidx.preference.R.id.switchWidget); 80 if (mSwitch != null) { 81 mSwitch.setOnClickListener(v -> { 82 if (mSwitch != null && !mSwitch.isEnabled()) { 83 return; 84 } 85 final boolean newChecked = !mChecked; 86 if (callChangeListener(newChecked)) { 87 SettingsJankMonitor.detectToggleJank(getKey(), mSwitch); 88 setChecked(newChecked); 89 persistBoolean(newChecked); 90 } 91 }); 92 93 // Consumes move events to ignore drag actions. 94 mSwitch.setOnTouchListener((v, event) -> { 95 return event.getActionMasked() == MotionEvent.ACTION_MOVE; 96 }); 97 98 mSwitch.setContentDescription(getTitle()); 99 mSwitch.setChecked(mChecked); 100 mSwitch.setEnabled(mEnableSwitch); 101 } 102 } 103 isChecked()104 public boolean isChecked() { 105 return mSwitch != null && mChecked; 106 } 107 108 /** 109 * Used to validate the state of mChecked and mCheckedSet when testing, without requiring 110 * that a ViewHolder be bound to the object. 111 */ 112 @Keep 113 @Nullable getCheckedState()114 public Boolean getCheckedState() { 115 return mCheckedSet ? mChecked : null; 116 } 117 118 /** 119 * Set the checked status to be {@code checked}. 120 * 121 * @param checked The new checked status 122 */ setChecked(boolean checked)123 public void setChecked(boolean checked) { 124 // Always set checked the first time; don't assume the field's default of false. 125 final boolean changed = mChecked != checked; 126 if (changed || !mCheckedSet) { 127 mChecked = checked; 128 mCheckedSet = true; 129 if (mSwitch != null) { 130 mSwitch.setChecked(checked); 131 } 132 } 133 } 134 135 /** 136 * Set the Switch to be the status of {@code enabled}. 137 * 138 * @param enabled The new enabled status 139 */ setSwitchEnabled(boolean enabled)140 public void setSwitchEnabled(boolean enabled) { 141 mEnableSwitch = enabled; 142 if (mSwitch != null) { 143 mSwitch.setEnabled(enabled); 144 } 145 } 146 147 @VisibleForTesting(otherwise = VisibleForTesting.NONE) isSwitchEnabled()148 public boolean isSwitchEnabled() { 149 return mEnableSwitch; 150 } 151 152 /** 153 * If admin is not null, disables the switch. 154 * Otherwise, keep it enabled. 155 */ setDisabledByAdmin(EnforcedAdmin admin)156 public void setDisabledByAdmin(EnforcedAdmin admin) { 157 super.setDisabledByAdmin(admin); 158 setSwitchEnabled(admin == null); 159 } 160 getSwitch()161 public CompoundButton getSwitch() { 162 return mSwitch; 163 } 164 165 @Override shouldHideSecondTarget()166 protected boolean shouldHideSecondTarget() { 167 return getSecondTargetResId() == 0; 168 } 169 } 170