1 /*
2  * Copyright (C) 2017 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.settings.wifi.tether;
18 
19 import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION;
20 import static android.view.View.INVISIBLE;
21 import static android.view.View.VISIBLE;
22 
23 import static com.android.settings.wifi.WifiUtils.canShowWifiHotspot;
24 
25 import android.app.settings.SettingsEnums;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.net.wifi.SoftApConfiguration;
31 import android.os.Bundle;
32 import android.os.UserManager;
33 import android.util.Log;
34 
35 import androidx.annotation.Nullable;
36 import androidx.annotation.VisibleForTesting;
37 import androidx.preference.Preference;
38 
39 import com.android.settings.R;
40 import com.android.settings.SettingsActivity;
41 import com.android.settings.dashboard.RestrictedDashboardFragment;
42 import com.android.settings.overlay.FeatureFactory;
43 import com.android.settings.search.BaseSearchIndexProvider;
44 import com.android.settings.widget.SettingsMainSwitchBar;
45 import com.android.settings.wifi.WifiUtils;
46 import com.android.settings.wifi.repository.SharedConnectivityRepository;
47 import com.android.settingslib.TetherUtil;
48 import com.android.settingslib.core.AbstractPreferenceController;
49 import com.android.settingslib.search.SearchIndexable;
50 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
51 
52 import java.util.ArrayList;
53 import java.util.List;
54 
55 @SearchIndexable
56 public class WifiTetherSettings extends RestrictedDashboardFragment
57         implements WifiTetherBasePreferenceController.OnTetherConfigUpdateListener {
58 
59     private static final String TAG = "WifiTetherSettings";
60     private static final IntentFilter TETHER_STATE_CHANGE_FILTER;
61     private static final String KEY_WIFI_TETHER_SCREEN = "wifi_tether_settings_screen";
62 
63     @VisibleForTesting
64     static final String KEY_WIFI_TETHER_NETWORK_NAME = "wifi_tether_network_name";
65     @VisibleForTesting
66     static final String KEY_WIFI_TETHER_SECURITY = "wifi_tether_security";
67     @VisibleForTesting
68     static final String KEY_WIFI_TETHER_NETWORK_PASSWORD = "wifi_tether_network_password";
69     @VisibleForTesting
70     static final String KEY_WIFI_TETHER_AUTO_OFF = "wifi_tether_auto_turn_off";
71     @VisibleForTesting
72     static final String KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY =
73             WifiTetherMaximizeCompatibilityPreferenceController.PREF_KEY;
74     @VisibleForTesting
75     static final String KEY_WIFI_HOTSPOT_SECURITY = "wifi_hotspot_security";
76     @VisibleForTesting
77     static final String KEY_WIFI_HOTSPOT_SPEED = "wifi_hotspot_speed";
78     @VisibleForTesting
79     static final String KEY_INSTANT_HOTSPOT = "wifi_hotspot_instant";
80 
81     @VisibleForTesting
82     SettingsMainSwitchBar mMainSwitchBar;
83     private WifiTetherSwitchBarController mSwitchBarController;
84     @VisibleForTesting
85     WifiTetherSSIDPreferenceController mSSIDPreferenceController;
86     @VisibleForTesting
87     WifiTetherPasswordPreferenceController mPasswordPreferenceController;
88     @VisibleForTesting
89     WifiTetherSecurityPreferenceController mSecurityPreferenceController;
90     @VisibleForTesting
91     WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController;
92     @VisibleForTesting
93     WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController;
94 
95     @VisibleForTesting
96     boolean mUnavailable;
97     private WifiRestriction mWifiRestriction;
98     @VisibleForTesting
99     TetherChangeReceiver mTetherChangeReceiver;
100 
101     @VisibleForTesting
102     WifiTetherViewModel mWifiTetherViewModel;
103     @VisibleForTesting
104     Preference mWifiHotspotSecurity;
105     @VisibleForTesting
106     Preference mWifiHotspotSpeed;
107     @VisibleForTesting
108     Preference mInstantHotspot;
109 
110     static {
111         TETHER_STATE_CHANGE_FILTER = new IntentFilter(WIFI_AP_STATE_CHANGED_ACTION);
112     }
113 
WifiTetherSettings()114     public WifiTetherSettings() {
115         super(UserManager.DISALLOW_CONFIG_TETHERING);
116         mWifiRestriction = new WifiRestriction();
117     }
118 
WifiTetherSettings(WifiRestriction wifiRestriction)119     public WifiTetherSettings(WifiRestriction wifiRestriction) {
120         super(UserManager.DISALLOW_CONFIG_TETHERING);
121         mWifiRestriction = wifiRestriction;
122     }
123 
124     @Override
getMetricsCategory()125     public int getMetricsCategory() {
126         return SettingsEnums.WIFI_TETHER_SETTINGS;
127     }
128 
129     @Override
getLogTag()130     protected String getLogTag() {
131         return "WifiTetherSettings";
132     }
133 
134     @Override
onCreate(Bundle icicle)135     public void onCreate(Bundle icicle) {
136         super.onCreate(icicle);
137         if (!canShowWifiHotspot(getContext())) {
138             Log.e(TAG, "can not launch Wi-Fi hotspot settings"
139                     + " because the config is not set to show.");
140             finish();
141             return;
142         }
143 
144         setIfOnlyAvailableForAdmins(true);
145         mUnavailable = isUiRestricted() || !mWifiRestriction.isHotspotAvailable(getContext());
146         if (mUnavailable) {
147             return;
148         }
149 
150         mWifiTetherViewModel = FeatureFactory.getFeatureFactory().getWifiFeatureProvider()
151                 .getWifiTetherViewModel(this);
152         if (mWifiTetherViewModel != null) {
153             setupSpeedFeature(mWifiTetherViewModel.isSpeedFeatureAvailable());
154             setupInstantHotspot(mWifiTetherViewModel.isInstantHotspotFeatureAvailable());
155             mWifiTetherViewModel.getRestarting().observe(this, this::onRestartingChanged);
156         }
157     }
158 
159     @VisibleForTesting
setupSpeedFeature(boolean isSpeedFeatureAvailable)160     void setupSpeedFeature(boolean isSpeedFeatureAvailable) {
161         mWifiHotspotSecurity = findPreference(KEY_WIFI_HOTSPOT_SECURITY);
162         mWifiHotspotSpeed = findPreference(KEY_WIFI_HOTSPOT_SPEED);
163         if (mWifiHotspotSecurity == null || mWifiHotspotSpeed == null) {
164             return;
165         }
166         mWifiHotspotSecurity.setVisible(isSpeedFeatureAvailable);
167         mWifiHotspotSpeed.setVisible(isSpeedFeatureAvailable);
168         if (isSpeedFeatureAvailable) {
169             mWifiTetherViewModel.getSecuritySummary().observe(this, this::onSecuritySummaryChanged);
170             mWifiTetherViewModel.getSpeedSummary().observe(this, this::onSpeedSummaryChanged);
171         }
172     }
173 
174     @VisibleForTesting
setupInstantHotspot(boolean isFeatureAvailable)175     void setupInstantHotspot(boolean isFeatureAvailable) {
176         if (!isFeatureAvailable) {
177             return;
178         }
179         mInstantHotspot = findPreference(KEY_INSTANT_HOTSPOT);
180         if (mInstantHotspot == null) {
181             Log.e(TAG, "Failed to find Instant Hotspot preference:" + KEY_INSTANT_HOTSPOT);
182             return;
183         }
184         mWifiTetherViewModel.getInstantHotspotSummary()
185                 .observe(this, this::onInstantHotspotChanged);
186         mInstantHotspot.setOnPreferenceClickListener(p -> {
187             mWifiTetherViewModel.launchInstantHotspotSettings();
188             return true;
189         });
190     }
191 
192     @Override
onAttach(Context context)193     public void onAttach(Context context) {
194         super.onAttach(context);
195         mTetherChangeReceiver = new TetherChangeReceiver();
196 
197         mSSIDPreferenceController = use(WifiTetherSSIDPreferenceController.class);
198         mSecurityPreferenceController = use(WifiTetherSecurityPreferenceController.class);
199         mPasswordPreferenceController = use(WifiTetherPasswordPreferenceController.class);
200         mMaxCompatibilityPrefController =
201                 use(WifiTetherMaximizeCompatibilityPreferenceController.class);
202         mWifiTetherAutoOffPreferenceController = use(WifiTetherAutoOffPreferenceController.class);
203     }
204 
205     @Override
onActivityCreated(Bundle savedInstanceState)206     public void onActivityCreated(Bundle savedInstanceState) {
207         super.onActivityCreated(savedInstanceState);
208         if (mUnavailable) {
209             return;
210         }
211         // Assume we are in a SettingsActivity. This is only safe because we currently use
212         // SettingsActivity as base for all preference fragments.
213         final SettingsActivity activity = (SettingsActivity) getActivity();
214         mMainSwitchBar = activity.getSwitchBar();
215         mMainSwitchBar.setTitle(getString(R.string.use_wifi_hotsopt_main_switch_title));
216         mSwitchBarController = new WifiTetherSwitchBarController(activity, mMainSwitchBar);
217         getSettingsLifecycle().addObserver(mSwitchBarController);
218         mMainSwitchBar.show();
219     }
220 
221     @Override
onStart()222     public void onStart() {
223         super.onStart();
224         if (!mWifiRestriction.isHotspotAvailable(getContext())) {
225             getEmptyTextView().setText(R.string.not_allowed_by_ent);
226             getPreferenceScreen().removeAll();
227             return;
228         }
229         if (mUnavailable) {
230             if (!isUiRestrictedByOnlyAdmin()) {
231                 getEmptyTextView()
232                         .setText(com.android.settingslib.R.string.tethering_settings_not_available);
233             }
234             getPreferenceScreen().removeAll();
235             return;
236         }
237         final Context context = getContext();
238         if (context != null) {
239             context.registerReceiver(mTetherChangeReceiver, TETHER_STATE_CHANGE_FILTER,
240                     Context.RECEIVER_EXPORTED_UNAUDITED);
241             // The intent WIFI_AP_STATE_CHANGED_ACTION is not sticky intent anymore after SC-V2
242             // Handle the initial state after register the receiver.
243             updateDisplayWithNewConfig();
244         }
245         mWifiTetherViewModel.refresh();
246     }
247 
248     @Override
onStop()249     public void onStop() {
250         super.onStop();
251         if (mUnavailable) {
252             return;
253         }
254         final Context context = getContext();
255         if (context != null) {
256             context.unregisterReceiver(mTetherChangeReceiver);
257         }
258     }
259 
onSecuritySummaryChanged(Integer securityResId)260     protected void onSecuritySummaryChanged(Integer securityResId) {
261         mWifiHotspotSecurity.setSummary(securityResId);
262     }
263 
onSpeedSummaryChanged(Integer summaryResId)264     protected void onSpeedSummaryChanged(Integer summaryResId) {
265         mWifiHotspotSpeed.setSummary(summaryResId);
266     }
267 
268     @Override
getPreferenceScreenResId()269     protected int getPreferenceScreenResId() {
270         return R.xml.wifi_tether_settings;
271     }
272 
273     @Override
createPreferenceControllers(Context context)274     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
275         return buildPreferenceControllers(context, this::onTetherConfigUpdated);
276     }
277 
buildPreferenceControllers(Context context, WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener)278     private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
279             WifiTetherBasePreferenceController.OnTetherConfigUpdateListener listener) {
280         final List<AbstractPreferenceController> controllers = new ArrayList<>();
281         controllers.add(new WifiTetherSSIDPreferenceController(context, listener));
282         controllers.add(new WifiTetherSecurityPreferenceController(context, listener));
283         controllers.add(new WifiTetherPasswordPreferenceController(context, listener));
284         controllers.add(
285                 new WifiTetherAutoOffPreferenceController(context, KEY_WIFI_TETHER_AUTO_OFF));
286         controllers.add(new WifiTetherMaximizeCompatibilityPreferenceController(context, listener));
287         return controllers;
288     }
289 
290     @Override
onTetherConfigUpdated(AbstractPreferenceController context)291     public void onTetherConfigUpdated(AbstractPreferenceController context) {
292         final SoftApConfiguration config = buildNewConfig();
293         mPasswordPreferenceController.setSecurityType(config.getSecurityType());
294 
295         mWifiTetherViewModel.setSoftApConfiguration(config);
296     }
297 
298     @VisibleForTesting
onRestartingChanged(Boolean restarting)299     void onRestartingChanged(Boolean restarting) {
300         mMainSwitchBar.setVisibility((restarting) ? INVISIBLE : VISIBLE);
301         setLoading(restarting, false);
302     }
303 
304     @VisibleForTesting
onInstantHotspotChanged(String summary)305     void onInstantHotspotChanged(String summary) {
306         if (summary == null) {
307             mInstantHotspot.setVisible(false);
308             return;
309         }
310         mInstantHotspot.setVisible(true);
311         mInstantHotspot.setSummary(summary);
312     }
313 
314     @VisibleForTesting
buildNewConfig()315     SoftApConfiguration buildNewConfig() {
316         SoftApConfiguration currentConfig = mWifiTetherViewModel.getSoftApConfiguration();
317         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(currentConfig);
318         configBuilder.setSsid(mSSIDPreferenceController.getSSID());
319         int securityType =
320                 mWifiTetherViewModel.isSpeedFeatureAvailable()
321                         ? currentConfig.getSecurityType()
322                         : mSecurityPreferenceController.getSecurityType();
323         String passphrase =
324                 securityType == SoftApConfiguration.SECURITY_TYPE_OPEN
325                         ? null
326                         : mPasswordPreferenceController.getPasswordValidated(securityType);
327         configBuilder.setPassphrase(passphrase, securityType);
328         if (!mWifiTetherViewModel.isSpeedFeatureAvailable()) {
329             mMaxCompatibilityPrefController.setupMaximizeCompatibility(configBuilder);
330         }
331         configBuilder.setAutoShutdownEnabled(
332                 mWifiTetherAutoOffPreferenceController.isEnabled());
333         return configBuilder.build();
334     }
335 
updateDisplayWithNewConfig()336     private void updateDisplayWithNewConfig() {
337         use(WifiTetherSSIDPreferenceController.class).updateDisplay();
338         use(WifiTetherSecurityPreferenceController.class).updateDisplay();
339         use(WifiTetherPasswordPreferenceController.class).updateDisplay();
340         use(WifiTetherMaximizeCompatibilityPreferenceController.class).updateDisplay();
341     }
342 
343     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
344             new SearchIndexProvider(R.xml.wifi_tether_settings);
345 
346     @VisibleForTesting
347     static class SearchIndexProvider extends BaseSearchIndexProvider {
348 
349         private final WifiRestriction mWifiRestriction;
350         private final boolean mIsInstantHotspotEnabled;
351 
SearchIndexProvider(int xmlRes)352         SearchIndexProvider(int xmlRes) {
353             super(xmlRes);
354             mWifiRestriction = new WifiRestriction();
355             mIsInstantHotspotEnabled = SharedConnectivityRepository.isDeviceConfigEnabled();
356         }
357 
358         @VisibleForTesting
SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction, boolean isInstantHotspotEnabled)359         SearchIndexProvider(int xmlRes, WifiRestriction wifiRestriction,
360                 boolean isInstantHotspotEnabled) {
361             super(xmlRes);
362             mWifiRestriction = wifiRestriction;
363             mIsInstantHotspotEnabled = isInstantHotspotEnabled;
364         }
365 
366         @Override
getNonIndexableKeys(Context context)367         public List<String> getNonIndexableKeys(Context context) {
368             final List<String> keys = super.getNonIndexableKeys(context);
369 
370             if (!mWifiRestriction.isTetherAvailable(context)
371                     || !mWifiRestriction.isHotspotAvailable(context)) {
372                 keys.add(KEY_WIFI_TETHER_NETWORK_NAME);
373                 keys.add(KEY_WIFI_TETHER_SECURITY);
374                 keys.add(KEY_WIFI_TETHER_NETWORK_PASSWORD);
375                 keys.add(KEY_WIFI_TETHER_AUTO_OFF);
376                 keys.add(KEY_WIFI_TETHER_MAXIMIZE_COMPATIBILITY);
377                 keys.add(KEY_INSTANT_HOTSPOT);
378             } else if (!mIsInstantHotspotEnabled) {
379                 keys.add(KEY_INSTANT_HOTSPOT);
380             }
381 
382             // Remove duplicate
383             keys.add(KEY_WIFI_TETHER_SCREEN);
384             return keys;
385         }
386 
387         @Override
isPageSearchEnabled(Context context)388         protected boolean isPageSearchEnabled(Context context) {
389             if (context == null) {
390                 return false;
391             }
392             UserManager userManager = context.getSystemService(UserManager.class);
393             if (userManager == null || !userManager.isAdminUser()) {
394                 return false;
395             }
396             return WifiUtils.canShowWifiHotspot(context);
397         }
398 
399         @Override
createPreferenceControllers(Context context)400         public List<AbstractPreferenceController> createPreferenceControllers(Context context) {
401             return buildPreferenceControllers(context, null /* listener */);
402         }
403     }
404 
405     @VisibleForTesting
406     static class WifiRestriction {
isTetherAvailable(@ullable Context context)407         public boolean isTetherAvailable(@Nullable Context context) {
408             if (context == null) return true;
409             return TetherUtil.isTetherAvailable(context);
410         }
411 
isHotspotAvailable(@ullable Context context)412         public boolean isHotspotAvailable(@Nullable Context context) {
413             if (context == null) return true;
414             return WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(context);
415         }
416     }
417 
418     @VisibleForTesting
419     class TetherChangeReceiver extends BroadcastReceiver {
420         @Override
onReceive(Context content, Intent intent)421         public void onReceive(Context content, Intent intent) {
422             String action = intent.getAction();
423             Log.d(TAG, "updating display config due to receiving broadcast action " + action);
424             updateDisplayWithNewConfig();
425         }
426     }
427 }
428