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 package com.android.settings.wifi.details;
17 
18 import static com.android.settings.network.NetworkProviderSettings.WIFI_DIALOG_ID;
19 import static com.android.settings.network.telephony.MobileNetworkUtils.NO_CELL_DATA_TYPE_ICON;
20 import static com.android.settingslib.Utils.formatPercentage;
21 
22 import android.app.Dialog;
23 import android.app.admin.DevicePolicyManager;
24 import android.app.settings.SettingsEnums;
25 import android.content.Context;
26 import android.graphics.drawable.Drawable;
27 import android.net.ConnectivityManager;
28 import android.net.wifi.WifiManager;
29 import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.HandlerThread;
33 import android.os.Looper;
34 import android.os.Process;
35 import android.os.SimpleClock;
36 import android.os.SystemClock;
37 import android.os.UserHandle;
38 import android.os.UserManager;
39 import android.telephony.SignalStrength;
40 import android.view.Menu;
41 import android.view.MenuInflater;
42 import android.view.MenuItem;
43 
44 import androidx.annotation.NonNull;
45 import androidx.annotation.VisibleForTesting;
46 import androidx.preference.Preference;
47 import androidx.preference.PreferenceScreen;
48 
49 import com.android.settings.R;
50 import com.android.settings.Utils;
51 import com.android.settings.dashboard.RestrictedDashboardFragment;
52 import com.android.settings.network.telephony.MobileNetworkUtils;
53 import com.android.settings.overlay.FeatureFactory;
54 import com.android.settings.wifi.WifiConfigUiBase2;
55 import com.android.settings.wifi.WifiDialog2;
56 import com.android.settings.wifi.WifiUtils;
57 import com.android.settings.wifi.details2.AddDevicePreferenceController2;
58 import com.android.settings.wifi.details2.CertificateDetailsPreferenceController;
59 import com.android.settings.wifi.details2.ServerNamePreferenceController;
60 import com.android.settings.wifi.details2.WifiAutoConnectPreferenceController2;
61 import com.android.settings.wifi.details2.WifiDetailPreferenceController2;
62 import com.android.settings.wifi.details2.WifiMeteredPreferenceController2;
63 import com.android.settings.wifi.details2.WifiPrivacyPreferenceController;
64 import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2;
65 import com.android.settings.wifi.details2.WifiSecondSummaryController2;
66 import com.android.settings.wifi.details2.WifiSubscriptionDetailPreferenceController2;
67 import com.android.settings.wifi.repository.SharedConnectivityRepository;
68 import com.android.settingslib.RestrictedLockUtils;
69 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
70 import com.android.settingslib.RestrictedLockUtilsInternal;
71 import com.android.settingslib.core.AbstractPreferenceController;
72 import com.android.wifitrackerlib.NetworkDetailsTracker;
73 import com.android.wifitrackerlib.WifiEntry;
74 
75 import java.time.Clock;
76 import java.time.ZoneOffset;
77 import java.util.ArrayList;
78 import java.util.List;
79 
80 /**
81  * Detail page for the currently connected wifi network.
82  *
83  * <p>The key of {@link WifiEntry} should be saved to the intent Extras when launching this class
84  * in order to properly render this page.
85  */
86 public class WifiNetworkDetailsFragment extends RestrictedDashboardFragment implements
87         WifiDialog2.WifiDialog2Listener {
88 
89     private static final String TAG = "WifiNetworkDetailsFrg";
90 
91     // Key of a Bundle to save/restore the selected WifiEntry
92     public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
93 
94     public static final String KEY_HOTSPOT_DEVICE_CATEGORY = "hotspot_device_details_category";
95     public static final String KEY_HOTSPOT_DEVICE_INTERNET_SOURCE =
96             "hotspot_device_details_internet_source";
97     public static final String KEY_HOTSPOT_DEVICE_BATTERY = "hotspot_device_details_battery";
98     public static final String KEY_HOTSPOT_CONNECTION_CATEGORY = "hotspot_connection_category";
99 
100     // Max age of tracked WifiEntries
101     private static final long MAX_SCAN_AGE_MILLIS = 15_000;
102     // Interval between initiating SavedNetworkTracker scans
103     private static final long SCAN_INTERVAL_MILLIS = 10_000;
104 
105     @VisibleForTesting
106     boolean mIsUiRestricted;
107     @VisibleForTesting
108     NetworkDetailsTracker mNetworkDetailsTracker;
109     private HandlerThread mWorkerThread;
110     @VisibleForTesting
111     WifiDetailPreferenceController2 mWifiDetailPreferenceController2;
112     private List<WifiDialog2.WifiDialog2Listener> mWifiDialogListeners = new ArrayList<>();
113     @VisibleForTesting
114     List<AbstractPreferenceController> mControllers;
115     private boolean mIsInstantHotspotFeatureEnabled =
116             SharedConnectivityRepository.isDeviceConfigEnabled();
117     @VisibleForTesting
118     WifiNetworkDetailsViewModel mWifiNetworkDetailsViewModel;
119 
WifiNetworkDetailsFragment()120     public WifiNetworkDetailsFragment() {
121         super(UserManager.DISALLOW_CONFIG_WIFI);
122     }
123 
124     @Override
onAttach(@onNull Context context)125     public void onAttach(@NonNull Context context) {
126         super.onAttach(context);
127         String wifiEntryKey = getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY);
128         setupNetworksDetailTracker();
129         use(WifiPrivacyPreferenceController.class)
130                 .setWifiEntryKey(wifiEntryKey);
131         use(CertificateDetailsPreferenceController.class)
132                 .setWifiEntry(mNetworkDetailsTracker.getWifiEntry());
133         use(ServerNamePreferenceController.class)
134                 .setWifiEntry(mNetworkDetailsTracker.getWifiEntry());
135     }
136 
137     @Override
onCreate(Bundle icicle)138     public void onCreate(Bundle icicle) {
139         super.onCreate(icicle);
140         setIfOnlyAvailableForAdmins(true);
141         mIsUiRestricted = isUiRestricted();
142     }
143 
144     @Override
onStart()145     public void onStart() {
146         super.onStart();
147         if (mIsUiRestricted) {
148             restrictUi();
149         }
150     }
151 
152     @VisibleForTesting
restrictUi()153     void restrictUi() {
154         clearWifiEntryCallback();
155         if (!isUiRestrictedByOnlyAdmin()) {
156             getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted);
157         }
158         getPreferenceScreen().removeAll();
159     }
160 
161     @Override
onDestroy()162     public void onDestroy() {
163         mWorkerThread.quit();
164 
165         super.onDestroy();
166     }
167 
168     @Override
getMetricsCategory()169     public int getMetricsCategory() {
170         return SettingsEnums.WIFI_NETWORK_DETAILS;
171     }
172 
173     @Override
getLogTag()174     protected String getLogTag() {
175         return TAG;
176     }
177 
178     @Override
getPreferenceScreenResId()179     protected int getPreferenceScreenResId() {
180         return R.xml.wifi_network_details_fragment2;
181     }
182 
183     @Override
getDialogMetricsCategory(int dialogId)184     public int getDialogMetricsCategory(int dialogId) {
185         if (dialogId == WIFI_DIALOG_ID) {
186             return SettingsEnums.DIALOG_WIFI_AP_EDIT;
187         }
188         return 0;
189     }
190 
191     @Override
onCreateDialog(int dialogId)192     public Dialog onCreateDialog(int dialogId) {
193         if (getActivity() == null || mWifiDetailPreferenceController2 == null) {
194             return null;
195         }
196 
197         final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry();
198         return new WifiDialog2(
199                 getActivity(),
200                 this,
201                 wifiEntry,
202                 WifiConfigUiBase2.MODE_MODIFY,
203                 0,
204                 false,
205                 true);
206     }
207 
208     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)209     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
210         if (!mIsUiRestricted && isEditable()) {
211             MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.wifi_modify);
212             item.setIcon(com.android.internal.R.drawable.ic_mode_edit);
213             item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
214         }
215         super.onCreateOptionsMenu(menu, inflater);
216     }
217 
218     @Override
onOptionsItemSelected(MenuItem menuItem)219     public boolean onOptionsItemSelected(MenuItem menuItem) {
220         switch (menuItem.getItemId()) {
221             case Menu.FIRST:
222                 if (!mWifiDetailPreferenceController2.canModifyNetwork()) {
223                     EnforcedAdmin admin = RestrictedLockUtilsInternal.getDeviceOwner(getContext());
224                     if (admin == null) {
225                         final DevicePolicyManager dpm = (DevicePolicyManager)
226                                 getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
227                         final UserManager um = (UserManager)
228                                 getContext().getSystemService(Context.USER_SERVICE);
229                         final int profileOwnerUserId = Utils.getManagedProfileId(
230                                 um, UserHandle.myUserId());
231                         if (profileOwnerUserId != UserHandle.USER_NULL) {
232                             admin = new EnforcedAdmin(dpm.getProfileOwnerAsUser(profileOwnerUserId),
233                                     null, UserHandle.of(profileOwnerUserId));
234                         }
235                     }
236                     RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin);
237                 } else {
238                     showDialog(WIFI_DIALOG_ID);
239                 }
240                 return true;
241             default:
242                 return super.onOptionsItemSelected(menuItem);
243         }
244     }
245 
246     @Override
createPreferenceControllers(Context context)247     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
248         mControllers = new ArrayList<>();
249         final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
250         setupNetworksDetailTracker();
251         final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry();
252 
253         if (mIsInstantHotspotFeatureEnabled) {
254             getWifiNetworkDetailsViewModel().setWifiEntry(wifiEntry);
255         }
256 
257         final WifiSecondSummaryController2 wifiSecondSummaryController2 =
258                 new WifiSecondSummaryController2(context);
259         wifiSecondSummaryController2.setWifiEntry(wifiEntry);
260         mControllers.add(wifiSecondSummaryController2);
261 
262         mWifiDetailPreferenceController2 = WifiDetailPreferenceController2.newInstance(
263                 wifiEntry,
264                 cm,
265                 context,
266                 this,
267                 new Handler(Looper.getMainLooper()),  // UI thread.
268                 getSettingsLifecycle(),
269                 context.getSystemService(WifiManager.class),
270                 mMetricsFeatureProvider);
271         mControllers.add(mWifiDetailPreferenceController2);
272 
273         final WifiAutoConnectPreferenceController2 wifiAutoConnectPreferenceController2 =
274                 new WifiAutoConnectPreferenceController2(context);
275         wifiAutoConnectPreferenceController2.setWifiEntry(wifiEntry);
276         mControllers.add(wifiAutoConnectPreferenceController2);
277 
278         final AddDevicePreferenceController2 addDevicePreferenceController2 =
279                 new AddDevicePreferenceController2(context);
280         addDevicePreferenceController2.setWifiEntry(wifiEntry);
281         mControllers.add(addDevicePreferenceController2);
282 
283         final WifiMeteredPreferenceController2 meteredPreferenceController2 =
284                 new WifiMeteredPreferenceController2(context, wifiEntry);
285         mControllers.add(meteredPreferenceController2);
286 
287         final WifiPrivacyPreferenceController2 privacyController2 =
288                 new WifiPrivacyPreferenceController2(context);
289         privacyController2.setWifiEntry(wifiEntry);
290         mControllers.add(privacyController2);
291 
292         final WifiSubscriptionDetailPreferenceController2
293                 wifiSubscriptionDetailPreferenceController2 =
294                 new WifiSubscriptionDetailPreferenceController2(context);
295         wifiSubscriptionDetailPreferenceController2.setWifiEntry(wifiEntry);
296         mControllers.add(wifiSubscriptionDetailPreferenceController2);
297 
298         // Sets callback listener for wifi dialog.
299         mWifiDialogListeners.add(mWifiDetailPreferenceController2);
300 
301         return mControllers;
302     }
303 
304     @Override
onSubmit(@onNull WifiDialog2 dialog)305     public void onSubmit(@NonNull WifiDialog2 dialog) {
306         for (WifiDialog2.WifiDialog2Listener listener : mWifiDialogListeners) {
307             listener.onSubmit(dialog);
308         }
309     }
310 
setupNetworksDetailTracker()311     private void setupNetworksDetailTracker() {
312         if (mNetworkDetailsTracker != null) {
313             return;
314         }
315 
316         final Context context = getContext();
317         mWorkerThread = new HandlerThread(TAG
318                 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
319                 Process.THREAD_PRIORITY_BACKGROUND);
320         mWorkerThread.start();
321         final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
322             @Override
323             public long millis() {
324                 return SystemClock.elapsedRealtime();
325             }
326         };
327 
328         mNetworkDetailsTracker = FeatureFactory.getFeatureFactory()
329                 .getWifiTrackerLibProvider()
330                 .createNetworkDetailsTracker(
331                         getSettingsLifecycle(),
332                         context,
333                         new Handler(Looper.getMainLooper()),
334                         mWorkerThread.getThreadHandler(),
335                         elapsedRealtimeClock,
336                         MAX_SCAN_AGE_MILLIS,
337                         SCAN_INTERVAL_MILLIS,
338                         getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY));
339     }
340 
clearWifiEntryCallback()341     private void clearWifiEntryCallback() {
342         if (mNetworkDetailsTracker == null) {
343             return;
344         }
345         final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry();
346         if (wifiEntry == null) {
347             return;
348         }
349         wifiEntry.setListener(null);
350     }
351 
isEditable()352     private boolean isEditable() {
353         if (mNetworkDetailsTracker == null) {
354             return false;
355         }
356         final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry();
357         if (wifiEntry == null) {
358             return false;
359         }
360         return wifiEntry.isSaved();
361     }
362 
363     /**
364      * API call for refreshing the preferences in this fragment.
365      */
refreshPreferences()366     public void refreshPreferences() {
367         updatePreferenceStates();
368         displayPreferenceControllers();
369     }
370 
displayPreferenceControllers()371     protected void displayPreferenceControllers() {
372         final PreferenceScreen screen = getPreferenceScreen();
373         for (AbstractPreferenceController controller : mControllers) {
374             // WifiDetailPreferenceController2 gets the callback WifiEntryCallback#onUpdated,
375             // it can control the visibility change by itself.
376             // And WifiDetailPreferenceController2#updatePreference renew mEntityHeaderController
377             // instance which will cause icon reset.
378             if (controller instanceof WifiDetailPreferenceController2) {
379                 continue;
380             }
381             controller.displayPreference(screen);
382         }
383         if (mIsInstantHotspotFeatureEnabled) {
384             getWifiNetworkDetailsViewModel().setWifiEntry(mNetworkDetailsTracker.getWifiEntry());
385         }
386     }
387 
getWifiNetworkDetailsViewModel()388     private WifiNetworkDetailsViewModel getWifiNetworkDetailsViewModel() {
389         if (mWifiNetworkDetailsViewModel == null) {
390             mWifiNetworkDetailsViewModel = FeatureFactory.getFeatureFactory()
391                     .getWifiFeatureProvider().getWifiNetworkDetailsViewModel(this);
392             mWifiNetworkDetailsViewModel.getHotspotNetworkData()
393                     .observe(this, this::onHotspotNetworkChanged);
394         }
395         return mWifiNetworkDetailsViewModel;
396     }
397 
398     @VisibleForTesting
onHotspotNetworkChanged(WifiNetworkDetailsViewModel.HotspotNetworkData data)399     void onHotspotNetworkChanged(WifiNetworkDetailsViewModel.HotspotNetworkData data) {
400         PreferenceScreen screen = getPreferenceScreen();
401         if (screen == null) {
402             return;
403         }
404         if (data == null) {
405             screen.findPreference(KEY_HOTSPOT_DEVICE_CATEGORY).setVisible(false);
406             screen.findPreference(KEY_HOTSPOT_CONNECTION_CATEGORY).setVisible(false);
407             if (mWifiDetailPreferenceController2 != null) {
408                 mWifiDetailPreferenceController2.setSignalStrengthTitle(R.string.wifi_signal);
409             }
410             return;
411         }
412         screen.findPreference(KEY_HOTSPOT_DEVICE_CATEGORY).setVisible(true);
413         updateInternetSource(data.getNetworkType(), data.getUpstreamConnectionStrength());
414         updateBattery(data.isBatteryCharging(), data.getBatteryPercentage());
415 
416         screen.findPreference(KEY_HOTSPOT_CONNECTION_CATEGORY).setVisible(true);
417         if (mWifiDetailPreferenceController2 != null) {
418             mWifiDetailPreferenceController2
419                     .setSignalStrengthTitle(R.string.hotspot_connection_strength);
420         }
421     }
422 
423     @VisibleForTesting
updateInternetSource(int networkType, int upstreamConnectionStrength)424     void updateInternetSource(int networkType, int upstreamConnectionStrength) {
425         Preference internetSource = getPreferenceScreen()
426                 .findPreference(KEY_HOTSPOT_DEVICE_INTERNET_SOURCE);
427         Drawable drawable;
428         if (networkType == HotspotNetwork.NETWORK_TYPE_WIFI) {
429             internetSource.setSummary(R.string.internet_source_wifi);
430             drawable = getContext().getDrawable(
431                     WifiUtils.getInternetIconResource(upstreamConnectionStrength, false));
432         } else if (networkType == HotspotNetwork.NETWORK_TYPE_CELLULAR) {
433             internetSource.setSummary(R.string.internet_source_mobile_data);
434             drawable = getMobileDataIcon(upstreamConnectionStrength);
435         } else if (networkType == HotspotNetwork.NETWORK_TYPE_ETHERNET) {
436             internetSource.setSummary(R.string.internet_source_ethernet);
437             drawable = getContext().getDrawable(R.drawable.ic_settings_ethernet);
438         } else {
439             internetSource.setSummary(R.string.summary_placeholder);
440             drawable = null;
441         }
442         if (drawable != null) {
443             drawable.setTintList(
444                     Utils.getColorAttr(getContext(), android.R.attr.colorControlNormal));
445         }
446         internetSource.setIcon(drawable);
447     }
448 
449     @VisibleForTesting
getMobileDataIcon(int level)450     Drawable getMobileDataIcon(int level) {
451         return MobileNetworkUtils.getSignalStrengthIcon(getContext(), level,
452                 SignalStrength.NUM_SIGNAL_STRENGTH_BINS, NO_CELL_DATA_TYPE_ICON, false, false);
453     }
454 
455     @VisibleForTesting
updateBattery(boolean isChanging, int percentage)456     void updateBattery(boolean isChanging, int percentage) {
457         Preference battery = getPreferenceScreen().findPreference(KEY_HOTSPOT_DEVICE_BATTERY);
458         battery.setSummary((isChanging)
459                 ? getString(R.string.hotspot_battery_charging_summary, formatPercentage(percentage))
460                 : formatPercentage(percentage));
461     }
462 }
463