1 /* 2 * Copyright (C) 2018 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 package com.android.settings.widget; 17 18 import android.app.AlertDialog; 19 import android.content.Context; 20 import android.content.DialogInterface; 21 import android.net.wifi.WifiConfiguration; 22 import android.os.Bundle; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.support.annotation.VisibleForTesting; 26 import android.util.AttributeSet; 27 import android.view.View; 28 import android.widget.Button; 29 import android.widget.CheckBox; 30 import android.widget.CompoundButton; 31 import android.widget.LinearLayout; 32 33 import com.android.settings.R; 34 import com.android.settingslib.CustomDialogPreference; 35 36 import java.util.ArrayList; 37 38 public class HotspotApBandSelectionPreference extends CustomDialogPreference implements 39 CompoundButton.OnCheckedChangeListener, DialogInterface.OnShowListener { 40 private static final int UNSET = Integer.MIN_VALUE; 41 42 @VisibleForTesting 43 static final String KEY_CHECKED_BANDS = "checked_bands"; 44 @VisibleForTesting 45 static final String KEY_HOTSPOT_SUPER_STATE = "hotspot_super_state"; 46 47 @VisibleForTesting 48 CheckBox mBox2G; 49 @VisibleForTesting 50 CheckBox mBox5G; 51 @VisibleForTesting 52 ArrayList<Integer> mRestoredBands; 53 @VisibleForTesting 54 boolean mShouldRestore; 55 56 private String[] mBandEntries; 57 private int mExistingConfigValue = UNSET; 58 59 public HotspotApBandSelectionPreference(Context context) { 60 super(context); 61 } 62 63 public HotspotApBandSelectionPreference(Context context, AttributeSet attrs) { 64 super(context, attrs); 65 } 66 67 public HotspotApBandSelectionPreference(Context context, AttributeSet attrs, int defStyleAttr) { 68 super(context, attrs, defStyleAttr); 69 } 70 71 public HotspotApBandSelectionPreference(Context context, AttributeSet attrs, int defStyleAttr, 72 int defStyleRes) { 73 super(context, attrs, defStyleAttr, defStyleRes); 74 } 75 76 @Override 77 protected void onRestoreInstanceState(Parcelable state) { 78 SavedState myState = (SavedState) state; 79 80 super.onRestoreInstanceState(myState.getSuperState()); 81 82 mShouldRestore = myState.shouldRestore; 83 if (mShouldRestore) { 84 mRestoredBands = new ArrayList<>(); 85 if (myState.enabled2G) { 86 mRestoredBands.add(WifiConfiguration.AP_BAND_2GHZ); 87 } 88 if (myState.enabled5G) { 89 mRestoredBands.add(WifiConfiguration.AP_BAND_5GHZ); 90 } 91 } else { 92 mRestoredBands = null; 93 } 94 updatePositiveButton(); 95 } 96 97 @Override 98 protected void onBindDialogView(View view) { 99 super.onBindDialogView(view); 100 final Context context = getContext(); 101 102 // Register so we can adjust the buttons if needed once the dialog is available. 103 setOnShowListener(this); 104 105 mBandEntries = context.getResources().getStringArray(R.array.wifi_ap_band_config_full); 106 // add a checkbox for every band entry. 107 addApBandViews((LinearLayout) view); 108 // try to update the button just in case we already missed the onShow call. 109 updatePositiveButton(); 110 // clear any saved state so it doesn't leak across multiple rotations/dialog closings 111 mRestoredBands = null; 112 mShouldRestore = false; 113 } 114 115 @Override 116 protected Parcelable onSaveInstanceState() { 117 final Parcelable superState = super.onSaveInstanceState(); 118 119 SavedState myState = new SavedState(superState); 120 myState.shouldRestore = getDialog() != null; 121 myState.enabled2G = mBox2G != null && mBox2G.isChecked(); 122 myState.enabled5G = mBox5G != null && mBox5G.isChecked(); 123 return myState; 124 } 125 126 @Override 127 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 128 if (!(buttonView instanceof CheckBox)) { 129 return; 130 } 131 updatePositiveButton(); 132 } 133 134 @Override 135 protected void onClick(DialogInterface dialog, int which) { 136 // we only want to persist our enabled bands if apply is clicked 137 if (which == DialogInterface.BUTTON_POSITIVE) { 138 if (mBox2G.isChecked() || mBox5G.isChecked()) { 139 int wifiBand = getWifiBand(); 140 mExistingConfigValue = wifiBand; 141 callChangeListener(wifiBand); 142 } 143 } 144 } 145 146 /** 147 * Used to set the band selection for the preference if one already exists 148 * @param band the band to set it to from {@link WifiConfiguration} 149 */ 150 public void setExistingConfigValue(int band) { 151 mExistingConfigValue = band; 152 } 153 154 private void addApBandViews(LinearLayout view) { 155 mBox2G = view.findViewById(R.id.box_2g); 156 mBox2G.setText(mBandEntries[WifiConfiguration.AP_BAND_2GHZ]); 157 mBox2G.setChecked(restoreBandIfNeeded(WifiConfiguration.AP_BAND_2GHZ)); 158 mBox2G.setOnCheckedChangeListener(this); 159 160 mBox5G = view.findViewById(R.id.box_5g); 161 mBox5G.setText(mBandEntries[WifiConfiguration.AP_BAND_5GHZ]); 162 mBox5G.setChecked(restoreBandIfNeeded(WifiConfiguration.AP_BAND_5GHZ)); 163 mBox5G.setOnCheckedChangeListener(this); 164 } 165 166 private boolean restoreBandIfNeeded(int band) { 167 // Only use the provided config if we aren't restoring, restore if state available 168 return (isBandPreviouslySelected(band) && !mShouldRestore) 169 || (mShouldRestore && mRestoredBands.contains(band)); 170 } 171 172 private void updatePositiveButton() { 173 AlertDialog dialog = (AlertDialog) getDialog(); 174 Button button = dialog == null ? null : dialog.getButton(DialogInterface.BUTTON_POSITIVE); 175 if (button != null && mBox5G != null && mBox2G != null) { 176 button.setEnabled(mBox2G.isChecked() || mBox5G.isChecked()); 177 } 178 } 179 180 @VisibleForTesting 181 int getWifiBand() { 182 final boolean checked_2g = mBox2G.isChecked(); 183 final boolean checked_5g = mBox5G.isChecked(); 184 if (checked_2g && checked_5g) { 185 return WifiConfiguration.AP_BAND_ANY; 186 } else if (checked_2g && !checked_5g) { 187 return WifiConfiguration.AP_BAND_2GHZ; 188 } else if (checked_5g && !checked_2g) { 189 return WifiConfiguration.AP_BAND_5GHZ; 190 } else { 191 throw new IllegalStateException("Wifi Config only supports selecting one or all bands"); 192 } 193 } 194 195 private boolean isBandPreviouslySelected(int bandIndex) { 196 switch(mExistingConfigValue) { 197 case WifiConfiguration.AP_BAND_ANY: 198 return true; 199 case WifiConfiguration.AP_BAND_2GHZ: 200 return bandIndex == 0; 201 case WifiConfiguration.AP_BAND_5GHZ: 202 return bandIndex == 1; 203 case UNSET: 204 default: 205 return false; 206 } 207 } 208 209 @Override 210 public void onShow(DialogInterface dialog) { 211 updatePositiveButton(); 212 } 213 214 private static class SavedState extends BaseSavedState { 215 boolean shouldRestore; 216 boolean enabled2G; 217 boolean enabled5G; 218 219 public SavedState(Parcelable source) { 220 super(source); 221 } 222 223 private SavedState(Parcel in) { 224 super(in); 225 shouldRestore = in.readByte() == 1; 226 enabled2G = in.readByte() == 1; 227 enabled5G = in.readByte() == 1; 228 } 229 230 @Override 231 public void writeToParcel(Parcel dest, int flags) { 232 super.writeToParcel(dest, flags); 233 dest.writeByte((byte) (shouldRestore ? 1 : 0)); 234 dest.writeByte((byte) (enabled2G ? 1: 0)); 235 dest.writeByte((byte) (enabled5G ? 1 : 0)); 236 } 237 238 @Override 239 public String toString() { 240 return "HotspotApBandSelectionPreference.SavedState{" 241 + Integer.toHexString(System.identityHashCode(this)) 242 + " shouldRestore=" + shouldRestore 243 + " enabled2G=" + enabled2G 244 + " enabled5G=" + enabled5G + "}"; 245 } 246 247 public static final Parcelable.Creator<SavedState> CREATOR 248 = new Parcelable.Creator<SavedState>() { 249 public SavedState createFromParcel(Parcel in) { 250 return new SavedState(in); 251 } 252 253 public SavedState[] newArray(int size) { 254 return new SavedState[size]; 255 } 256 }; 257 } 258 } 259