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.car.settings.wifi; 18 19 import android.car.drivingstate.CarUxRestrictions; 20 import android.car.drivingstate.CarUxRestrictionsManager; 21 import android.content.Context; 22 import android.net.wifi.WifiConfiguration; 23 import android.net.wifi.WifiManager; 24 import android.widget.Toast; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.VisibleForTesting; 28 import androidx.preference.PreferenceGroup; 29 30 import com.android.car.settings.R; 31 import com.android.car.settings.common.FragmentController; 32 import com.android.car.settings.common.Logger; 33 import com.android.car.settings.wifi.details.WifiDetailsFragment; 34 import com.android.wifitrackerlib.WifiEntry; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * Renders a list of {@link WifiEntry} as a list of preferences. 41 */ 42 public class WifiEntryListPreferenceController extends 43 WifiBasePreferenceController<PreferenceGroup> implements 44 CarUxRestrictionsManager.OnUxRestrictionsChangedListener { 45 private static final Logger LOG = new Logger(WifiEntryListPreferenceController.class); 46 private final WifiManager.ActionListener mConnectionListener = 47 new WifiManager.ActionListener() { 48 @Override 49 public void onSuccess() { 50 LOG.d("connected to network"); 51 } 52 53 @Override 54 public void onFailure(int reason) { 55 LOG.d("Failed to connect to network. Failure code: " + reason); 56 Toast.makeText(getContext(), R.string.wifi_failed_connect_message, 57 Toast.LENGTH_SHORT).show(); 58 } 59 }; 60 61 @VisibleForTesting 62 final WifiPasswordDialog.WifiDialogListener mDialogListener = 63 new WifiPasswordDialog.WifiDialogListener() { 64 @Override 65 public void onSubmit(WifiPasswordDialog dialog) { 66 WifiConfiguration config = dialog.getConfig(); 67 WifiEntry wifiEntry = dialog.getWifiEntry(); 68 if (config == null) { 69 wifiEntry.connect( 70 new WifiEntryConnectCallback(wifiEntry, 71 /* editIfNoConfig= */ false)); 72 } else { 73 getWifiManager().connect(config, mConnectionListener); 74 } 75 } 76 }; 77 78 private List<WifiEntry> mWifiEntries = new ArrayList<>(); 79 WifiEntryListPreferenceController(@onNull Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)80 public WifiEntryListPreferenceController(@NonNull Context context, String preferenceKey, 81 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 82 super(context, preferenceKey, fragmentController, uxRestrictions); 83 } 84 85 @Override getPreferenceType()86 protected Class<PreferenceGroup> getPreferenceType() { 87 return PreferenceGroup.class; 88 } 89 90 @Override updateState(PreferenceGroup preferenceGroup)91 protected void updateState(PreferenceGroup preferenceGroup) { 92 if (getCarWifiManager() == null) { 93 return; 94 } 95 mWifiEntries = fetchWifiEntries(); 96 97 LOG.d("showing wifiEntries: " + mWifiEntries.size()); 98 99 preferenceGroup.setVisible(!mWifiEntries.isEmpty()); 100 preferenceGroup.removeAll(); 101 102 List<WifiEntry> connectedWifiEntries = getCarWifiManager().getConnectedWifiEntries(); 103 for (int i = 0; i < mWifiEntries.size(); i++) { 104 WifiEntry wifiEntry = mWifiEntries.get(i); 105 106 // fetchWifiEntries() sort the connected networks to the front 107 if (i < connectedWifiEntries.size()) { 108 preferenceGroup.addPreference( 109 createWifiEntryPreference(wifiEntry, /* connected= */ true)); 110 } else { 111 preferenceGroup.addPreference( 112 createWifiEntryPreference(wifiEntry, /* connected= */ false)); 113 } 114 } 115 } 116 117 @Override onApplyUxRestrictions(CarUxRestrictions uxRestrictions)118 protected void onApplyUxRestrictions(CarUxRestrictions uxRestrictions) { 119 // Since the list dynamically changes based on the UX restrictions, we enable this fragment 120 // regardless of the restriction. Intentional no-op. 121 } 122 123 @Override onWifiEntriesChanged()124 public void onWifiEntriesChanged() { 125 refreshUi(); 126 } 127 128 @Override onWifiStateChanged(int state)129 public void onWifiStateChanged(int state) { 130 if (state == WifiManager.WIFI_STATE_ENABLED) { 131 refreshUi(); 132 } 133 } 134 135 /** 136 * Get all {@link WifiEntry} that should be displayed as a list. 137 * @return List of wifi entries that should be displayed 138 */ fetchWifiEntries()139 protected List<WifiEntry> fetchWifiEntries() { 140 List<WifiEntry> wifiEntries; 141 // Do not populate the entries when DISALLOW_CONFIG_WIFI restriction is set. 142 Context context = getContext(); 143 if (WifiUtil.isConfigWifiRestrictedByUm(context) 144 || WifiUtil.isConfigWifiRestrictedByDpm(context)) { 145 wifiEntries = new ArrayList<>(); 146 } else if (shouldApplyUxRestrictions(getUxRestrictions())) { 147 wifiEntries = getCarWifiManager().getSavedWifiEntries(); 148 } else { 149 wifiEntries = getCarWifiManager().getAllWifiEntries(); 150 } 151 152 List<WifiEntry> connectedWifiEntries = getCarWifiManager().getConnectedWifiEntries(); 153 // Insert current connected networks as first items, if available 154 wifiEntries.addAll(0, connectedWifiEntries); 155 156 return wifiEntries; 157 } 158 159 @VisibleForTesting getWifiManager()160 WifiManager getWifiManager() { 161 return getContext().getSystemService(WifiManager.class); 162 } 163 createWifiEntryPreference(WifiEntry wifiEntry, boolean connected)164 private WifiEntryPreference createWifiEntryPreference(WifiEntry wifiEntry, boolean connected) { 165 LOG.d("Adding preference for " + WifiUtil.getKey(wifiEntry)); 166 WifiEntryPreference wifiEntryPreference = new WifiEntryPreference(getContext(), wifiEntry); 167 168 // Make connected secondary networks unclickable 169 if (!connected || wifiEntry.isPrimaryNetwork()) { 170 wifiEntryPreference.setOnPreferenceClickListener(pref -> { 171 if (connected) { 172 if (wifiEntry.canSignIn()) { 173 wifiEntry.signIn(/* callback= */ null); 174 } else { 175 getFragmentController().launchFragment( 176 WifiDetailsFragment.getInstance(wifiEntry)); 177 } 178 } else if (wifiEntry.shouldEditBeforeConnect()) { 179 getFragmentController().showDialog( 180 new WifiPasswordDialog(wifiEntry, mDialogListener), 181 WifiPasswordDialog.TAG); 182 } else { 183 wifiEntry.connect( 184 new WifiEntryConnectCallback(wifiEntry, /* editIfNoConfig= */ true)); 185 } 186 return true; 187 }); 188 } 189 190 if (wifiEntry.isSaved()) { 191 wifiEntryPreference.setSecondaryActionIcon(R.drawable.ic_delete); 192 wifiEntryPreference.setOnSecondaryActionClickListener( 193 () -> wifiEntry.forget(/* callback= */ null)); 194 wifiEntryPreference.setSecondaryActionVisible(true); 195 } 196 197 // Since this preference is dynamically created, it doesn't have the dpm behaviors set 198 wifiEntryPreference.setEnabled(getPreference().isEnabled()); 199 wifiEntryPreference.setClickableWhileDisabled(true); 200 wifiEntryPreference.setDisabledClickListener(p -> 201 WifiUtil.runClickableWhileDisabled(getContext(), getFragmentController())); 202 203 return wifiEntryPreference; 204 } 205 206 private class WifiEntryConnectCallback implements WifiEntry.ConnectCallback { 207 final WifiEntry mConnectWifiEntry; 208 final boolean mEditIfNoConfig; 209 WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig)210 WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig) { 211 mConnectWifiEntry = connectWifiEntry; 212 mEditIfNoConfig = editIfNoConfig; 213 } 214 215 @Override onConnectResult(@onnectStatus int status)216 public void onConnectResult(@ConnectStatus int status) { 217 if (!isStarted()) { 218 return; 219 } 220 221 if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) { 222 if (mEditIfNoConfig) { 223 getFragmentController().showDialog( 224 new WifiPasswordDialog(mConnectWifiEntry, mDialogListener), 225 WifiPasswordDialog.TAG); 226 } 227 } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { 228 Toast.makeText(getContext(), R.string.wifi_failed_connect_message, 229 Toast.LENGTH_SHORT).show(); 230 } 231 } 232 } 233 } 234