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.SoftApConfiguration;
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 SoftApConfiguration config = buildNewConfig();
190         mPasswordPreferenceController.updateVisibility(config.getSecurityType());
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.setSoftApConfiguration(config);
204     }
205 
buildNewConfig()206     private SoftApConfiguration buildNewConfig() {
207         final int securityType = mSecurityPreferenceController.getSecurityType();
208 
209         return new SoftApConfiguration.Builder()
210                 .setSsid(mSSIDPreferenceController.getSSID())
211                 .setPassphrase(mPasswordPreferenceController.getPasswordValidated(securityType),
212                         securityType)
213                 .setBand(mApBandPreferenceController.getBand())
214                 .build();
215     }
216 
startTether()217     private void startTether() {
218         mRestartWifiApAfterConfigChange = false;
219         mSwitchBarController.startTether();
220     }
221 
updateDisplayWithNewConfig()222     private void updateDisplayWithNewConfig() {
223         use(WifiTetherSSIDPreferenceController.class)
224                 .updateDisplay();
225         use(WifiTetherSecurityPreferenceController.class)
226                 .updateDisplay();
227         use(WifiTetherPasswordPreferenceController.class)
228                 .updateDisplay();
229         use(WifiTetherApBandPreferenceController.class)
230                 .updateDisplay();
231     }
232 
233     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
234             new BaseSearchIndexProvider() {
235                 @Override
236                 public List<SearchIndexableResource> getXmlResourcesToIndex(
237                         Context context, boolean enabled) {
238                     final SearchIndexableResource sir = new SearchIndexableResource(context);
239                     sir.xmlResId = R.xml.wifi_tether_settings;
240                     return Arrays.asList(sir);
241                 }
242 
243                 @Override
244                 public List<String> getNonIndexableKeys(Context context) {
245                     final List<String> keys = super.getNonIndexableKeys(context);
246 
247                     if (!TetherUtil.isTetherAvailable(context)) {
248                         keys.add(KEY_WIFI_TETHER_NETWORK_NAME);
249                         keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD);
250                         keys.add(KEY_WIFI_TETHER_AUTO_OFF);
251                         keys.add(KEY_WIFI_TETHER_NETWORK_AP_BAND);
252                     }
253 
254                     // Remove duplicate
255                     keys.add(KEY_WIFI_TETHER_SCREEN);
256                     return keys;
257                 }
258 
259                 @Override
260                 public List<AbstractPreferenceController> createPreferenceControllers(
261                         Context context) {
262                     return buildPreferenceControllers(context, null /* listener */);
263                 }
264             };
265 
266     @VisibleForTesting
267     class TetherChangeReceiver extends BroadcastReceiver {
268         @Override
onReceive(Context content, Intent intent)269         public void onReceive(Context content, Intent intent) {
270             String action = intent.getAction();
271             Log.d(TAG, "updating display config due to receiving broadcast action " + action);
272             updateDisplayWithNewConfig();
273             if (action.equals(ACTION_TETHER_STATE_CHANGED)) {
274                 if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_DISABLED
275                         && mRestartWifiApAfterConfigChange) {
276                     startTether();
277                 }
278             } else if (action.equals(WIFI_AP_STATE_CHANGED_ACTION)) {
279                 int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, 0);
280                 if (state == WifiManager.WIFI_AP_STATE_DISABLED
281                         && mRestartWifiApAfterConfigChange) {
282                     startTether();
283                 }
284             }
285         }
286     }
287 }
288