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