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