/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.wifi.details; import static com.android.settings.network.NetworkProviderSettings.WIFI_DIALOG_ID; import static com.android.settings.network.telephony.MobileNetworkUtils.NO_CELL_DATA_TYPE_ICON; import static com.android.settingslib.Utils.formatPercentage; import android.app.Dialog; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.Context; import android.graphics.drawable.Drawable; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.net.wifi.sharedconnectivity.app.HotspotNetwork; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Process; import android.os.SimpleClock; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.telephony.SignalStrength; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.network.telephony.MobileNetworkUtils; import com.android.settings.overlay.FeatureFactory; import com.android.settings.wifi.WifiConfigUiBase2; import com.android.settings.wifi.WifiDialog2; import com.android.settings.wifi.WifiUtils; import com.android.settings.wifi.details2.AddDevicePreferenceController2; import com.android.settings.wifi.details2.CertificateDetailsPreferenceController; import com.android.settings.wifi.details2.ServerNamePreferenceController; import com.android.settings.wifi.details2.WifiAutoConnectPreferenceController2; import com.android.settings.wifi.details2.WifiDetailPreferenceController2; import com.android.settings.wifi.details2.WifiMeteredPreferenceController2; import com.android.settings.wifi.details2.WifiPrivacyPreferenceController; import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2; import com.android.settings.wifi.details2.WifiSecondSummaryController2; import com.android.settings.wifi.details2.WifiSubscriptionDetailPreferenceController2; import com.android.settings.wifi.repository.SharedConnectivityRepository; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.core.AbstractPreferenceController; import com.android.wifitrackerlib.NetworkDetailsTracker; import com.android.wifitrackerlib.WifiEntry; import java.time.Clock; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; /** * Detail page for the currently connected wifi network. * *

The key of {@link WifiEntry} should be saved to the intent Extras when launching this class * in order to properly render this page. */ public class WifiNetworkDetailsFragment extends RestrictedDashboardFragment implements WifiDialog2.WifiDialog2Listener { private static final String TAG = "WifiNetworkDetailsFrg"; // Key of a Bundle to save/restore the selected WifiEntry public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key"; public static final String KEY_HOTSPOT_DEVICE_CATEGORY = "hotspot_device_details_category"; public static final String KEY_HOTSPOT_DEVICE_INTERNET_SOURCE = "hotspot_device_details_internet_source"; public static final String KEY_HOTSPOT_DEVICE_BATTERY = "hotspot_device_details_battery"; public static final String KEY_HOTSPOT_CONNECTION_CATEGORY = "hotspot_connection_category"; // Max age of tracked WifiEntries private static final long MAX_SCAN_AGE_MILLIS = 15_000; // Interval between initiating SavedNetworkTracker scans private static final long SCAN_INTERVAL_MILLIS = 10_000; @VisibleForTesting boolean mIsUiRestricted; @VisibleForTesting NetworkDetailsTracker mNetworkDetailsTracker; private HandlerThread mWorkerThread; @VisibleForTesting WifiDetailPreferenceController2 mWifiDetailPreferenceController2; private List mWifiDialogListeners = new ArrayList<>(); @VisibleForTesting List mControllers; private boolean mIsInstantHotspotFeatureEnabled = SharedConnectivityRepository.isDeviceConfigEnabled(); @VisibleForTesting WifiNetworkDetailsViewModel mWifiNetworkDetailsViewModel; public WifiNetworkDetailsFragment() { super(UserManager.DISALLOW_CONFIG_WIFI); } @Override public void onAttach(@NonNull Context context) { super.onAttach(context); String wifiEntryKey = getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY); setupNetworksDetailTracker(); use(WifiPrivacyPreferenceController.class) .setWifiEntryKey(wifiEntryKey); use(CertificateDetailsPreferenceController.class) .setWifiEntry(mNetworkDetailsTracker.getWifiEntry()); use(ServerNamePreferenceController.class) .setWifiEntry(mNetworkDetailsTracker.getWifiEntry()); } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setIfOnlyAvailableForAdmins(true); mIsUiRestricted = isUiRestricted(); } @Override public void onStart() { super.onStart(); if (mIsUiRestricted) { restrictUi(); } } @VisibleForTesting void restrictUi() { clearWifiEntryCallback(); if (!isUiRestrictedByOnlyAdmin()) { getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted); } getPreferenceScreen().removeAll(); } @Override public void onDestroy() { mWorkerThread.quit(); super.onDestroy(); } @Override public int getMetricsCategory() { return SettingsEnums.WIFI_NETWORK_DETAILS; } @Override protected String getLogTag() { return TAG; } @Override protected int getPreferenceScreenResId() { return R.xml.wifi_network_details_fragment2; } @Override public int getDialogMetricsCategory(int dialogId) { if (dialogId == WIFI_DIALOG_ID) { return SettingsEnums.DIALOG_WIFI_AP_EDIT; } return 0; } @Override public Dialog onCreateDialog(int dialogId) { if (getActivity() == null || mWifiDetailPreferenceController2 == null) { return null; } final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry(); return new WifiDialog2( getActivity(), this, wifiEntry, WifiConfigUiBase2.MODE_MODIFY, 0, false, true); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (!mIsUiRestricted && isEditable()) { MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.wifi_modify); item.setIcon(com.android.internal.R.drawable.ic_mode_edit); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case Menu.FIRST: if (!mWifiDetailPreferenceController2.canModifyNetwork()) { EnforcedAdmin admin = RestrictedLockUtilsInternal.getDeviceOwner(getContext()); if (admin == null) { final DevicePolicyManager dpm = (DevicePolicyManager) getContext().getSystemService(Context.DEVICE_POLICY_SERVICE); final UserManager um = (UserManager) getContext().getSystemService(Context.USER_SERVICE); final int profileOwnerUserId = Utils.getManagedProfileId( um, UserHandle.myUserId()); if (profileOwnerUserId != UserHandle.USER_NULL) { admin = new EnforcedAdmin(dpm.getProfileOwnerAsUser(profileOwnerUserId), null, UserHandle.of(profileOwnerUserId)); } } RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin); } else { showDialog(WIFI_DIALOG_ID); } return true; default: return super.onOptionsItemSelected(menuItem); } } @Override protected List createPreferenceControllers(Context context) { mControllers = new ArrayList<>(); final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); setupNetworksDetailTracker(); final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry(); if (mIsInstantHotspotFeatureEnabled) { getWifiNetworkDetailsViewModel().setWifiEntry(wifiEntry); } final WifiSecondSummaryController2 wifiSecondSummaryController2 = new WifiSecondSummaryController2(context); wifiSecondSummaryController2.setWifiEntry(wifiEntry); mControllers.add(wifiSecondSummaryController2); mWifiDetailPreferenceController2 = WifiDetailPreferenceController2.newInstance( wifiEntry, cm, context, this, new Handler(Looper.getMainLooper()), // UI thread. getSettingsLifecycle(), context.getSystemService(WifiManager.class), mMetricsFeatureProvider); mControllers.add(mWifiDetailPreferenceController2); final WifiAutoConnectPreferenceController2 wifiAutoConnectPreferenceController2 = new WifiAutoConnectPreferenceController2(context); wifiAutoConnectPreferenceController2.setWifiEntry(wifiEntry); mControllers.add(wifiAutoConnectPreferenceController2); final AddDevicePreferenceController2 addDevicePreferenceController2 = new AddDevicePreferenceController2(context); addDevicePreferenceController2.setWifiEntry(wifiEntry); mControllers.add(addDevicePreferenceController2); final WifiMeteredPreferenceController2 meteredPreferenceController2 = new WifiMeteredPreferenceController2(context, wifiEntry); mControllers.add(meteredPreferenceController2); final WifiPrivacyPreferenceController2 privacyController2 = new WifiPrivacyPreferenceController2(context); privacyController2.setWifiEntry(wifiEntry); mControllers.add(privacyController2); final WifiSubscriptionDetailPreferenceController2 wifiSubscriptionDetailPreferenceController2 = new WifiSubscriptionDetailPreferenceController2(context); wifiSubscriptionDetailPreferenceController2.setWifiEntry(wifiEntry); mControllers.add(wifiSubscriptionDetailPreferenceController2); // Sets callback listener for wifi dialog. mWifiDialogListeners.add(mWifiDetailPreferenceController2); return mControllers; } @Override public void onSubmit(@NonNull WifiDialog2 dialog) { for (WifiDialog2.WifiDialog2Listener listener : mWifiDialogListeners) { listener.onSubmit(dialog); } } private void setupNetworksDetailTracker() { if (mNetworkDetailsTracker != null) { return; } final Context context = getContext(); mWorkerThread = new HandlerThread(TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", Process.THREAD_PRIORITY_BACKGROUND); mWorkerThread.start(); final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) { @Override public long millis() { return SystemClock.elapsedRealtime(); } }; mNetworkDetailsTracker = FeatureFactory.getFeatureFactory() .getWifiTrackerLibProvider() .createNetworkDetailsTracker( getSettingsLifecycle(), context, new Handler(Looper.getMainLooper()), mWorkerThread.getThreadHandler(), elapsedRealtimeClock, MAX_SCAN_AGE_MILLIS, SCAN_INTERVAL_MILLIS, getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY)); } private void clearWifiEntryCallback() { if (mNetworkDetailsTracker == null) { return; } final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry(); if (wifiEntry == null) { return; } wifiEntry.setListener(null); } private boolean isEditable() { if (mNetworkDetailsTracker == null) { return false; } final WifiEntry wifiEntry = mNetworkDetailsTracker.getWifiEntry(); if (wifiEntry == null) { return false; } return wifiEntry.isSaved(); } /** * API call for refreshing the preferences in this fragment. */ public void refreshPreferences() { updatePreferenceStates(); displayPreferenceControllers(); } protected void displayPreferenceControllers() { final PreferenceScreen screen = getPreferenceScreen(); for (AbstractPreferenceController controller : mControllers) { // WifiDetailPreferenceController2 gets the callback WifiEntryCallback#onUpdated, // it can control the visibility change by itself. // And WifiDetailPreferenceController2#updatePreference renew mEntityHeaderController // instance which will cause icon reset. if (controller instanceof WifiDetailPreferenceController2) { continue; } controller.displayPreference(screen); } if (mIsInstantHotspotFeatureEnabled) { getWifiNetworkDetailsViewModel().setWifiEntry(mNetworkDetailsTracker.getWifiEntry()); } } private WifiNetworkDetailsViewModel getWifiNetworkDetailsViewModel() { if (mWifiNetworkDetailsViewModel == null) { mWifiNetworkDetailsViewModel = FeatureFactory.getFeatureFactory() .getWifiFeatureProvider().getWifiNetworkDetailsViewModel(this); mWifiNetworkDetailsViewModel.getHotspotNetworkData() .observe(this, this::onHotspotNetworkChanged); } return mWifiNetworkDetailsViewModel; } @VisibleForTesting void onHotspotNetworkChanged(WifiNetworkDetailsViewModel.HotspotNetworkData data) { PreferenceScreen screen = getPreferenceScreen(); if (screen == null) { return; } if (data == null) { screen.findPreference(KEY_HOTSPOT_DEVICE_CATEGORY).setVisible(false); screen.findPreference(KEY_HOTSPOT_CONNECTION_CATEGORY).setVisible(false); if (mWifiDetailPreferenceController2 != null) { mWifiDetailPreferenceController2.setSignalStrengthTitle(R.string.wifi_signal); } return; } screen.findPreference(KEY_HOTSPOT_DEVICE_CATEGORY).setVisible(true); updateInternetSource(data.getNetworkType(), data.getUpstreamConnectionStrength()); updateBattery(data.isBatteryCharging(), data.getBatteryPercentage()); screen.findPreference(KEY_HOTSPOT_CONNECTION_CATEGORY).setVisible(true); if (mWifiDetailPreferenceController2 != null) { mWifiDetailPreferenceController2 .setSignalStrengthTitle(R.string.hotspot_connection_strength); } } @VisibleForTesting void updateInternetSource(int networkType, int upstreamConnectionStrength) { Preference internetSource = getPreferenceScreen() .findPreference(KEY_HOTSPOT_DEVICE_INTERNET_SOURCE); Drawable drawable; if (networkType == HotspotNetwork.NETWORK_TYPE_WIFI) { internetSource.setSummary(R.string.internet_source_wifi); drawable = getContext().getDrawable( WifiUtils.getInternetIconResource(upstreamConnectionStrength, false)); } else if (networkType == HotspotNetwork.NETWORK_TYPE_CELLULAR) { internetSource.setSummary(R.string.internet_source_mobile_data); drawable = getMobileDataIcon(upstreamConnectionStrength); } else if (networkType == HotspotNetwork.NETWORK_TYPE_ETHERNET) { internetSource.setSummary(R.string.internet_source_ethernet); drawable = getContext().getDrawable(R.drawable.ic_settings_ethernet); } else { internetSource.setSummary(R.string.summary_placeholder); drawable = null; } if (drawable != null) { drawable.setTintList( Utils.getColorAttr(getContext(), android.R.attr.colorControlNormal)); } internetSource.setIcon(drawable); } @VisibleForTesting Drawable getMobileDataIcon(int level) { return MobileNetworkUtils.getSignalStrengthIcon(getContext(), level, SignalStrength.NUM_SIGNAL_STRENGTH_BINS, NO_CELL_DATA_TYPE_ICON, false, false); } @VisibleForTesting void updateBattery(boolean isChanging, int percentage) { Preference battery = getPreferenceScreen().findPreference(KEY_HOTSPOT_DEVICE_BATTERY); battery.setSummary((isChanging) ? getString(R.string.hotspot_battery_charging_summary, formatPercentage(percentage)) : formatPercentage(percentage)); } }