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 35 /** 36 * A custom preference that provides inline switch toggle. It has a mandatory field for title, and 37 * optional fields for icon and sub-text. And it can be restricted by admin state. 38 */ 39 public class PrimarySwitchPreference extends RestrictedPreference { 40 41 private CompoundButton mSwitch; 42 private boolean mChecked; 43 private boolean mCheckedSet; 44 private boolean mEnableSwitch = true; 45 PrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)46 public PrimarySwitchPreference(Context context, AttributeSet attrs, 47 int defStyleAttr, int defStyleRes) { 48 super(context, attrs, defStyleAttr, defStyleRes); 49 } 50 PrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr)51 public PrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { 52 super(context, attrs, defStyleAttr); 53 } 54 PrimarySwitchPreference(Context context, AttributeSet attrs)55 public PrimarySwitchPreference(Context context, AttributeSet attrs) { 56 super(context, attrs); 57 } 58 PrimarySwitchPreference(Context context)59 public PrimarySwitchPreference(Context context) { 60 super(context); 61 } 62 63 @Override getSecondTargetResId()64 protected int getSecondTargetResId() { 65 return androidx.preference.R.layout.preference_widget_switch_compat; 66 } 67 68 @Override onBindViewHolder(PreferenceViewHolder holder)69 public void onBindViewHolder(PreferenceViewHolder holder) { 70 super.onBindViewHolder(holder); 71 final View widgetFrame = holder.findViewById(android.R.id.widget_frame); 72 if (widgetFrame instanceof LinearLayout linearLayout) { 73 linearLayout.setGravity(Gravity.END | Gravity.CENTER_VERTICAL); 74 } 75 mSwitch = (CompoundButton) holder.findViewById(androidx.preference.R.id.switchWidget); 76 if (mSwitch != null) { 77 mSwitch.setOnClickListener(v -> { 78 if (mSwitch != null && !mSwitch.isEnabled()) { 79 return; 80 } 81 final boolean newChecked = !mChecked; 82 if (callChangeListener(newChecked)) { 83 SettingsJankMonitor.detectToggleJank(getKey(), mSwitch); 84 setChecked(newChecked); 85 persistBoolean(newChecked); 86 } 87 }); 88 89 // Consumes move events to ignore drag actions. 90 mSwitch.setOnTouchListener((v, event) -> { 91 return event.getActionMasked() == MotionEvent.ACTION_MOVE; 92 }); 93 94 mSwitch.setContentDescription(getTitle()); 95 mSwitch.setChecked(mChecked); 96 mSwitch.setEnabled(mEnableSwitch); 97 } 98 } 99 isChecked()100 public boolean isChecked() { 101 return mSwitch != null && mChecked; 102 } 103 104 /** 105 * Used to validate the state of mChecked and mCheckedSet when testing, without requiring 106 * that a ViewHolder be bound to the object. 107 */ 108 @Keep 109 @Nullable getCheckedState()110 public Boolean getCheckedState() { 111 return mCheckedSet ? mChecked : null; 112 } 113 114 /** 115 * Set the checked status to be {@code checked}. 116 * 117 * @param checked The new checked status 118 */ setChecked(boolean checked)119 public void setChecked(boolean checked) { 120 // Always set checked the first time; don't assume the field's default of false. 121 final boolean changed = mChecked != checked; 122 if (changed || !mCheckedSet) { 123 mChecked = checked; 124 mCheckedSet = true; 125 if (mSwitch != null) { 126 mSwitch.setChecked(checked); 127 } 128 } 129 } 130 131 /** 132 * Set the Switch to be the status of {@code enabled}. 133 * 134 * @param enabled The new enabled status 135 */ setSwitchEnabled(boolean enabled)136 public void setSwitchEnabled(boolean enabled) { 137 mEnableSwitch = enabled; 138 if (mSwitch != null) { 139 mSwitch.setEnabled(enabled); 140 } 141 } 142 143 @VisibleForTesting(otherwise = VisibleForTesting.NONE) isSwitchEnabled()144 public boolean isSwitchEnabled() { 145 return mEnableSwitch; 146 } 147 148 /** 149 * If admin is not null, disables the switch. 150 * Otherwise, keep it enabled. 151 */ setDisabledByAdmin(EnforcedAdmin admin)152 public void setDisabledByAdmin(EnforcedAdmin admin) { 153 super.setDisabledByAdmin(admin); 154 setSwitchEnabled(admin == null); 155 } 156 getSwitch()157 public CompoundButton getSwitch() { 158 return mSwitch; 159 } 160 161 @Override shouldHideSecondTarget()162 protected boolean shouldHideSecondTarget() { 163 return getSecondTargetResId() == 0; 164 } 165 } 166