1 /*
2  * Copyright (C) 2019 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.developeroptions.wifi.tether;
18 
19 import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
20 import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION;
21 
22 import android.app.settings.SettingsEnums;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.net.wifi.WifiConfiguration;
28 import android.net.wifi.WifiManager;
29 import android.os.Bundle;
30 import android.os.UserManager;
31 import android.provider.SearchIndexableResource;
32 import android.util.Log;
33 
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.car.developeroptions.R;
37 import com.android.car.developeroptions.SettingsActivity;
38 import com.android.car.developeroptions.dashboard.RestrictedDashboardFragment;
39 import com.android.car.developeroptions.search.BaseSearchIndexProvider;
40 import com.android.car.developeroptions.widget.SwitchBar;
41 import com.android.car.developeroptions.widget.SwitchBarController;
42 import com.android.settingslib.TetherUtil;
43 import com.android.settingslib.core.AbstractPreferenceController;
44 import com.android.settingslib.search.SearchIndexable;
45 
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.List;
49 
50 @SearchIndexable
51 public class WifiTetherSettings extends RestrictedDashboardFragment
52         implements WifiTetherBasePreferenceController.OnTetherConfigUpdateListener {
53 
54     private static final String TAG = "WifiTetherSettings";
55     private static final IntentFilter TETHER_STATE_CHANGE_FILTER;
56     private static final String KEY_WIFI_TETHER_SCREEN = "wifi_tether_settings_screen";
57     @VisibleForTesting
58     static final String KEY_WIFI_TETHER_NETWORK_NAME = "wifi_tether_network_name";
59     @VisibleForTesting
60     static final String KEY_WIFI_TETHER_NETWORK_PASSWORD = "wifi_tether_network_password";
61     @VisibleForTesting
62     static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off";
63     @VisibleForTesting
64     static final String KEY_WIFI_TETHER_NETWORK_AP_BAND = "wifi_tether_network_ap_band";
65 
66     private WifiTetherSwitchBarController mSwitchBarController;
67     private WifiTetherSSIDPreferenceController mSSIDPreferenceController;
68     private WifiTetherPasswordPreferenceController mPasswordPreferenceController;
69     private WifiTetherApBandPreferenceController mApBandPreferenceController;
70     private WifiTetherSecurityPreferenceController mSecurityPreferenceController;
71 
72     private WifiManager mWifiManager;
73     private boolean mRestartWifiApAfterConfigChange;
74     private boolean mUnavailable;
75 
76     @VisibleForTesting
77     TetherChangeReceiver mTetherChangeReceiver;
78 
79     static {
80         TETHER_STATE_CHANGE_FILTER = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
81         TETHER_STATE_CHANGE_FILTER.addAction(WIFI_AP_STATE_CHANGED_ACTION);
82     }
83 
WifiTetherSettings()84     public WifiTetherSettings() {
85         super(UserManager.DISALLOW_CONFIG_TETHERING);
86     }
87 
88     @Override
getMetricsCategory()89     public int getMetricsCategory() {
90         return SettingsEnums.WIFI_TETHER_SETTINGS;
91     }
92 
93     @Override
getLogTag()94     protected String getLogTag() {
95         return "WifiTetherSettings";
96     }
97 
98     @Override
onCreate(Bundle icicle)99     public void onCreate(Bundle icicle) {
100         super.onCreate(icicle);
101         setIfOnlyAvailableForAdmins(true);
102         if (isUiRestricted()) {
103             mUnavailable = true;
104         }
105     }
106 
107     @Override
onAttach(Context context)108     public void onAttach(Context context) {
109         super.onAttach(context);
110         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
111         mTetherChangeReceiver = new TetherChangeReceiver();
112 
113         mSSIDPreferenceController = use(WifiTetherSSIDPreferenceController.class);
114         mSecurityPreferenceController = use(WifiTetherSecurityPreferenceController.class);
115         mPasswordPreferenceController = use(WifiTetherPasswordPreferenceController.class);
116         mApBandPreferenceController = use(WifiTetherApBandPreferenceController.class);
117     }
118 
119     @Override
onActivityCreated(Bundle savedInstanceState)120     public void onActivityCreated(Bundle savedInstanceState) {
121         super.onActivityCreated(savedInstanceState);
122         if (mUnavailable) {
123             return;
124         }
125         // Assume we are in a SettingsActivity. This is only safe because we currently use
126         // SettingsActivity as base for all preference fragments.
127         final SettingsActivity activity = (SettingsActivity) getActivity();
128         final SwitchBar switchBar = activity.getSwitchBar();
129         mSwitchBarController = new WifiTetherSwitchBarController(activity,
130                 new SwitchBarController(switchBar));
131         getSettingsLifecycle().addObserver(mSwitchBarController);
132         switchBar.show();
133     }
134 
135     @Override
onStart()136     public void onStart() {
137         super.onStart();
138         if (mUnavailable) {
139             if (!isUiRestrictedByOnlyAdmin()) {
140                 getEmptyTextView().setText(R.string.tethering_settings_not_available);
141             }
142             getPreferenceScreen().removeAll();
143             return;
144         }
145         final Context context = getContext();
146         if (context != null) {
147             context.registerReceiver(mTetherChangeReceiver, TETHER_STATE_CHANGE_FILTER);
148         }
149     }
150 
151     @Override
onStop()152     public void onStop() {
153         super.onStop();
154         if (mUnavailable) {
155             return;
156         }
157         final Context context = getContext();
158         if (context != null) {
159             context.unregisterReceiver(mTetherChangeReceiver);
160         }
161     }
162 
163 
164     @Override
getPreferenceScreenResId()165     protected int getPreferenceScreenResId() {
166         return R.xml.wifi_tether_settings;
167     }
168 
169     @Override
createPreferenceControllers(Context context)170     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
171         return buildPreferenceControllers(context, this::onTetherConfigUpdated);
172     }
173 
buildPreferenceControllers(Context context, WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener)174     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
175             WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) {
176         final List<AbstractPreferenceController> controllers = new ArrayList<>();
177         controllers.add(new WifiTetherSSIDPreferenceController(context, listener));
178         controllers.add(new WifiTetherSecurityPreferenceController(context, listener));
179         controllers.add(new WifiTetherPasswordPreferenceController(context, listener));
180         controllers.add(new WifiTetherApBandPreferenceController(context, listener));
181         controllers.add(
182                 new WifiTetherAutoOffPreferenceController(context, KEY_WIFI_TETHER_AUTO_OFF));
183 
184         return controllers;
185     }
186 
187     @Override
onTetherConfigUpdated()188     public void onTetherConfigUpdated() {
189         final WifiConfiguration config = buildNewConfig();
190         mPasswordPreferenceController.updateVisibility(config.getAuthType());
191 
192         /**
193          * if soft AP is stopped, bring up
194          * else restart with new config
195          * TODO: update config on a running access point when framework support is added
196          */
197         if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
198             Log.d("TetheringSettings",
199                     "Wifi AP config changed while enabled, stop and restart");
200             mRestartWifiApAfterConfigChange = true;
201             mSwitchBarController.stopTether();
202         }
203         mWifiManager.setWifiApConfiguration(config);
204     }
205 
buildNewConfig()206     private WifiConfiguration buildNewConfig() {
207         final WifiConfiguration config = new WifiConfiguration();
208         final int securityType = mSecurityPreferenceController.getSecurityType();
209 
210         config.SSID = mSSIDPreferenceController.getSSID();
211         config.allowedKeyManagement.set(securityType);
212         config.preSharedKey = mPasswordPreferenceController.getPasswordValidated(securityType);
213         config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
214         config.apBand = mApBandPreferenceController.getBandIndex();
215         return config;
216     }
217 
startTether()218     private void startTether() {
219         mRestartWifiApAfterConfigChange = false;
220         mSwitchBarController.startTether();
221     }
222 
updateDisplayWithNewConfig()223     private void updateDisplayWithNewConfig() {
224         use(WifiTetherSSIDPreferenceController.class)
225                 .updateDisplay();
226         use(WifiTetherSecurityPreferenceController.class)
227                 .updateDisplay();
228         use(WifiTetherPasswordPreferenceController.class)
229                 .updateDisplay();
230         use(WifiTetherApBandPreferenceController.class)
231                 .updateDisplay();
232     }
233 
234     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
235             new BaseSearchIndexProvider() {
236                 @Override
237                 public List<SearchIndexableResource> getXmlResourcesToIndex(
238                         Context context, boolean enabled) {
239                     final SearchIndexableResource sir = new SearchIndexableResource(context);
240                     sir.xmlResId = R.xml.wifi_tether_settings;
241                     return Arrays.asList(sir);
242                 }
243 
244                 @Override
245                 public List<String> getNonIndexableKeys(Context context) {
246                     final List<String> keys = super.getNonIndexableKeys(context);
247 
248                     if (!TetherUtil.isTetherAvailable(context)) {
249                         keys.add(KEY_WIFI_TETHER_NETWORK_NAME);
250                         keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD);
251                         keys.add(KEY_WIFI_TETHER_AUTO_OFF);
252                         keys.add(KEY_WIFI_TETHER_NETWORK_AP_BAND);
253                     }
254 
255                     // Remove duplicate
256                     keys.add(KEY_WIFI_TETHER_SCREEN);
257                     return keys;
258                 }
259 
260                 @Override
261                 public List<AbstractPreferenceController> createPreferenceControllers(
262                         Context context) {
263                     return buildPreferenceControllers(context, null /* listener */);
264                 }
265             };
266 
267     @VisibleForTesting
268     class TetherChangeReceiver extends BroadcastReceiver {
269         @Override
onReceive(Context content, Intent intent)270         public void onReceive(Context content, Intent intent) {
271             String action = intent.getAction();
272             Log.d(TAG, "updating display config due to receiving broadcast action " + action);
273             updateDisplayWithNewConfig();
274             if (action.equals(ACTION_TETHER_STATE_CHANGED)) {
275                 if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_DISABLED
276                         && mRestartWifiApAfterConfigChange) {
277                     startTether();
278                 }
279             } else if (action.equals(WIFI_AP_STATE_CHANGED_ACTION)) {
280                 int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, 0);
281                 if (state == WifiManager.WIFI_AP_STATE_DISABLED
282                         && mRestartWifiApAfterConfigChange) {
283                     startTether();
284                 }
285             }
286         }
287     }
288 }
289