fix: 引入Settings的Module

This commit is contained in:
2024-12-10 14:57:24 +08:00
parent ad8fc8731d
commit df105485bd
6934 changed files with 896168 additions and 2 deletions

View File

@@ -0,0 +1,260 @@
/*
* Copyright (C) 2018 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;
import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.UserManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.wifi.dpp.WifiDppQrCodeScannerFragment;
import com.android.settings.wifi.dpp.WifiDppUtils;
/**
* A full screen UI component for users to edit and add a Wi-Fi network.
*/
public class AddNetworkFragment extends InstrumentedFragment implements WifiConfigUiBase2,
View.OnClickListener {
private static final String TAG = "AddNetworkFragment";
public static final String WIFI_CONFIG_KEY = "wifi_config_key";
@VisibleForTesting
final static int SUBMIT_BUTTON_ID = android.R.id.button1;
@VisibleForTesting
final static int CANCEL_BUTTON_ID = android.R.id.button2;
final static int SSID_SCANNER_BUTTON_ID = R.id.ssid_scanner_button;
private static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0;
private static final String EXTRA_SAVE_WHEN_SUBMIT = ":settings:save_when_submit";
private WifiConfigController2 mUIController;
private Button mSubmitBtn;
private Button mCancelBtn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!isAddWifiConfigAllowed(getContext())) {
getActivity().finish();
return;
}
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_ADD_NETWORK;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.wifi_add_network_view, container, false);
final Button neutral = rootView.findViewById(android.R.id.button3);
if (neutral != null) {
neutral.setVisibility(View.GONE);
}
mSubmitBtn = rootView.findViewById(SUBMIT_BUTTON_ID);
mCancelBtn = rootView.findViewById(CANCEL_BUTTON_ID);
final ImageButton ssidScannerButton = rootView.findViewById(SSID_SCANNER_BUTTON_ID);
mSubmitBtn.setOnClickListener(this);
mCancelBtn.setOnClickListener(this);
ssidScannerButton.setOnClickListener(this);
mUIController = new WifiConfigController2(this, rootView, null, getMode());
// Resize the layout when keyboard opens.
getActivity().getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return rootView;
}
@Override
public void onResume() {
super.onResume();
mUIController.showSecurityFields(
/* refreshEapMethods */ false, /* refreshCertificates */ true);
}
@Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
mUIController.updatePassword();
}
@Override
public void onClick(View view) {
if (view.getId() == SUBMIT_BUTTON_ID) {
handleSubmitAction();
} else if (view.getId() == CANCEL_BUTTON_ID) {
handleCancelAction();
} else if (view.getId() == SSID_SCANNER_BUTTON_ID) {
final TextView ssidEditText = getView().findViewById(R.id.ssid);
final String ssid = ssidEditText.getText().toString();
// Launch QR code scanner to join a network.
startActivityForResult(
WifiDppUtils.getEnrolleeQrCodeScannerIntent(view.getContext(), ssid),
REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) {
if (resultCode != Activity.RESULT_OK) {
return;
}
final WifiConfiguration config = data.getParcelableExtra(
WifiDppQrCodeScannerFragment.KEY_WIFI_CONFIGURATION);
successfullyFinish(config);
}
}
@Override
public int getMode() {
return WifiConfigUiBase2.MODE_CONNECT;
}
@Override
public WifiConfigController2 getController() {
return mUIController;
}
@Override
public void dispatchSubmit() {
handleSubmitAction();
}
@Override
public void setTitle(int id) {
getActivity().setTitle(id);
}
@Override
public void setTitle(CharSequence title) {
getActivity().setTitle(title);
}
@Override
public void setSubmitButton(CharSequence text) {
mSubmitBtn.setText(text);
}
@Override
public void setCancelButton(CharSequence text) {
mCancelBtn.setText(text);
}
@Override
public void setForgetButton(CharSequence text) {
// AddNetwork doesn't need forget button.
}
@Override
public Button getSubmitButton() {
return mSubmitBtn;
}
@Override
public Button getCancelButton() {
return mCancelBtn;
}
@Override
public Button getForgetButton() {
// AddNetwork doesn't need forget button.
return null;
}
@VisibleForTesting
void handleSubmitAction() {
successfullyFinish(mUIController.getConfig());
}
private void successfullyFinish(WifiConfiguration config) {
Activity activity = getActivity();
boolean autoSave = activity.getIntent().getBooleanExtra(EXTRA_SAVE_WHEN_SUBMIT, false);
if (autoSave && config != null) {
WifiManager.ActionListener saveListener = new WifiManager.ActionListener() {
@Override
public void onSuccess() {
if (activity != null && !activity.isFinishing()) {
activity.setResult(Activity.RESULT_OK);
activity.finish();
}
}
@Override
public void onFailure(int reason) {
if (activity != null && !activity.isFinishing()) {
Toast.makeText(activity, R.string.wifi_failed_save_message,
Toast.LENGTH_SHORT).show();
activity.finish();
}
}
};
activity.getSystemService(WifiManager.class).save(config, saveListener);
} else {
Intent intent = new Intent();
intent.putExtra(WIFI_CONFIG_KEY, config);
activity.setResult(Activity.RESULT_OK, intent);
activity.finish();
}
}
@VisibleForTesting
void handleCancelAction() {
final Activity activity = getActivity();
activity.setResult(Activity.RESULT_CANCELED);
activity.finish();
}
@VisibleForTesting
static boolean isAddWifiConfigAllowed(Context context) {
UserManager userManager = context.getSystemService(UserManager.class);
if (userManager != null && userManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)) {
Log.e(TAG, "The user is not allowed to add Wi-Fi configuration.");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2019 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;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageButton;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.wifi.dpp.WifiDppUtils;
import com.android.settingslib.RestrictedPreference;
/**
* The Preference for users to add Wi-Fi networks in WifiSettings
*/
public class AddWifiNetworkPreference extends RestrictedPreference {
private static final String TAG = "AddWifiNetworkPreference";
private final Drawable mScanIconDrawable;
public AddWifiNetworkPreference(Context context) {
this(context, null);
}
public AddWifiNetworkPreference(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setLayoutResource(com.android.settingslib.R.layout.preference_access_point);
setWidgetLayoutResource(R.layout.wifi_button_preference_widget);
setIcon(R.drawable.ic_add_24dp);
setTitle(R.string.wifi_add_network);
mScanIconDrawable = getDrawable(R.drawable.ic_scan_24dp);
checkRestrictionAndSetDisabled(UserManager.DISALLOW_ADD_WIFI_CONFIG);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final ImageButton scanButton = (ImageButton) holder.findViewById(R.id.button_icon);
scanButton.setImageDrawable(mScanIconDrawable);
scanButton.setContentDescription(
getContext().getString(R.string.wifi_dpp_scan_qr_code));
scanButton.setOnClickListener(view -> {
getContext().startActivity(
WifiDppUtils.getEnrolleeQrCodeScannerIntent(getContext(), /* ssid */ null));
});
}
private Drawable getDrawable(@DrawableRes int iconResId) {
Drawable buttonIcon = null;
try {
buttonIcon = getContext().getDrawable(iconResId);
} catch (Resources.NotFoundException exception) {
Log.e(TAG, "Resource does not exist: " + iconResId);
}
return buttonIcon;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2018 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;
import android.Manifest;
import android.app.AppOpsManager;
import android.content.Context;
import com.android.internal.util.ArrayUtils;
import com.android.settings.applications.AppStateAppOpsBridge;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.applications.ApplicationsState.AppFilter;
import java.util.List;
/*
* Connects info of apps that change wifi state to the ApplicationsState. Wraps around the generic
* AppStateAppOpsBridge class to tailor to the semantics of CHANGE_WIFI_STATE. Also provides app
* filters that can use the info.
*/
public class AppStateChangeWifiStateBridge extends AppStateAppOpsBridge {
private static final String TAG = "AppStateChangeWifiStateBridge";
private static final int APP_OPS_OP_CODE = AppOpsManager.OP_CHANGE_WIFI_STATE;
private static final String PM_CHANGE_WIFI_STATE = Manifest.permission.CHANGE_WIFI_STATE;
private static final String PM_NETWORK_SETTINGS = Manifest.permission.NETWORK_SETTINGS;
private static final String[] PM_PERMISSIONS = {
PM_CHANGE_WIFI_STATE
};
public AppStateChangeWifiStateBridge(Context context, ApplicationsState appState, Callback
callback) {
super(context, appState, callback, APP_OPS_OP_CODE, PM_PERMISSIONS);
}
@Override
protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
app.extraInfo = getWifiSettingsInfo(pkg, uid);
}
@Override
protected void loadAllExtraInfo() {
final List<AppEntry> allApps = mAppSession.getAllApps();
for (AppEntry entry : allApps) {
updateExtraInfo(entry, entry.info.packageName, entry.info.uid);
}
}
public WifiSettingsState getWifiSettingsInfo(String pkg, int uid) {
PermissionState permissionState = super.getPermissionInfo(pkg, uid);
return new WifiSettingsState(permissionState);
}
public static class WifiSettingsState extends AppStateAppOpsBridge.PermissionState {
public WifiSettingsState(PermissionState permissionState) {
super(permissionState.packageName, permissionState.userHandle);
this.packageInfo = permissionState.packageInfo;
this.appOpMode = permissionState.appOpMode;
this.permissionDeclared = permissionState.permissionDeclared;
this.staticPermissionGranted = permissionState.staticPermissionGranted;
}
}
public static final AppFilter FILTER_CHANGE_WIFI_STATE = new AppFilter() {
@Override
public void init() {
}
@Override
public boolean filterApp(AppEntry info) {
if (info == null || info.extraInfo == null
|| !(info.extraInfo instanceof WifiSettingsState)) {
return false;
}
WifiSettingsState wifiSettingsState = (WifiSettingsState) info.extraInfo;
if (wifiSettingsState.packageInfo != null) {
final String[] requestedPermissions
= wifiSettingsState.packageInfo.requestedPermissions;
if (ArrayUtils.contains(requestedPermissions, PM_NETWORK_SETTINGS)) {
/*
* NETWORK_SETTINGS permission trumps CHANGE_WIFI_CONFIG, so remove this from
* the list.
*/
return false;
}
}
return wifiSettingsState.permissionDeclared;
}
};
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/**
* CellularFallbackPreferenceController controls whether we should fall back to celluar when
* wifi is bad.
*/
public class CellularFallbackPreferenceController extends TogglePreferenceController {
public CellularFallbackPreferenceController(Context context, String key) {
super(context, key);
}
@Override
public int getAvailabilityStatus() {
return avoidBadWifiConfig() ? UNSUPPORTED_ON_DEVICE : AVAILABLE;
}
@Override
public boolean isChecked() {
return avoidBadWifiCurrentSettings();
}
@Override
public boolean setChecked(boolean isChecked) {
// On: avoid bad wifi. Off: prompt.
return Settings.Global.putString(mContext.getContentResolver(),
Settings.Global.NETWORK_AVOID_BAD_WIFI, isChecked ? "1" : null);
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_network;
}
private boolean avoidBadWifiConfig() {
final int activeDataSubscriptionId = getActiveDataSubscriptionId();
if (activeDataSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return true;
}
final Resources resources = getResourcesForSubId(activeDataSubscriptionId);
return resources.getInteger(com.android.internal.R.integer.config_networkAvoidBadWifi) == 1;
}
@VisibleForTesting
int getActiveDataSubscriptionId() {
return SubscriptionManager.getActiveDataSubscriptionId();
}
@VisibleForTesting
Resources getResourcesForSubId(int subscriptionId) {
return SubscriptionManager.getResourcesForSubId(mContext, subscriptionId);
}
private boolean avoidBadWifiCurrentSettings() {
return "1".equals(Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.NETWORK_AVOID_BAD_WIFI));
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2018 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;
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.applications.AppInfoWithHeader;
import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.AppStateChangeWifiStateBridge.WifiSettingsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
public class ChangeWifiStateDetails extends AppInfoWithHeader
implements OnPreferenceChangeListener {
private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
private static final String LOG_TAG = "ChangeWifiStateDetails";
private AppStateChangeWifiStateBridge mAppBridge;
private AppOpsManager mAppOpsManager;
private TwoStatePreference mSwitchPref;
private WifiSettingsState mWifiSettingsState;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = getActivity();
mAppBridge = new AppStateChangeWifiStateBridge(context, mState, null);
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
// find preferences
addPreferencesFromResource(R.xml.change_wifi_state_details);
mSwitchPref = (TwoStatePreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
// set title/summary for all of them
mSwitchPref.setTitle(R.string.change_wifi_state_app_detail_switch);
// install event listeners
mSwitchPref.setOnPreferenceChangeListener(this);
}
@Override
protected AlertDialog createDialog(int id, int errorCode) {
return null;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.CONFIGURE_WIFI;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mSwitchPref) {
if (mWifiSettingsState != null && (Boolean) newValue
!= mWifiSettingsState.isPermissible()) {
setCanChangeWifiState(!mWifiSettingsState.isPermissible());
refreshUi();
}
return true;
}
return false;
}
private void setCanChangeWifiState(boolean newState) {
logSpecialPermissionChange(newState, mPackageName);
mAppOpsManager.setMode(AppOpsManager.OP_CHANGE_WIFI_STATE,
mPackageInfo.applicationInfo.uid, mPackageName, newState
? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
}
protected void logSpecialPermissionChange(boolean newState, String packageName) {
int logCategory = newState ? SettingsEnums.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_ALLOW
: SettingsEnums.APP_SPECIAL_PERMISSION_SETTINGS_CHANGE_DENY;
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(getContext(),
logCategory, packageName);
}
@Override
protected boolean refreshUi() {
if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
return false;
}
mWifiSettingsState = mAppBridge.getWifiSettingsInfo(mPackageName,
mPackageInfo.applicationInfo.uid);
boolean canChange = mWifiSettingsState.isPermissible();
mSwitchPref.setChecked(canChange);
// you can't ask a user for a permission you didn't even declare!
mSwitchPref.setEnabled(mWifiSettingsState.permissionDeclared);
return true;
}
public static CharSequence getSummary(Context context, AppEntry entry) {
WifiSettingsState state;
if (entry.extraInfo instanceof WifiSettingsState) {
state = (WifiSettingsState) entry.extraInfo;
} else if (entry.extraInfo instanceof PermissionState) {
state = new WifiSettingsState((PermissionState) entry.extraInfo);
} else {
state = new AppStateChangeWifiStateBridge(context, null, null).getWifiSettingsInfo(
entry.info.packageName, entry.info.uid);
}
return getSummary(context, state);
}
public static CharSequence getSummary(Context context, WifiSettingsState wifiSettingsState) {
return context.getString(wifiSettingsState.isPermissible()
? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed);
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright (C) 2019 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;
import android.app.ActionBar;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
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.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.wifitrackerlib.NetworkDetailsTracker;
import com.android.wifitrackerlib.WifiEntry;
import java.time.Clock;
import java.time.ZoneOffset;
/**
* Detail page for configuring Wi-Fi network.
*
* The WifiEntry should be saved to the argument when launching this class in order to properly
* render this page.
*/
public class ConfigureWifiEntryFragment extends InstrumentedFragment implements WifiConfigUiBase2 {
private static final String TAG = "ConfigureWifiEntryFragment";
public static final String NETWORK_CONFIG_KEY = "network_config_key";
private static final int SUBMIT_BUTTON_ID = android.R.id.button1;
private static final int CANCEL_BUTTON_ID = android.R.id.button2;
// 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;
private WifiConfigController2 mUiController;
private Button mSubmitBtn;
private Button mCancelBtn;
private WifiEntry mWifiEntry;
@VisibleForTesting
NetworkDetailsTracker mNetworkDetailsTracker;
private HandlerThread mWorkerThread;
@Override
public void onAttach(Context context) {
super.onAttach(context);
setupNetworkDetailsTracker();
mWifiEntry = mNetworkDetailsTracker.getWifiEntry();
}
@Override
public void onDestroy() {
if (mWorkerThread != null) {
mWorkerThread.quit();
}
super.onDestroy();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_CONFIGURE_NETWORK;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.wifi_add_network_view,
container, false /* attachToRoot */);
final Button neutral = rootView.findViewById(android.R.id.button3);
if (neutral != null) {
neutral.setVisibility(View.GONE);
}
mSubmitBtn = rootView.findViewById(SUBMIT_BUTTON_ID);
mCancelBtn = rootView.findViewById(CANCEL_BUTTON_ID);
mSubmitBtn.setOnClickListener(view -> handleSubmitAction());
mCancelBtn.setOnClickListener(view -> handleCancelAction());
mUiController = new WifiConfigController2(this, rootView, mWifiEntry, getMode());
/**
* For this add WifiEntry UI, need to remove the Home button, so set related feature as
* false.
*/
final ActionBar actionBar = getActivity().getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(false);
actionBar.setHomeButtonEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
}
// Resize the layout when keyboard opens.
getActivity().getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return rootView;
}
@Override
public void onResume() {
super.onResume();
mUiController.showSecurityFields(
/* refreshEapMethods */ false, /* refreshCertificates */ true);
}
@Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
mUiController.updatePassword();
}
@Override
public int getMode() {
return WifiConfigUiBase2.MODE_CONNECT;
}
@Override
public WifiConfigController2 getController() {
return mUiController;
}
@Override
public void dispatchSubmit() {
handleSubmitAction();
}
@Override
public void setTitle(int id) {
getActivity().setTitle(id);
}
@Override
public void setTitle(CharSequence title) {
getActivity().setTitle(title);
}
@Override
public void setSubmitButton(CharSequence text) {
mSubmitBtn.setText(text);
}
@Override
public void setCancelButton(CharSequence text) {
mCancelBtn.setText(text);
}
@Override
public void setForgetButton(CharSequence text) {
// AddNetwork doesn't need forget button.
}
@Override
public Button getSubmitButton() {
return mSubmitBtn;
}
@Override
public Button getCancelButton() {
return mCancelBtn;
}
@Override
public Button getForgetButton() {
// AddNetwork doesn't need forget button.
return null;
}
@VisibleForTesting
void handleSubmitAction() {
final Intent intent = new Intent();
final Activity activity = getActivity();
intent.putExtra(NETWORK_CONFIG_KEY, mUiController.getConfig());
activity.setResult(Activity.RESULT_OK, intent);
activity.finish();
}
@VisibleForTesting
void handleCancelAction() {
getActivity().finish();
}
private void setupNetworkDetailsTracker() {
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(
WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY));
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2015 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;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.UserManager;
import android.util.EventLog;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.wifi.p2p.WifiP2pPreferenceController;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
@SearchIndexable
public class ConfigureWifiSettings extends DashboardFragment {
private static final String TAG = "ConfigureWifiSettings";
@VisibleForTesting
static final String KEY_INSTALL_CREDENTIALS = "install_credentials";
private static final String ACTION_INSTALL_CERTS = "android.credentials.INSTALL";
private static final String PACKAGE_INSTALL_CERTS = "com.android.certinstaller";
private static final String CLASS_INSTALL_CERTS = "com.android.certinstaller.CertInstallerMain";
private static final String KEY_INSTALL_CERTIFICATE = "certificate_install_usage";
private static final String INSTALL_CERTIFICATE_VALUE = "wifi";
public static final int WIFI_WAKEUP_REQUEST_CODE = 600;
private WifiWakeupPreferenceController mWifiWakeupPreferenceController;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (isGuestUser(context)) return;
mWifiWakeupPreferenceController = use(WifiWakeupPreferenceController.class);
mWifiWakeupPreferenceController.setFragment(this);
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
getActivity().setTitle(R.string.network_and_internet_preferences_title);
if (isGuestUser(getContext())) return;
final Preference installCredentialsPref = findPreference(KEY_INSTALL_CREDENTIALS);
if (installCredentialsPref != null) {
installCredentialsPref.setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(ACTION_INSTALL_CERTS);
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(
new ComponentName(PACKAGE_INSTALL_CERTS, CLASS_INSTALL_CERTS));
intent.putExtra(KEY_INSTALL_CERTIFICATE, INSTALL_CERTIFICATE_VALUE);
getContext().startActivity(intent);
return true;
});
} else {
Log.d(TAG, "Can not find the preference.");
}
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!isGuestUser(getContext())) return;
Log.w(TAG, "Displays the restricted UI because the user is a guest.");
EventLog.writeEvent(0x534e4554, "231987122", -1 /* UID */, "User is a guest");
// Restricted UI
final TextView emptyView = getActivity().findViewById(android.R.id.empty);
if (emptyView != null) {
emptyView.setVisibility(View.VISIBLE);
emptyView.setText(R.string.wifi_empty_list_user_restricted);
}
getPreferenceScreen().removeAll();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.CONFIGURE_WIFI;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.wifi_configure_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
if (isGuestUser(context)) return null;
final WifiManager wifiManager = getSystemService(WifiManager.class);
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new WifiP2pPreferenceController(context, getSettingsLifecycle(),
wifiManager));
return controllers;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mWifiWakeupPreferenceController != null && requestCode == WIFI_WAKEUP_REQUEST_CODE) {
mWifiWakeupPreferenceController.onActivityResult(requestCode, resultCode);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.wifi_configure_settings) {
protected boolean isPageSearchEnabled(Context context) {
if (isGuestUser(context)) return false;
return context.getResources()
.getBoolean(R.bool.config_show_wifi_settings);
}
};
private static boolean isGuestUser(Context context) {
if (context == null) return false;
final UserManager userManager = context.getSystemService(UserManager.class);
if (userManager == null) return false;
return userManager.isGuestUser();
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 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;
import android.content.Context;
import android.view.View;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.wifitrackerlib.WifiEntry;
/**
* An AP preference for the currently connected AP.
*/
public class ConnectedWifiEntryPreference extends LongPressWifiEntryPreference implements
View.OnClickListener {
private OnGearClickListener mOnGearClickListener;
public ConnectedWifiEntryPreference(Context context, WifiEntry wifiEntry, Fragment fragment) {
super(context, wifiEntry, fragment);
setWidgetLayoutResource(R.layout.preference_widget_gear_optional_background);
}
/**
* Set gear icon click callback listener.
*/
public void setOnGearClickListener(OnGearClickListener l) {
mOnGearClickListener = l;
notifyChanged();
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final View gear = holder.findViewById(R.id.settings_button);
gear.setOnClickListener(this);
final boolean canSignIn = getWifiEntry().canSignIn();
holder.findViewById(R.id.settings_button_no_background).setVisibility(
canSignIn ? View.INVISIBLE : View.VISIBLE);
gear.setVisibility(canSignIn ? View.VISIBLE : View.INVISIBLE);
holder.findViewById(com.android.settingslib.widget.preference.twotarget.R.id.two_target_divider).setVisibility(
canSignIn ? View.VISIBLE : View.INVISIBLE);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.settings_button) {
if (mOnGearClickListener != null) {
mOnGearClickListener.onGearClick(this);
}
}
}
/**
* Gear Icon click callback interface.
*/
public interface OnGearClickListener {
/**
* The callback triggered when gear icon is clicked.
*/
void onGearClick(ConnectedWifiEntryPreference p);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.LinkifyUtils;
import com.android.settings.R;
/**
* A preference with a title that can have linkable content on click.
*/
public class LinkablePreference extends Preference {
private LinkifyUtils.OnClickListener mClickListener;
private CharSequence mContentTitle;
private CharSequence mContentDescription;
public LinkablePreference(Context ctx, AttributeSet attrs, int defStyle) {
super(ctx, attrs, defStyle);
setIcon(R.drawable.ic_info_outline_24dp);
setSelectable(false);
}
public LinkablePreference(Context ctx, AttributeSet attrs) {
this(ctx, attrs, TypedArrayUtils.getAttr(
ctx, com.android.settingslib.widget.theme.R.attr.footerPreferenceStyle,
android.R.attr.preferenceStyle));
}
public LinkablePreference(Context ctx) {
this(ctx, null);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
TextView textView = (TextView) view.findViewById(android.R.id.title);
if (textView == null) {
return;
}
textView.setSingleLine(false);
if (mContentTitle == null || mClickListener == null) {
return;
}
StringBuilder contentBuilder = new StringBuilder().append(mContentTitle);
if (mContentDescription != null) {
contentBuilder.append("\n\n");
contentBuilder.append(mContentDescription);
}
boolean linked = LinkifyUtils.linkify(textView, contentBuilder, mClickListener);
if (linked && mContentTitle != null) {
Spannable spannableContent = (Spannable) textView.getText();
spannableContent.setSpan(
new TextAppearanceSpan(getContext(), android.R.style.TextAppearance_Small),
0,
mContentTitle.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(spannableContent);
textView.setMovementMethod(new LinkMovementMethod());
}
}
/**
* Sets the linkable text for the Preference title.
*
* @param contentTitle text to set the Preference title.
* @param contentDescription description text to append underneath title, can be null.
* @param clickListener OnClickListener for the link portion of the text.
*/
public void setText(
CharSequence contentTitle,
@Nullable CharSequence contentDescription,
LinkifyUtils.OnClickListener clickListener) {
mContentTitle = contentTitle;
mContentDescription = contentDescription;
mClickListener = clickListener;
// sets the title so that the title TextView is not hidden in super.onBindViewHolder()
super.setTitle(contentTitle);
}
/**
* Sets the title of the LinkablePreference. resets linkable text for reusability.
*/
@Override
public void setTitle(int titleResId) {
mContentTitle = null;
mContentDescription = null;
super.setTitle(titleResId);
}
/**
* Sets the title of the LinkablePreference. resets linkable text for reusability.
*/
@Override
public void setTitle(CharSequence title) {
mContentTitle = null;
mContentDescription = null;
super.setTitle(title);
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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;
import android.content.Context;
import android.os.UserManager;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.RestrictedLockUtils;
import com.android.wifitrackerlib.WifiEntry;
/**
* WifiEntryPreference that can be long pressed.
*/
public class LongPressWifiEntryPreference extends WifiEntryPreference {
private final Fragment mFragment;
public LongPressWifiEntryPreference(Context context, WifiEntry wifiEntry, Fragment fragment) {
super(context, wifiEntry);
mFragment = fragment;
checkRestrictionAndSetDisabled();
}
@Override
public void onBindViewHolder(final PreferenceViewHolder view) {
super.onBindViewHolder(view);
if (mFragment != null) {
view.itemView.setOnCreateContextMenuListener(mFragment);
view.itemView.setTag(this);
view.itemView.setLongClickable(true);
}
}
@Override
public void refresh() {
super.refresh();
setEnabled(shouldEnabled());
}
@VisibleForTesting
boolean shouldEnabled() {
WifiEntry wifiEntry = getWifiEntry();
if (wifiEntry == null) return false;
boolean enabled = wifiEntry.canConnect();
// If Wi-Fi is connected or saved network, leave it enabled to disconnect or configure.
if (!enabled && (wifiEntry.canDisconnect() || wifiEntry.isSaved())) {
enabled = true;
}
return enabled;
}
@VisibleForTesting
void checkRestrictionAndSetDisabled() {
if (!getWifiEntry().hasAdminRestrictions()) {
return;
}
RestrictedLockUtils.EnforcedAdmin admin = null;
Context context = getContext();
if (context != null) {
admin = RestrictedLockUtils.getProfileOrDeviceOwner(context, context.getUser());
}
if (admin == null) {
// Use UserManager.DISALLOW_ADD_WIFI_CONFIG as default Wi-Fi network restriction.
admin = RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
UserManager.DISALLOW_ADD_WIFI_CONFIG);
}
setDisabledByAdmin(admin);
}
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright (C) 2018 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;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.android.settings.R;
import com.android.settings.wifi.NetworkRequestErrorDialogFragment.ERROR_DIALOG_TYPE;
import java.util.List;
/**
* When other applications request to have a wifi connection, framework will bring up this activity
* to let user select which wifi ap wanna to connect. This activity contains
* {@code NetworkRequestDialogFragment}, {@code NetworkRequestSingleSsidDialogFragment} to show UI
* and handles framework callback.
*/
public class NetworkRequestDialogActivity extends FragmentActivity implements
NetworkRequestMatchCallback {
private static String TAG = "NetworkRequestDialogActivity";
/** Message sent to stop scanning wifi and pop up timeout dialog. */
private static final int MESSAGE_STOP_SCAN_WIFI_LIST = 0;
/** Delayed time to stop scanning wifi. */
private static final int DELAY_TIME_STOP_SCAN_MS = 30 * 1000;
final static String EXTRA_IS_SPECIFIED_SSID =
"com.android.settings.wifi.extra.REQUEST_IS_FOR_SINGLE_NETWORK";
@VisibleForTesting
NetworkRequestDialogBaseFragment mDialogFragment;
@VisibleForTesting
boolean mIsSpecifiedSsid;
@VisibleForTesting
boolean mShowingErrorDialog;
@VisibleForTesting
ProgressDialog mProgressDialog;
private NetworkRequestUserSelectionCallback mUserSelectionCallback;
private WifiConfiguration mMatchedConfig;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
if (intent != null) {
mIsSpecifiedSsid = intent.getBooleanExtra(EXTRA_IS_SPECIFIED_SSID, false);
}
if (mIsSpecifiedSsid) {
showProgressDialog(getString(R.string.network_connection_searching_message));
} else {
mDialogFragment = NetworkRequestDialogFragment.newInstance();
mDialogFragment.show(getSupportFragmentManager(), TAG);
}
}
private void showProgressDialog(String message) {
dismissDialogs();
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setIndeterminate(true);
mProgressDialog.setCancelable(false);
mProgressDialog.setMessage(message);
mProgressDialog.show();
}
private void showSingleSsidRequestDialog(String ssid, boolean isTryAgain) {
dismissDialogs();
mDialogFragment = new NetworkRequestSingleSsidDialogFragment();
final Bundle bundle = new Bundle();
bundle.putString(NetworkRequestSingleSsidDialogFragment.EXTRA_SSID, ssid);
bundle.putBoolean(NetworkRequestSingleSsidDialogFragment.EXTRA_TRYAGAIN, isTryAgain);
mDialogFragment.setArguments(bundle);
mDialogFragment.show(getSupportFragmentManager(), TAG);
}
@VisibleForTesting
void dismissDialogs() {
if (mDialogFragment != null) {
mDialogFragment.dismiss();
mDialogFragment = null;
}
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
}
@Override
protected void onResume() {
super.onResume();
final WifiManager wifiManager = getSystemService(WifiManager.class);
if (wifiManager != null) {
wifiManager.registerNetworkRequestMatchCallback(new HandlerExecutor(mHandler), this);
}
// Sets time-out to stop scanning.
mHandler.sendEmptyMessageDelayed(MESSAGE_STOP_SCAN_WIFI_LIST, DELAY_TIME_STOP_SCAN_MS);
}
@Override
protected void onPause() {
mHandler.removeMessages(MESSAGE_STOP_SCAN_WIFI_LIST);
final WifiManager wifiManager = getSystemService(WifiManager.class);
if (wifiManager != null) {
wifiManager.unregisterNetworkRequestMatchCallback(this);
}
super.onPause();
}
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_STOP_SCAN_WIFI_LIST:
removeMessages(MESSAGE_STOP_SCAN_WIFI_LIST);
stopScanningAndPopErrorDialog(ERROR_DIALOG_TYPE.TIME_OUT);
break;
default:
// Do nothing.
break;
}
}
};
protected void stopScanningAndPopErrorDialog(ERROR_DIALOG_TYPE type) {
dismissDialogs();
// Throws error dialog.
final FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) {
return;
}
final NetworkRequestErrorDialogFragment dialogFragment =
NetworkRequestErrorDialogFragment.newInstance();
dialogFragment.setRejectCallback(mUserSelectionCallback);
final Bundle bundle = new Bundle();
bundle.putSerializable(NetworkRequestErrorDialogFragment.DIALOG_TYPE, type);
dialogFragment.setArguments(bundle);
dialogFragment.show(fragmentManager, TAG);
mShowingErrorDialog = true;
}
@Override
public void onUserSelectionCallbackRegistration(
NetworkRequestUserSelectionCallback userSelectionCallback) {
if (mIsSpecifiedSsid) {
mUserSelectionCallback = userSelectionCallback;
return;
}
if (mDialogFragment != null) {
mDialogFragment.onUserSelectionCallbackRegistration(userSelectionCallback);
}
}
@Override
public void onAbort() {
stopScanningAndPopErrorDialog(ERROR_DIALOG_TYPE.ABORT);
}
@Override
public void onMatch(List<ScanResult> scanResults) {
if (mShowingErrorDialog) {
// Don't do anything since error dialog shows.
return;
}
mHandler.removeMessages(MESSAGE_STOP_SCAN_WIFI_LIST);
if (mIsSpecifiedSsid) {
// Prevent from throwing same dialog, because onMatch() will be called many times.
if (mMatchedConfig == null) {
mMatchedConfig = WifiUtils.getWifiConfig(null /* wifiEntry */, scanResults.get(0));
showSingleSsidRequestDialog(
WifiInfo.sanitizeSsid(mMatchedConfig.SSID), false /* isTryAgain */);
}
return;
}
if (mDialogFragment != null) {
mDialogFragment.onMatch(scanResults);
}
}
@Override
public void onUserSelectionConnectSuccess(WifiConfiguration wificonfiguration) {
if (!isFinishing()) {
Toast.makeText(this, R.string.network_connection_connect_successful, Toast.LENGTH_SHORT)
.show();
setResult(RESULT_OK);
finish();
}
}
@Override
public void onUserSelectionConnectFailure(WifiConfiguration wificonfiguration) {
if (!isFinishing()) {
Toast.makeText(this, R.string.network_connection_connect_failure, Toast.LENGTH_SHORT)
.show();
setResult(RESULT_OK);
finish();
}
}
// Called when user click "Connect" button. Called by
// {@code NetworkRequestSingleSsidDialogFragment}.
public void onClickConnectButton() {
if (mUserSelectionCallback != null) {
mUserSelectionCallback.select(mMatchedConfig);
showProgressDialog(getString(R.string.network_connection_connecting_message));
}
}
// Called when user click retry button. Called by {@link NetworkRequestErrorDialogFragment}.
public void onClickRescanButton() {
// Sets time-out to stop scanning.
mHandler.sendEmptyMessageDelayed(MESSAGE_STOP_SCAN_WIFI_LIST, DELAY_TIME_STOP_SCAN_MS);
mShowingErrorDialog = false;
if (mIsSpecifiedSsid) {
mMatchedConfig = null;
showProgressDialog(getString(R.string.network_connection_searching_message));
} else {
mDialogFragment = NetworkRequestDialogFragment.newInstance();
mDialogFragment.show(getSupportFragmentManager(), TAG);
}
}
// Called when user click cancel button.
public void onCancel() {
dismissDialogs();
if (mUserSelectionCallback != null) {
mUserSelectionCallback.reject();
}
finish();
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2019 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;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import java.util.List;
/**
* This is base fragment of {@link NetworkRequestDialogFragment} and
* {@link NetworkRequestSingleSsidDialogFragment} to handle activity callback methods.
*/
abstract public class NetworkRequestDialogBaseFragment extends InstrumentedDialogFragment {
@VisibleForTesting
final static String EXTRA_APP_NAME = "com.android.settings.wifi.extra.APP_NAME";
NetworkRequestDialogActivity mActivity = null;
private String mAppName = "";
@Override
public int getMetricsCategory() {
return SettingsEnums.WIFI_SCANNING_NEEDED_DIALOG;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof NetworkRequestDialogActivity) {
mActivity = (NetworkRequestDialogActivity) context;
}
final Intent intent = getActivity().getIntent();
if (intent != null) {
mAppName = intent.getStringExtra(EXTRA_APP_NAME);
}
}
@Override
public void onDetach() {
super.onDetach();
mActivity = null;
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
if (mActivity != null) {
mActivity.onCancel();
}
}
protected String getTitle() {
return getString(R.string.network_connection_request_dialog_title);
}
protected String getSummary() {
return getString(R.string.network_connection_request_dialog_summary, mAppName);
}
protected void onUserSelectionCallbackRegistration(
NetworkRequestUserSelectionCallback userSelectionCallback) {
}
protected void onMatch(List<ScanResult> scanResults) {
}
}

View File

@@ -0,0 +1,401 @@
/*
* Copyright (C) 2018 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;
import static com.android.wifitrackerlib.Utils.getSecurityTypesFromScanResult;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
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.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.internal.PreferenceImageView;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
/**
* The Fragment sets up callback {@link NetworkRequestMatchCallback} with framework. To handle most
* behaviors of the callback when requesting wifi network, except for error message. When error
* happens, {@link NetworkRequestErrorDialogFragment} will be called to display error message.
*/
public class NetworkRequestDialogFragment extends NetworkRequestDialogBaseFragment implements
DialogInterface.OnClickListener, WifiPickerTracker.WifiPickerTrackerCallback {
private static final String TAG = "NetworkRequestDialogFragment";
/**
* Spec defines there should be 5 wifi ap on the list at most or just show all if {@code
* mShowLimitedItem} is false.
*/
private static final int MAX_NUMBER_LIST_ITEM = 5;
private boolean mShowLimitedItem = true;
private static class MatchWifi {
String mSsid;
List<Integer> mSecurityTypes;
}
private List<MatchWifi> mMatchWifis = new ArrayList<>();
@VisibleForTesting List<WifiEntry> mFilteredWifiEntries = new ArrayList<>();
private WifiEntryAdapter mDialogAdapter;
private NetworkRequestUserSelectionCallback mUserSelectionCallback;
@VisibleForTesting WifiPickerTracker mWifiPickerTracker;
// Worker thread used for WifiPickerTracker work.
private HandlerThread mWorkerThread;
// Max age of tracked WifiEntries.
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating WifiPickerTracker scans.
private static final long SCAN_INTERVAL_MILLIS = 10_000;
public static NetworkRequestDialogFragment newInstance() {
NetworkRequestDialogFragment dialogFragment = new NetworkRequestDialogFragment();
return dialogFragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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();
}
};
final Context context = getContext();
mWifiPickerTracker = FeatureFactory.getFeatureFactory()
.getWifiTrackerLibProvider()
.createWifiPickerTracker(getSettingsLifecycle(), context,
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
this);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getContext();
// Prepares title.
final LayoutInflater inflater = LayoutInflater.from(context);
final View customTitle = inflater.inflate(R.layout.network_request_dialog_title, null);
final TextView title = customTitle.findViewById(R.id.network_request_title_text);
title.setText(getTitle());
final TextView summary = customTitle.findViewById(R.id.network_request_summary_text);
summary.setText(getSummary());
final ProgressBar progressBar = customTitle.findViewById(
R.id.network_request_title_progress);
progressBar.setVisibility(View.VISIBLE);
// Prepares adapter.
mDialogAdapter = new WifiEntryAdapter(context,
com.android.settingslib.R.layout.preference_access_point, mFilteredWifiEntries);
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setCustomTitle(customTitle)
.setAdapter(mDialogAdapter, this)
.setNegativeButton(R.string.cancel, (dialog, which) -> onCancel(dialog))
// Do nothings, will replace the onClickListener to avoid auto closing dialog.
.setNeutralButton(R.string.network_connection_request_dialog_showall,
null /* OnClickListener */);
// Clicking list item is to connect wifi ap.
final AlertDialog dialog = builder.create();
dialog.getListView().setOnItemClickListener(
(parent, view, position, id) -> this.onClick(dialog, position));
// Don't dismiss dialog when touching outside. User reports it is easy to touch outside.
// This causes dialog to close.
setCancelable(false);
dialog.setOnShowListener((dialogInterface) -> {
// Replace NeutralButton onClickListener to avoid closing dialog
final Button neutralBtn = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
neutralBtn.setVisibility(View.GONE);
neutralBtn.setOnClickListener(v -> {
mShowLimitedItem = false;
updateWifiEntries();
updateUi();
neutralBtn.setVisibility(View.GONE);
});
});
return dialog;
}
private BaseAdapter getDialogAdapter() {
return mDialogAdapter;
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (mFilteredWifiEntries.size() == 0 || which >= mFilteredWifiEntries.size()) {
return; // Invalid values.
}
if (mUserSelectionCallback == null) {
return; // Callback is missing or not ready.
}
final WifiEntry wifiEntry = mFilteredWifiEntries.get(which);
WifiConfiguration config = wifiEntry.getWifiConfiguration();
if (config == null) {
config = WifiUtils.getWifiConfig(wifiEntry, null /* scanResult */);
}
mUserSelectionCallback.select(config);
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
if (mUserSelectionCallback != null) {
mUserSelectionCallback.reject();
}
}
@Override
public void onDestroy() {
mWorkerThread.quit();
super.onDestroy();
}
private void showAllButton() {
final AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog == null) {
return;
}
final Button neutralBtn = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
if (neutralBtn != null) {
neutralBtn.setVisibility(View.VISIBLE);
}
}
private void hideProgressIcon() {
final AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog == null) {
return;
}
final View progress = alertDialog.findViewById(R.id.network_request_title_progress);
if (progress != null) {
progress.setVisibility(View.GONE);
}
}
/** Called when the state of Wifi has changed. */
@Override
public void onWifiStateChanged() {
if (mMatchWifis.size() == 0) {
return;
}
updateWifiEntries();
updateUi();
}
/**
* Update the results when data changes
*/
@Override
public void onWifiEntriesChanged() {
if (mMatchWifis.size() == 0) {
return;
}
updateWifiEntries();
updateUi();
}
@Override
public void onNumSavedSubscriptionsChanged() {
// Do nothing.
}
@Override
public void onNumSavedNetworksChanged() {
// Do nothing.
}
@VisibleForTesting
void updateWifiEntries() {
final List<WifiEntry> wifiEntries = new ArrayList<>();
WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
String connectedSsid;
if (connectedWifiEntry != null) {
connectedSsid = connectedWifiEntry.getSsid();
wifiEntries.add(connectedWifiEntry);
} else {
connectedSsid = null;
}
wifiEntries.addAll(mWifiPickerTracker.getWifiEntries());
mFilteredWifiEntries.clear();
mFilteredWifiEntries.addAll(wifiEntries.stream()
.filter(entry -> isMatchedWifiEntry(entry, connectedSsid))
.limit(mShowLimitedItem ? MAX_NUMBER_LIST_ITEM : Long.MAX_VALUE)
.toList());
}
private boolean isMatchedWifiEntry(WifiEntry entry, String connectedSsid) {
if (entry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED
&& TextUtils.equals(entry.getSsid(), connectedSsid)) {
// WifiPickerTracker may return a duplicate unsaved network that is separate from
// the connecting app-requested network, so make sure we only show the connected
// app-requested one.
return false;
}
for (MatchWifi wifi : mMatchWifis) {
if (!TextUtils.equals(entry.getSsid(), wifi.mSsid)) {
continue;
}
for (Integer security : wifi.mSecurityTypes) {
if (entry.getSecurityTypes().contains(security)) {
return true;
}
}
}
return false;
}
private class WifiEntryAdapter extends ArrayAdapter<WifiEntry> {
private final int mResourceId;
private final LayoutInflater mInflater;
WifiEntryAdapter(Context context, int resourceId, List<WifiEntry> objects) {
super(context, resourceId, objects);
mResourceId = resourceId;
mInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
view = mInflater.inflate(mResourceId, parent, false);
final View divider = view.findViewById(
com.android.settingslib.widget.preference.twotarget.R.id.two_target_divider);
divider.setVisibility(View.GONE);
}
final WifiEntry wifiEntry = getItem(position);
final TextView titleView = view.findViewById(android.R.id.title);
if (titleView != null) {
// Shows whole SSID for better UX.
titleView.setSingleLine(false);
titleView.setText(wifiEntry.getTitle());
}
final TextView summary = view.findViewById(android.R.id.summary);
if (summary != null) {
final String summaryString = wifiEntry.getSummary();
if (TextUtils.isEmpty(summaryString)) {
summary.setVisibility(View.GONE);
} else {
summary.setVisibility(View.VISIBLE);
summary.setText(summaryString);
}
}
final PreferenceImageView imageView = view.findViewById(android.R.id.icon);
final int level = wifiEntry.getLevel();
if (imageView != null && level != WifiEntry.WIFI_LEVEL_UNREACHABLE) {
final Drawable drawable = getContext().getDrawable(
Utils.getWifiIconResource(level));
drawable.setTintList(
Utils.getColorAttr(getContext(), android.R.attr.colorControlNormal));
imageView.setImageDrawable(drawable);
}
return view;
}
}
@Override
public void onUserSelectionCallbackRegistration(
NetworkRequestUserSelectionCallback userSelectionCallback) {
mUserSelectionCallback = userSelectionCallback;
}
@Override
public void onMatch(List<ScanResult> scanResults) {
mMatchWifis.clear();
for (ScanResult scanResult : scanResults) {
MatchWifi matchWifi = new MatchWifi();
matchWifi.mSsid = scanResult.SSID;
matchWifi.mSecurityTypes = getSecurityTypesFromScanResult(scanResult);
mMatchWifis.add(matchWifi);
}
updateWifiEntries();
updateUi();
}
@VisibleForTesting
void updateUi() {
// Update related UI buttons
if (mShowLimitedItem && mFilteredWifiEntries.size() >= MAX_NUMBER_LIST_ITEM) {
showAllButton();
}
if (mFilteredWifiEntries.size() > 0) {
hideProgressIcon();
}
if (getDialogAdapter() != null) {
getDialogAdapter().notifyDataSetChanged();
}
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2018 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;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
/**
* The dialog shows an error message when requesting network {@link NetworkRequestDialogFragment}.
* Contains multi-error types in {@code ERROR_DIALOG_TYPE}.
*/
public class NetworkRequestErrorDialogFragment extends InstrumentedDialogFragment {
public static final String DIALOG_TYPE = "DIALOG_ERROR_TYPE";
public enum ERROR_DIALOG_TYPE {TIME_OUT, ABORT}
@Nullable
private NetworkRequestUserSelectionCallback mRejectCallback;
public static NetworkRequestErrorDialogFragment newInstance() {
return new NetworkRequestErrorDialogFragment();
}
@Override
public void onCancel(@NonNull DialogInterface dialog) {
super.onCancel(dialog);
// Wants to finish the activity when user clicks back key or outside of the dialog.
rejectNetworkRequestAndFinish();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Gets error type to construct dialog. Default is TIME_OUT dialog.
ERROR_DIALOG_TYPE msgType = ERROR_DIALOG_TYPE.TIME_OUT;
if (getArguments() != null) {
msgType = (ERROR_DIALOG_TYPE) getArguments().getSerializable(DIALOG_TYPE);
}
final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
if (msgType == ERROR_DIALOG_TYPE.TIME_OUT) {
builder.setMessage(R.string.network_connection_timeout_dialog_message)
.setPositiveButton(R.string.network_connection_timeout_dialog_ok,
(dialog, which) -> onRescanClick())
.setNegativeButton(R.string.cancel,
(dialog, which) -> rejectNetworkRequestAndFinish());
} else {
builder.setMessage(R.string.network_connection_errorstate_dialog_message)
.setPositiveButton(R.string.okay,
(dialog, which) -> rejectNetworkRequestAndFinish());
}
return builder.create();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.WIFI_SCANNING_NEEDED_DIALOG;
}
// Sets the callback for fragment to reject this request.
public void setRejectCallback(NetworkRequestUserSelectionCallback rejectCallback) {
mRejectCallback = rejectCallback;
}
protected void onRescanClick() {
if (getActivity() != null) {
dismiss();
((NetworkRequestDialogActivity)getActivity()).onClickRescanButton();
}
}
private void rejectNetworkRequestAndFinish() {
if (getActivity() != null) {
if (mRejectCallback != null) {
mRejectCallback.reject();
}
getActivity().finish();
}
}
}

View File

@@ -0,0 +1,63 @@
package com.android.settings.wifi;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
/**
* This is similar fragment with {@link NetworkRequestDialogFragment} but only for single SSID mode.
*/
public class NetworkRequestSingleSsidDialogFragment extends
NetworkRequestDialogBaseFragment {
public static final String EXTRA_SSID = "DIALOG_REQUEST_SSID";
public static final String EXTRA_TRYAGAIN = "DIALOG_IS_TRYAGAIN";
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
boolean isTryAgain = false;
String requestSsid = "";
if (getArguments() != null) {
isTryAgain = getArguments().getBoolean(EXTRA_TRYAGAIN, true);
requestSsid = getArguments().getString(EXTRA_SSID, "");
}
final Context context = getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
final View customTitle = inflater.inflate(R.layout.network_request_dialog_title, null);
final TextView title = customTitle.findViewById(R.id.network_request_title_text);
title.setText(getTitle());
final TextView summary = customTitle.findViewById(R.id.network_request_summary_text);
summary.setText(getSummary());
final ProgressBar progressBar = customTitle
.findViewById(R.id.network_request_title_progress);
progressBar.setVisibility(View.GONE);
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setCustomTitle(customTitle)
.setMessage(requestSsid)
.setPositiveButton(isTryAgain ? R.string.network_connection_timeout_dialog_ok
: R.string.wifi_connect, (dialog, which) -> onUserClickConnectButton())
.setNeutralButton(R.string.cancel, (dialog, which) -> onCancel(dialog));
// Don't dismiss dialog when touching outside. User reports it is easy to touch outside.
// This causes dialog to close.
setCancelable(false);
return builder.create();
}
private void onUserClickConnectButton() {
if (mActivity != null) {
mActivity.onClickConnectButton();
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2017 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;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.TogglePreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
/**
* {@link TogglePreferenceController} that controls whether we should notify user when open
* network is available.
*/
public class NotifyOpenNetworksPreferenceController extends TogglePreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnResume, OnPause {
private static final String KEY_NOTIFY_OPEN_NETWORKS = "notify_open_networks";
private SettingObserver mSettingObserver;
public NotifyOpenNetworksPreferenceController(Context context) {
super(context, KEY_NOTIFY_OPEN_NETWORKS);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mSettingObserver = new SettingObserver(screen.findPreference(getPreferenceKey()));
}
@Override
public void onResume() {
if (mSettingObserver != null) {
mSettingObserver.register(mContext.getContentResolver(), true /* register */);
}
}
@Override
public void onPause() {
if (mSettingObserver != null) {
mSettingObserver.register(mContext.getContentResolver(), false /* register */);
}
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
isChecked ? 1 : 0);
return true;
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_network;
}
class SettingObserver extends ContentObserver {
private final Uri NETWORKS_AVAILABLE_URI = Settings.Global.getUriFor(
Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
private final Preference mPreference;
public SettingObserver(Preference preference) {
super(new Handler());
mPreference = preference;
}
public void register(ContentResolver cr, boolean register) {
if (register) {
cr.registerContentObserver(NETWORKS_AVAILABLE_URI, false, this);
} else {
cr.unregisterContentObserver(this);
}
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
if (NETWORKS_AVAILABLE_URI.equals(uri)) {
updateState(mPreference);
}
}
}
}

View File

@@ -0,0 +1,6 @@
# Default reviewers for this and subdirectories.
andychou@google.com
arcwang@google.com
changbetty@google.com
songferngwang@google.com
wengsu@google.com

View File

@@ -0,0 +1,339 @@
/*
* Copyright (C) 2016 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;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AlertActivity;
import com.android.settings.R;
/**
* This activity handles requests to toggle WiFi by collecting user
* consent and waiting until the state change is completed.
*/
public class RequestToggleWiFiActivity extends AlertActivity
implements DialogInterface.OnClickListener {
private static final String LOG_TAG = "RequestToggleWiFiActivity";
private static final long TOGGLE_TIMEOUT_MILLIS = 10000; // 10 sec
private static final int STATE_UNKNOWN = -1;
private static final int STATE_ENABLE = 1;
private static final int STATE_ENABLING = 2;
private static final int STATE_DISABLE = 3;
private static final int STATE_DISABLING = 4;
private final StateChangeReceiver mReceiver = new StateChangeReceiver();
private final Runnable mTimeoutCommand = () -> {
if (!isFinishing() && !isDestroyed()) {
finish();
}
};
private @NonNull WifiManager mWiFiManager;
private @NonNull CharSequence mAppLabel;
@VisibleForTesting
protected IActivityManager mActivityManager = ActivityManager.getService();
private int mState = STATE_UNKNOWN;
private int mLastUpdateState = STATE_UNKNOWN;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
mWiFiManager = getSystemService(WifiManager.class);
setResult(Activity.RESULT_CANCELED);
mAppLabel = getAppLabel();
if (TextUtils.isEmpty(mAppLabel)) {
finish();
return;
}
String action = getIntent().getAction();
switch (action) {
case WifiManager.ACTION_REQUEST_ENABLE: {
mState = STATE_ENABLE;
} break;
case WifiManager.ACTION_REQUEST_DISABLE: {
mState = STATE_DISABLE;
} break;
default: {
finish();
}
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE: {
switch (mState) {
case STATE_ENABLE: {
mWiFiManager.setWifiEnabled(true);
mState = STATE_ENABLING;
scheduleToggleTimeout();
updateUi();
} break;
case STATE_DISABLE: {
mWiFiManager.setWifiEnabled(false);
mState = STATE_DISABLING;
scheduleToggleTimeout();
updateUi();
} break;
}
}
break;
case DialogInterface.BUTTON_NEGATIVE: {
finish();
}
break;
}
}
@Override
protected void onStart() {
super.onStart();
mReceiver.register();
final int wifiState = mWiFiManager.getWifiState();
switch (mState) {
case STATE_ENABLE: {
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED: {
setResult(RESULT_OK);
finish();
} return;
case WifiManager.WIFI_STATE_ENABLING: {
mState = STATE_ENABLING;
scheduleToggleTimeout();
} break;
}
} break;
case STATE_DISABLE: {
switch (wifiState) {
case WifiManager.WIFI_STATE_DISABLED: {
setResult(RESULT_OK);
finish();
}
return;
case WifiManager.WIFI_STATE_ENABLING: {
mState = STATE_DISABLING;
scheduleToggleTimeout();
}
break;
}
} break;
case STATE_ENABLING: {
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED: {
setResult(RESULT_OK);
finish();
} return;
case WifiManager.WIFI_STATE_ENABLING: {
scheduleToggleTimeout();
} break;
case WifiManager.WIFI_STATE_DISABLED:
case WifiManager.WIFI_STATE_DISABLING: {
mState = STATE_ENABLE;
} break;
}
} break;
case STATE_DISABLING: {
switch (wifiState) {
case WifiManager.WIFI_STATE_DISABLED: {
setResult(RESULT_OK);
finish();
} return;
case WifiManager.WIFI_STATE_DISABLING: {
scheduleToggleTimeout();
} break;
case WifiManager.WIFI_STATE_ENABLED:
case WifiManager.WIFI_STATE_ENABLING: {
mState = STATE_DISABLE;
} break;
}
} break;
}
updateUi();
}
@Override
protected void onStop() {
mReceiver.unregister();
unscheduleToggleTimeout();
super.onStop();
}
@VisibleForTesting
protected CharSequence getAppLabel() {
String packageName;
try {
packageName = mActivityManager.getLaunchedFromPackage(getActivityToken());
if (TextUtils.isEmpty(packageName)) {
Log.d(LOG_TAG, "Package name is null");
return null;
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not get the package from activity manager");
return null;
}
try {
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
packageName, 0);
return applicationInfo.loadSafeLabel(getPackageManager(),
PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, PackageItemInfo.SAFE_LABEL_FLAG_TRIM
| PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Couldn't find app with package name " + packageName);
return null;
}
}
private void updateUi() {
if (mLastUpdateState == mState) {
return;
}
mLastUpdateState = mState;
switch (mState) {
case STATE_ENABLE: {
mAlertParams.mPositiveButtonText = getString(R.string.allow);
mAlertParams.mPositiveButtonListener = this;
mAlertParams.mNegativeButtonText = getString(R.string.deny);
mAlertParams.mNegativeButtonListener = this;
mAlertParams.mMessage = getString(R.string.wifi_ask_enable, mAppLabel);
} break;
case STATE_ENABLING: {
// Params set button text only if non-null, but we want a null
// button text to hide the button, so reset the controller directly.
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, null, null, null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, null, null, null);
mAlertParams.mPositiveButtonText = null;
mAlertParams.mPositiveButtonListener = null;
mAlertParams.mNegativeButtonText = null;
mAlertParams.mNegativeButtonListener = null;
mAlertParams.mMessage = getString(R.string.wifi_starting);
} break;
case STATE_DISABLE: {
mAlertParams.mPositiveButtonText = getString(R.string.allow);
mAlertParams.mPositiveButtonListener = this;
mAlertParams.mNegativeButtonText = getString(R.string.deny);
mAlertParams.mNegativeButtonListener = this;
mAlertParams.mMessage = getString(R.string.wifi_ask_disable, mAppLabel);
} break;
case STATE_DISABLING: {
// Params set button text only if non-null, but we want a null
// button text to hide the button, so reset the controller directly.
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, null, null, null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, null, null, null);
mAlertParams.mPositiveButtonText = null;
mAlertParams.mPositiveButtonListener = null;
mAlertParams.mNegativeButtonText = null;
mAlertParams.mNegativeButtonListener = null;
mAlertParams.mMessage = getString(R.string.wifi_stopping);
} break;
}
setupAlert();
}
@Override
public void dismiss() {
// Clicking on the dialog buttons dismisses the dialog and finishes
// the activity but we want to finish after the WiFi state changed.
}
private void scheduleToggleTimeout() {
getWindow().getDecorView().postDelayed(mTimeoutCommand, TOGGLE_TIMEOUT_MILLIS);
}
private void unscheduleToggleTimeout() {
getWindow().getDecorView().removeCallbacks(mTimeoutCommand);
}
private final class StateChangeReceiver extends BroadcastReceiver {
private final IntentFilter mFilter = new IntentFilter(
WifiManager.WIFI_STATE_CHANGED_ACTION);
public void register() {
registerReceiver(this, mFilter);
}
public void unregister() {
unregisterReceiver(this);
}
public void onReceive(Context context, Intent intent) {
Activity activity = RequestToggleWiFiActivity.this;
if (activity.isFinishing() || activity.isDestroyed()) {
return;
}
final int currentState = mWiFiManager.getWifiState();
switch (currentState) {
case WifiManager.WIFI_STATE_ENABLED:
case WifiManager.WIFI_STATE_DISABLED: {
if (mState == STATE_ENABLING || mState == STATE_DISABLING) {
RequestToggleWiFiActivity.this.setResult(Activity.RESULT_OK);
finish();
}
} break;
}
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2024 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
import android.content.Context
import android.net.wifi.WifiManager
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.fragment.app.Fragment
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.framework.compose.OverridableFlow
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.wifi.flags.Flags
import com.android.wifitrackerlib.WifiEntry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
/** Controller that controls whether the WEP network can be connected. */
class WepNetworksPreferenceController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) {
var wifiManager = context.getSystemService(WifiManager::class.java)!!
override fun getAvailabilityStatus() = if (Flags.androidVWifiApi()) AVAILABLE
else UNSUPPORTED_ON_DEVICE
@Composable
override fun Content() {
val checked by wepAllowedFlow.flow.collectAsStateWithLifecycle(initialValue = null)
var openDialog by rememberSaveable { mutableStateOf(false) }
val wifiInfo = wifiManager.connectionInfo
SwitchPreference(object : SwitchPreferenceModel {
override val title = stringResource(R.string.wifi_allow_wep_networks)
override val summary = { getSummary() }
override val checked = { checked }
override val changeable: () -> Boolean
get() = { carrierAllowed }
override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
if (!newChecked && wifiInfo.currentSecurityType == WifiEntry.SECURITY_WEP) {
openDialog = true
} else {
wifiManager.setWepAllowed(newChecked)
wepAllowedFlow.override(newChecked)
}
}
})
if (openDialog) {
SettingsAlertDialogWithIcon(
onDismissRequest = { openDialog = false },
confirmButton = AlertDialogButton(
stringResource(R.string.wifi_disconnect_button_text)
) {
wifiManager.setWepAllowed(false)
wepAllowedFlow.override(false)
openDialog = false
},
dismissButton =
AlertDialogButton(
stringResource(R.string.wifi_cancel)
) { openDialog = false },
title = String.format(
stringResource(R.string.wifi_settings_wep_networks_disconnect_title),
wifiInfo.ssid.removeSurrounding("\"")
),
text = {
Text(
stringResource(R.string.wifi_settings_wep_networks_disconnect_summary),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
})
}
}
override fun getSummary(): String = mContext.getString(
if (carrierAllowed) {
R.string.wifi_allow_wep_networks_summary
} else {
R.string.wifi_allow_wep_networks_summary_carrier_not_allow
}
)
private val carrierAllowed: Boolean
get() = wifiManager.isWepSupported
val wepAllowedFlow = OverridableFlow(callbackFlow {
wifiManager.queryWepAllowed(Dispatchers.Default.asExecutor(), ::trySend)
awaitClose { }
})
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (C) 2009 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;
import static android.content.Context.WIFI_SERVICE;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.text.Editable;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
/**
* Provide an interface for testing out the Wifi API
*/
public class WifiAPITest extends SettingsPreferenceFragment implements
Preference.OnPreferenceClickListener {
private static final String TAG = "WifiAPITest";
private int netid;
//============================
// Preference/activity member variables
//============================
private static final String KEY_DISCONNECT = "disconnect";
private static final String KEY_DISABLE_NETWORK = "disable_network";
private static final String KEY_ENABLE_NETWORK = "enable_network";
private Preference mWifiDisconnect;
private Preference mWifiDisableNetwork;
private Preference mWifiEnableNetwork;
private WifiManager mWifiManager;
//============================
// Activity lifecycle
//============================
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.wifi_api_test);
final PreferenceScreen preferenceScreen = getPreferenceScreen();
mWifiDisconnect = (Preference) preferenceScreen.findPreference(KEY_DISCONNECT);
mWifiDisconnect.setOnPreferenceClickListener(this);
mWifiDisableNetwork = (Preference) preferenceScreen.findPreference(KEY_DISABLE_NETWORK);
mWifiDisableNetwork.setOnPreferenceClickListener(this);
mWifiEnableNetwork = (Preference) preferenceScreen.findPreference(KEY_ENABLE_NETWORK);
mWifiEnableNetwork.setOnPreferenceClickListener(this);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.TESTING;
}
//============================
// Preference callbacks
//============================
@Override
public boolean onPreferenceTreeClick(Preference preference) {
super.onPreferenceTreeClick(preference);
return false;
}
/**
* Implements OnPreferenceClickListener interface
*/
public boolean onPreferenceClick(Preference pref) {
if (pref == mWifiDisconnect) {
mWifiManager.disconnect();
} else if (pref == mWifiDisableNetwork) {
AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
alert.setTitle("Input");
alert.setMessage("Enter Network ID");
// Set an EditText view to get user input
final EditText input = new EditText(getPrefContext());
alert.setView(input);
alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Editable value = input.getText();
try {
netid = Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
// Invalid netid
e.printStackTrace();
return;
}
mWifiManager.disableNetwork(netid);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Canceled.
}
});
alert.show();
} else if (pref == mWifiEnableNetwork) {
AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
alert.setTitle("Input");
alert.setMessage("Enter Network ID");
// Set an EditText view to get user input
final EditText input = new EditText(getPrefContext());
alert.setView(input);
alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Editable value = input.getText();
netid = Integer.parseInt(value.toString());
mWifiManager.enableNetwork(netid, false);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Canceled.
}
});
alert.show();
}
return true;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2009 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;
import android.app.Activity;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.widget.TextView;
import com.android.settings.R;
import java.util.List;
/**
* Configuration details saved by the user on the WifiSettings screen
*/
public class WifiConfigInfo extends Activity {
private TextView mConfigList;
private WifiManager mWifiManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
setContentView(R.layout.wifi_config_info);
mConfigList = (TextView) findViewById(R.id.config_list);
}
@Override
protected void onResume() {
super.onResume();
if (mWifiManager.isWifiEnabled()) {
final List<WifiConfiguration> wifiConfigs = mWifiManager.getConfiguredNetworks();
StringBuffer configList = new StringBuffer();
for (int i = wifiConfigs.size() - 1; i >= 0; i--) {
configList.append(wifiConfigs.get(i));
}
mConfigList.setText(configList);
} else {
mConfigList.setText(R.string.wifi_state_disabled);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2010 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;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.Button;
/**
* Foundation interface glues between Activities and UIs like {@link WifiDialog}.
*
* Migrating from Wi-Fi SettingsLib to to WifiTrackerLib, this object will be removed in the near
* future, please develop in {@link WifiConfigUiBase2}.
*/
public interface WifiConfigUiBase {
/**
* Viewing mode for a Wi-Fi access point. Data is displayed in non-editable mode.
*/
int MODE_VIEW = 0;
/**
* Connect mode. Data is displayed in editable mode, and a connect button will be shown.
*/
int MODE_CONNECT = 1;
/**
* Modify mode. All data is displayed in editable fields, and a "save" button is shown instead
* of "connect". Clients are expected to only save but not connect to the access point in this
* mode.
*/
int MODE_MODIFY = 2;
public Context getContext();
public WifiConfigController getController();
public LayoutInflater getLayoutInflater();
public int getMode();
public void dispatchSubmit();
public void setTitle(int id);
public void setTitle(CharSequence title);
public void setSubmitButton(CharSequence text);
public void setForgetButton(CharSequence text);
public void setCancelButton(CharSequence text);
public Button getSubmitButton();
public Button getForgetButton();
public Button getCancelButton();
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2019 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;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.Button;
/**
* Foundation interface glues between Activities and UIs like {@link WifiDialog2}.
*/
public interface WifiConfigUiBase2 {
/**
* Viewing mode for a Wi-Fi access point. Data is displayed in non-editable mode.
*/
int MODE_VIEW = 0;
/**
* Connect mode. Data is displayed in editable mode, and a connect button will be shown.
*/
int MODE_CONNECT = 1;
/**
* Modify mode. All data is displayed in editable fields, and a "save" button is shown instead
* of "connect". Clients are expected to only save but not connect to the access point in this
* mode.
*/
int MODE_MODIFY = 2;
/**
* UI like {@link WifiDialog} overrides to provide {@link Context} to controller.
*/
Context getContext();
/**
* {@link WifiConfigController2} share the logic for controlling buttons, text fields, etc.
*/
WifiConfigController2 getController();
/**
* UI like {@link WifiDialog} overrides to provide {@link LayoutInflater} to controller.
*/
LayoutInflater getLayoutInflater();
/**
* One of MODE_VIEW, MODE_CONNECT and MODE_MODIFY of the UI like {@link WifiDialog}.
*/
int getMode();
/**
* For controller to dispatch submit event to host UI and UI like {@link WifiDialog}.
*/
void dispatchSubmit();
/**
* UI like {@link WifiDialog} overrides to set title.
*/
void setTitle(int id);
/**
* UI like {@link WifiDialog} overrides to set title.
*/
void setTitle(CharSequence title);
/**
* UI like {@link WifiDialog} overrides to set submit button text.
*/
void setSubmitButton(CharSequence text);
/**
* UI like {@link WifiDialog} overrides to set forget button text.
*/
void setForgetButton(CharSequence text);
/**
* UI like {@link WifiDialog} overrides to set cancel button text.
*/
void setCancelButton(CharSequence text);
/**
* UI like {@link WifiDialog} overrides to get submit button.
*/
Button getSubmitButton();
/**
* UI like {@link WifiDialog} overrides to get forget button.
*/
Button getForgetButton();
/**
* UI like {@link WifiDialog} overrides to get cancel button.
*/
Button getCancelButton();
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright (C) 2018 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;
import android.content.Context;
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 androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
import java.time.ZoneOffset;
// TODO(b/151133650): Replace AbstractPreferenceController with BasePreferenceController.
/**
* This places a preference into a PreferenceGroup owned by some parent
* controller class when there is a wifi connection present.
*/
public class WifiConnectionPreferenceController extends AbstractPreferenceController implements
WifiPickerTracker.WifiPickerTrackerCallback, LifecycleObserver {
private static final String TAG = "WifiConnPrefCtrl";
private static final String KEY = "active_wifi_connection";
// Max age of tracked WifiEntries.
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating WifiPickerTracker scans.
private static final long SCAN_INTERVAL_MILLIS = 10_000;
private UpdateListener mUpdateListener;
private Context mPrefContext;
private String mPreferenceGroupKey;
private PreferenceGroup mPreferenceGroup;
@VisibleForTesting
public WifiPickerTracker mWifiPickerTracker;
private WifiEntryPreference mPreference;
private int order;
private int mMetricsCategory;
// Worker thread used for WifiPickerTracker work.
private HandlerThread mWorkerThread;
/**
* Used to notify a parent controller that this controller has changed in availability, or has
* updated the content in the preference that it manages.
*/
public interface UpdateListener {
void onChildrenUpdated();
}
/**
* @param context the context for the UI where we're placing the preference
* @param lifecycle for listening to lifecycle events for the UI
* @param updateListener for notifying a parent controller of changes
* @param preferenceGroupKey the key to use to lookup the PreferenceGroup where this controller
* will add its preference
* @param order the order that the preference added by this controller should use -
* useful when this preference needs to be ordered in a specific way
* relative to others in the PreferenceGroup
* @param metricsCategory - the category to use as the source when handling the click on the
* pref to go to the wifi connection detail page
*/
public WifiConnectionPreferenceController(Context context, Lifecycle lifecycle,
UpdateListener updateListener, String preferenceGroupKey, int order,
int metricsCategory) {
super(context);
lifecycle.addObserver(this);
mUpdateListener = updateListener;
mPreferenceGroupKey = preferenceGroupKey;
this.order = order;
mMetricsCategory = metricsCategory;
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();
}
};
mWifiPickerTracker = FeatureFactory.getFeatureFactory()
.getWifiTrackerLibProvider()
.createWifiPickerTracker(lifecycle, context,
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
this);
}
/**
* This event is triggered when users click back button at 'Network & internet'.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy() {
mWorkerThread.quit();
}
@Override
public boolean isAvailable() {
return mWifiPickerTracker.getConnectedWifiEntry() != null;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreferenceGroup = screen.findPreference(mPreferenceGroupKey);
mPrefContext = screen.getContext();
update();
}
private void updatePreference(WifiEntry wifiEntry) {
if (mPreference != null) {
mPreferenceGroup.removePreference(mPreference);
mPreference = null;
}
if (wifiEntry == null || mPrefContext == null) {
return;
}
mPreference = new WifiEntryPreference(mPrefContext, wifiEntry);
mPreference.setKey(KEY);
mPreference.refresh();
mPreference.setOrder(order);
mPreference.setOnPreferenceClickListener(pref -> {
final Bundle args = new Bundle();
args.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY,
wifiEntry.getKey());
new SubSettingLauncher(mPrefContext)
.setTitleRes(R.string.pref_title_network_details)
.setDestination(WifiNetworkDetailsFragment.class.getName())
.setArguments(args)
.setSourceMetricsCategory(mMetricsCategory)
.launch();
return true;
});
mPreferenceGroup.addPreference(mPreference);
}
private void update() {
final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
if (connectedWifiEntry == null) {
updatePreference(null);
} else {
if (mPreference == null || !mPreference.getWifiEntry().equals(connectedWifiEntry)) {
updatePreference(connectedWifiEntry);
} else if (mPreference != null) {
mPreference.refresh();
}
}
mUpdateListener.onChildrenUpdated();
}
/** Called when the state of Wifi has changed. */
@Override
public void onWifiStateChanged() {
update();
}
/**
* Update the results when data changes.
*/
@Override
public void onWifiEntriesChanged() {
update();
}
@Override
public void onNumSavedSubscriptionsChanged() {
// Do nothing.
}
@Override
public void onNumSavedNetworksChanged() {
// Do nothing.
}
}

View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2010 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;
import android.annotation.StyleRes;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.wifi.AccessPoint;
/**
* Dialog for users to edit a Wi-Fi network.
*
* Migrating from Wi-Fi SettingsLib to to WifiTrackerLib, this object will be removed in the near
* future, please develop in {@link WifiDialog2}.
*/
public class WifiDialog extends AlertDialog implements WifiConfigUiBase,
DialogInterface.OnClickListener {
public interface WifiDialogListener {
default void onForget(WifiDialog dialog) {
}
default void onSubmit(WifiDialog dialog) {
}
default void onScan(WifiDialog dialog, String ssid) {
}
}
private static final int BUTTON_SUBMIT = DialogInterface.BUTTON_POSITIVE;
private static final int BUTTON_FORGET = DialogInterface.BUTTON_NEUTRAL;
private final int mMode;
private final WifiDialogListener mListener;
private final AccessPoint mAccessPoint;
private View mView;
private WifiConfigController mController;
private boolean mHideSubmitButton;
/**
* Creates a WifiDialog with no additional style. It displays as a dialog above the current
* view.
*/
public static WifiDialog createModal(Context context, WifiDialogListener listener,
AccessPoint accessPoint, int mode) {
return new WifiDialog(context, listener, accessPoint, mode, 0 /* style */,
mode == WifiConfigUiBase.MODE_VIEW /* hideSubmitButton */);
}
/**
* Creates a WifiDialog with customized style. It displays as a dialog above the current
* view.
*/
public static WifiDialog createModal(Context context, WifiDialogListener listener,
AccessPoint accessPoint, int mode, @StyleRes int style) {
return new WifiDialog(context, listener, accessPoint, mode, style,
mode == WifiConfigUiBase.MODE_VIEW /* hideSubmitButton */);
}
/* package */ WifiDialog(Context context, WifiDialogListener listener, AccessPoint accessPoint,
int mode, @StyleRes int style, boolean hideSubmitButton) {
super(context, style);
mMode = mode;
mListener = listener;
mAccessPoint = accessPoint;
mHideSubmitButton = hideSubmitButton;
}
@Override
public WifiConfigController getController() {
return mController;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
mView = getLayoutInflater().inflate(R.layout.wifi_dialog, /* root */ null);
setView(mView);
mController = new WifiConfigController(this, mView, mAccessPoint, mMode);
super.onCreate(savedInstanceState);
if (mHideSubmitButton) {
mController.hideSubmitButton();
} else {
/* During creation, the submit button can be unavailable to determine
* visibility. Right after creation, update button visibility */
mController.enableSubmitIfAppropriate();
}
if (mAccessPoint == null) {
mController.hideForgetButton();
}
}
@SuppressWarnings("MissingSuperCall") // TODO: Fix me
@Override
protected void onStart() {
final ImageButton ssidScannerButton = findViewById(R.id.ssid_scanner_button);
if (mHideSubmitButton) {
ssidScannerButton.setVisibility(View.GONE);
return;
}
View.OnClickListener onClickScannerButtonListener = v -> {
if (mListener == null) {
return;
}
final TextView ssidEditText = findViewById(R.id.ssid);
final String ssid = ssidEditText.getText().toString();
mListener.onScan(/* WifiDialog */ this, ssid);
};
ssidScannerButton.setOnClickListener(onClickScannerButtonListener);
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mController.updatePassword();
}
@Override
public void dispatchSubmit() {
if (mListener != null) {
mListener.onSubmit(this);
}
dismiss();
}
@Override
public void onClick(DialogInterface dialogInterface, int id) {
if (mListener != null) {
switch (id) {
case BUTTON_SUBMIT:
mListener.onSubmit(this);
break;
case BUTTON_FORGET:
if (WifiUtils.isNetworkLockedDown(getContext(), mAccessPoint.getConfig())) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
RestrictedLockUtilsInternal.getDeviceOwner(getContext()));
return;
}
mListener.onForget(this);
break;
}
}
}
@Override
public int getMode() {
return mMode;
}
@Override
public Button getSubmitButton() {
return getButton(BUTTON_SUBMIT);
}
@Override
public Button getForgetButton() {
return getButton(BUTTON_FORGET);
}
@Override
public Button getCancelButton() {
return getButton(BUTTON_NEGATIVE);
}
@Override
public void setSubmitButton(CharSequence text) {
setButton(BUTTON_SUBMIT, text, this);
}
@Override
public void setForgetButton(CharSequence text) {
setButton(BUTTON_FORGET, text, this);
}
@Override
public void setCancelButton(CharSequence text) {
setButton(BUTTON_NEGATIVE, text, this);
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2023 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
import android.annotation.StyleRes
import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import android.widget.Button
import android.widget.ImageButton
import android.widget.TextView
import androidx.annotation.OpenForTesting
import androidx.appcompat.app.AlertDialog
import com.android.settings.R
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtilsInternal
import com.android.wifitrackerlib.WifiEntry
/**
* Dialog for users to edit a Wi-Fi network.
*/
@OpenForTesting
open class WifiDialog2 @JvmOverloads constructor(
context: Context,
private val listener: WifiDialog2Listener,
val wifiEntry: WifiEntry?,
private val mode: Int,
@StyleRes style: Int = 0,
private val hideSubmitButton: Boolean = mode == WifiConfigUiBase2.MODE_VIEW,
private val hideMeteredAndPrivacy: Boolean = false,
) : AlertDialog(context, style), WifiConfigUiBase2, DialogInterface.OnClickListener {
/**
* Host UI component of WifiDialog2 can receive callbacks by this interface.
*/
interface WifiDialog2Listener {
/**
* To forget the Wi-Fi network.
*/
fun onForget(dialog: WifiDialog2) {}
/**
* To save the Wi-Fi network.
*/
fun onSubmit(dialog: WifiDialog2) {}
/**
* To trigger Wi-Fi QR code scanner.
*/
fun onScan(dialog: WifiDialog2, ssid: String) {}
}
private lateinit var view: View
private lateinit var controller: WifiConfigController2
override fun getController(): WifiConfigController2 = controller
override fun onCreate(savedInstanceState: Bundle?) {
setWindowsOverlay()
view = layoutInflater.inflate(R.layout.wifi_dialog, null)
setView(view)
controller = WifiConfigController2(this, view, wifiEntry, mode, hideMeteredAndPrivacy)
super.onCreate(savedInstanceState)
if (hideSubmitButton) {
controller.hideSubmitButton()
} else {
// During creation, the submit button can be unavailable to determine visibility.
// Right after creation, update button visibility
controller.enableSubmitIfAppropriate()
}
if (wifiEntry == null) {
controller.hideForgetButton()
}
}
private fun setWindowsOverlay() {
window?.apply {
val lp = attributes
setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
attributes = lp
}
}
override fun onStart() {
super.onStart()
val ssidScannerButton = requireViewById<ImageButton>(R.id.ssid_scanner_button)
if (hideSubmitButton) {
ssidScannerButton.visibility = View.GONE
} else {
ssidScannerButton.setOnClickListener {
val ssidEditText = requireViewById<TextView>(R.id.ssid)
val ssid = ssidEditText.text.toString()
listener.onScan(this, ssid)
}
}
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
controller.updatePassword()
}
override fun dispatchSubmit() {
listener.onSubmit(this)
dismiss()
}
override fun onClick(dialogInterface: DialogInterface, id: Int) {
when (id) {
BUTTON_SUBMIT -> listener.onSubmit(this)
BUTTON_FORGET -> {
if (WifiUtils.isNetworkLockedDown(context, wifiEntry!!.wifiConfiguration)) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
context,
RestrictedLockUtilsInternal.getDeviceOwner(context)
)
return
}
listener.onForget(this)
}
}
}
override fun getMode(): Int = mode
override fun getSubmitButton(): Button? = getButton(BUTTON_SUBMIT)
override fun getForgetButton(): Button? = getButton(BUTTON_FORGET)
override fun getCancelButton(): Button? = getButton(BUTTON_NEGATIVE)
override fun setSubmitButton(text: CharSequence) {
setButton(BUTTON_SUBMIT, text, this)
}
override fun setForgetButton(text: CharSequence) {
setButton(BUTTON_FORGET, text, this)
}
override fun setCancelButton(text: CharSequence) {
setButton(BUTTON_NEGATIVE, text, this)
}
companion object {
private const val BUTTON_SUBMIT = BUTTON_POSITIVE
private const val BUTTON_FORGET = BUTTON_NEUTRAL
}
}

View File

@@ -0,0 +1,479 @@
/*
* Copyright (C) 2015 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;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG;
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
import android.app.KeyguardManager;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
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.UserManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.dpp.WifiDppUtils;
import com.android.settingslib.core.lifecycle.ObservableActivity;
import com.android.settingslib.wifi.AccessPoint;
import com.android.wifitrackerlib.NetworkDetailsTracker;
import com.android.wifitrackerlib.WifiEntry;
import com.google.android.setupcompat.util.WizardManagerHelper;
import com.google.android.setupdesign.util.ThemeHelper;
import java.lang.ref.WeakReference;
import java.time.Clock;
import java.time.ZoneOffset;
/**
* The activity shows a Wi-fi editor dialog.
*
* TODO(b/152571756): This activity supports both WifiTrackerLib and SettingsLib because this is an
* exported UI component, some other APPs (e.g., SetupWizard) still use
* SettingsLib. Remove the SettingsLib compatible part after these APPs use
* WifiTrackerLib.
*/
public class WifiDialogActivity extends ObservableActivity implements WifiDialog.WifiDialogListener,
WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener {
private static final String TAG = "WifiDialogActivity";
// For the callers which support WifiTrackerLib.
public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
// For the callers which support SettingsLib.
public static final String KEY_ACCESS_POINT_STATE = "access_point_state";
/**
* Boolean extra indicating whether this activity should connect to an access point on the
* caller's behalf. If this is set to false, the caller should check
* {@link #KEY_WIFI_CONFIGURATION} in the result data and save that using
* {@link WifiManager#connect(WifiConfiguration, ActionListener)}. Default is true.
*/
@VisibleForTesting
static final String KEY_CONNECT_FOR_CALLER = "connect_for_caller";
public static final String KEY_WIFI_CONFIGURATION = "wifi_configuration";
@VisibleForTesting
static final int RESULT_CONNECTED = RESULT_FIRST_USER;
private static final int RESULT_FORGET = RESULT_FIRST_USER + 1;
@VisibleForTesting
static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0;
// Max age of tracked WifiEntries.
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating NetworkDetailsTracker scans.
private static final long SCAN_INTERVAL_MILLIS = 10_000;
@VisibleForTesting
WifiDialog mDialog;
private AccessPoint mAccessPoint;
@VisibleForTesting
WifiDialog2 mDialog2;
// The received intent supports a key of WifiTrackerLib or SettingsLib.
private boolean mIsWifiTrackerLib;
private Intent mIntent;
private NetworkDetailsTracker mNetworkDetailsTracker;
private HandlerThread mWorkerThread;
private WifiManager mWifiManager;
private LockScreenMonitor mLockScreenMonitor;
@Override
protected void onCreate(Bundle savedInstanceState) {
mIntent = getIntent();
if (WizardManagerHelper.isSetupWizardIntent(mIntent)) {
setTheme(SetupWizardUtils.getTransparentTheme(this, mIntent));
}
super.onCreate(savedInstanceState);
if (!isConfigWifiAllowed() || !isAddWifiConfigAllowed()) {
finish();
return;
}
mIsWifiTrackerLib = !TextUtils.isEmpty(mIntent.getStringExtra(KEY_CHOSEN_WIFIENTRY_KEY));
if (mIsWifiTrackerLib) {
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(
getLifecycle(),
this,
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
mIntent.getStringExtra(KEY_CHOSEN_WIFIENTRY_KEY));
} else {
final Bundle accessPointState = mIntent.getBundleExtra(KEY_ACCESS_POINT_STATE);
if (accessPointState != null) {
mAccessPoint = new AccessPoint(this, accessPointState);
}
}
}
@Override
protected void onStart() {
super.onStart();
if (mDialog2 != null || mDialog != null || !hasWifiManager()) {
return;
}
if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
createDialogWithSuwTheme();
} else {
if (mIsWifiTrackerLib) {
mDialog2 = new WifiDialog2(this, this,
mNetworkDetailsTracker.getWifiEntry(), WifiConfigUiBase2.MODE_CONNECT);
} else {
mDialog = WifiDialog.createModal(
this, this, mAccessPoint, WifiConfigUiBase.MODE_CONNECT);
}
}
if (mIsWifiTrackerLib) {
if (mDialog2 != null) {
mDialog2.show();
mDialog2.setOnDismissListener(this);
}
} else {
if (mDialog != null) {
mDialog.show();
mDialog.setOnDismissListener(this);
}
}
if (mDialog2 != null || mDialog != null) {
mLockScreenMonitor = new LockScreenMonitor(this);
}
}
@VisibleForTesting
protected void createDialogWithSuwTheme() {
final int targetStyle = ThemeHelper.isSetupWizardDayNightEnabled(this)
? R.style.SuwAlertDialogThemeCompat_DayNight :
R.style.SuwAlertDialogThemeCompat_Light;
if (mIsWifiTrackerLib) {
mDialog2 = new WifiDialog2(this, this,
mNetworkDetailsTracker.getWifiEntry(),
WifiConfigUiBase2.MODE_CONNECT, targetStyle);
} else {
mDialog = WifiDialog.createModal(this, this, mAccessPoint,
WifiConfigUiBase.MODE_CONNECT, targetStyle);
}
}
@Override
public void finish() {
overridePendingTransition(0, 0);
super.finish();
}
@Override
public void onDestroy() {
if (mIsWifiTrackerLib) {
if (mDialog2 != null && mDialog2.isShowing()) {
mDialog2 = null;
}
mWorkerThread.quit();
} else {
if (mDialog != null && mDialog.isShowing()) {
mDialog = null;
}
}
if (mLockScreenMonitor != null) {
mLockScreenMonitor.release();
mLockScreenMonitor = null;
}
super.onDestroy();
}
@Override
public void onForget(WifiDialog2 dialog) {
final WifiEntry wifiEntry = dialog.getController().getWifiEntry();
if (wifiEntry != null && wifiEntry.canForget()) {
wifiEntry.forget(null /* callback */);
}
setResult(RESULT_FORGET);
finish();
}
@Override
public void onForget(WifiDialog dialog) {
if (!hasWifiManager()) return;
final AccessPoint accessPoint = dialog.getController().getAccessPoint();
if (accessPoint != null) {
if (!accessPoint.isSaved()) {
if (accessPoint.getNetworkInfo() != null &&
accessPoint.getNetworkInfo().getState() != NetworkInfo.State.DISCONNECTED) {
// Network is active but has no network ID - must be ephemeral.
mWifiManager.disableEphemeralNetwork(
AccessPoint.convertToQuotedString(accessPoint.getSsidStr()));
} else {
// Should not happen, but a monkey seems to trigger it
Log.e(TAG, "Failed to forget invalid network " + accessPoint.getConfig());
}
} else {
mWifiManager.forget(accessPoint.getConfig().networkId, null /* listener */);
}
}
Intent resultData = new Intent();
if (accessPoint != null) {
Bundle accessPointState = new Bundle();
accessPoint.saveWifiState(accessPointState);
resultData.putExtra(KEY_ACCESS_POINT_STATE, accessPointState);
}
setResult(RESULT_FORGET);
finish();
}
@Override
public void onSubmit(WifiDialog2 dialog) {
if (!hasWifiManager()) return;
final WifiEntry wifiEntry = dialog.getController().getWifiEntry();
final WifiConfiguration config = dialog.getController().getConfig();
if (getIntent().getBooleanExtra(KEY_CONNECT_FOR_CALLER, true)) {
if (config == null && wifiEntry != null && wifiEntry.canConnect()) {
wifiEntry.connect(null /* callback */);
} else {
mWifiManager.connect(config, null /* listener */);
}
}
Intent resultData = hasPermissionForResult() ? createResultData(config, null) : null;
setResult(RESULT_CONNECTED, resultData);
finish();
}
@Override
public void onSubmit(WifiDialog dialog) {
if (!hasWifiManager()) return;
final WifiConfiguration config = dialog.getController().getConfig();
final AccessPoint accessPoint = dialog.getController().getAccessPoint();
if (getIntent().getBooleanExtra(KEY_CONNECT_FOR_CALLER, true)) {
if (config == null) {
if (accessPoint != null && accessPoint.isSaved()) {
mWifiManager.connect(accessPoint.getConfig(), null /* listener */);
}
} else {
mWifiManager.save(config, null /* listener */);
if (accessPoint != null) {
// accessPoint is null for "Add network"
NetworkInfo networkInfo = accessPoint.getNetworkInfo();
if (networkInfo == null || !networkInfo.isConnected()) {
mWifiManager.connect(config, null /* listener */);
}
}
}
}
Intent resultData = hasPermissionForResult() ? createResultData(config, accessPoint) : null;
setResult(RESULT_CONNECTED, resultData);
finish();
}
protected Intent createResultData(WifiConfiguration config, AccessPoint accessPoint) {
Intent result = new Intent();
if (accessPoint != null) {
Bundle accessPointState = new Bundle();
accessPoint.saveWifiState(accessPointState);
result.putExtra(KEY_ACCESS_POINT_STATE, accessPointState);
}
if (config != null) {
result.putExtra(KEY_WIFI_CONFIGURATION, config);
}
return result;
}
@Override
public void onDismiss(DialogInterface dialogInterface) {
mDialog2 = null;
mDialog = null;
finish();
}
@Override
public void onScan(WifiDialog2 dialog, String ssid) {
Intent intent = WifiDppUtils.getEnrolleeQrCodeScannerIntent(dialog.getContext(), ssid);
WizardManagerHelper.copyWizardManagerExtras(mIntent, intent);
// Launch QR code scanner to join a network.
startActivityForResult(intent, REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER);
}
@Override
public void onScan(WifiDialog dialog, String ssid) {
Intent intent = WifiDppUtils.getEnrolleeQrCodeScannerIntent(dialog.getContext(), ssid);
WizardManagerHelper.copyWizardManagerExtras(mIntent, intent);
// Launch QR code scanner to join a network.
startActivityForResult(intent, REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) {
if (resultCode != RESULT_OK) {
return;
}
if (hasPermissionForResult()) {
setResult(RESULT_CONNECTED, data);
} else {
setResult(RESULT_CONNECTED);
}
finish();
}
}
@VisibleForTesting
boolean isConfigWifiAllowed() {
UserManager userManager = getSystemService(UserManager.class);
if (userManager == null) return true;
final boolean isConfigWifiAllowed = !userManager.hasUserRestriction(DISALLOW_CONFIG_WIFI);
if (!isConfigWifiAllowed) {
Log.e(TAG, "The user is not allowed to configure Wi-Fi.");
EventLog.writeEvent(0x534e4554, "226133034", getApplicationContext().getUserId(),
"The user is not allowed to configure Wi-Fi.");
}
return isConfigWifiAllowed;
}
@VisibleForTesting
boolean isAddWifiConfigAllowed() {
UserManager userManager = getSystemService(UserManager.class);
if (userManager != null && userManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)) {
Log.e(TAG, "The user is not allowed to add Wi-Fi configuration.");
return false;
}
return true;
}
private boolean hasWifiManager() {
if (mWifiManager != null) return true;
mWifiManager = getSystemService(WifiManager.class);
return (mWifiManager != null);
}
protected boolean hasPermissionForResult() {
final String callingPackage = getCallingPackage();
if (callingPackage == null) {
Log.d(TAG, "Failed to get the calling package, don't return the result.");
EventLog.writeEvent(0x534e4554, "185126813", -1 /* UID */, "no calling package");
return false;
}
if (getPackageManager().checkPermission(ACCESS_FINE_LOCATION, callingPackage)
== PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "The calling package has ACCESS_FINE_LOCATION permission for result.");
return true;
}
Log.d(TAG, "The calling package does not have the necessary permissions for result.");
try {
EventLog.writeEvent(0x534e4554, "185126813",
getPackageManager().getPackageUid(callingPackage, 0 /* flags */),
"no permission");
} catch (PackageManager.NameNotFoundException e) {
EventLog.writeEvent(0x534e4554, "185126813", -1 /* UID */, "no permission");
Log.w(TAG, "Cannot find the UID, calling package: " + callingPackage, e);
}
return false;
}
void dismissDialog() {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
if (mDialog2 != null) {
mDialog2.dismiss();
mDialog2 = null;
}
}
@VisibleForTesting
static final class LockScreenMonitor implements KeyguardManager.KeyguardLockedStateListener {
private final WeakReference<WifiDialogActivity> mWifiDialogActivity;
private KeyguardManager mKeyguardManager;
LockScreenMonitor(WifiDialogActivity activity) {
mWifiDialogActivity = new WeakReference<>(activity);
mKeyguardManager = activity.getSystemService(KeyguardManager.class);
mKeyguardManager.addKeyguardLockedStateListener(activity.getMainExecutor(), this);
}
void release() {
if (mKeyguardManager == null) return;
mKeyguardManager.removeKeyguardLockedStateListener(this);
mKeyguardManager = null;
}
@Override
public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
if (!isKeyguardLocked) return;
WifiDialogActivity activity = mWifiDialogActivity.get();
if (activity == null) return;
activity.dismissDialog();
Log.e(TAG, "Dismiss Wi-Fi dialog to prevent leaking user data on lock screen!");
EventLog.writeEvent(0x534e4554, "231583603", -1 /* UID */,
"Leak Wi-Fi dialog on lock screen");
}
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2010 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;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.provider.Settings;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.widget.SwitchWidgetController;
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import java.util.concurrent.atomic.AtomicBoolean;
public class WifiEnabler implements SwitchWidgetController.OnSwitchChangeListener {
private final SwitchWidgetController mSwitchWidget;
private final WifiManager mWifiManager;
private final ConnectivityManager mConnectivityManager;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private Context mContext;
private boolean mListeningToOnSwitchChange = false;
private AtomicBoolean mConnected = new AtomicBoolean(false);
private boolean mStateMachineEvent;
private final IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
handleWifiStateChanged(mWifiManager.getWifiState());
} else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
if (!mConnected.get()) {
handleStateChanged(WifiInfo.getDetailedStateOf((SupplicantState)
intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));
}
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
WifiManager.EXTRA_NETWORK_INFO);
mConnected.set(info.isConnected());
handleStateChanged(info.getDetailedState());
}
}
};
public WifiEnabler(Context context, SwitchWidgetController switchWidget,
MetricsFeatureProvider metricsFeatureProvider) {
this(context, switchWidget, metricsFeatureProvider,
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
}
@VisibleForTesting
WifiEnabler(Context context, SwitchWidgetController switchWidget,
MetricsFeatureProvider metricsFeatureProvider,
ConnectivityManager connectivityManager) {
mContext = context;
mSwitchWidget = switchWidget;
mSwitchWidget.setListener(this);
mMetricsFeatureProvider = metricsFeatureProvider;
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mConnectivityManager = connectivityManager;
mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
// The order matters! We really should not depend on this. :(
mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
setupSwitchController();
}
public void setupSwitchController() {
final int state = mWifiManager.getWifiState();
handleWifiStateChanged(state);
if (!mListeningToOnSwitchChange) {
mSwitchWidget.startListening();
mListeningToOnSwitchChange = true;
}
mSwitchWidget.setupView();
}
public void teardownSwitchController() {
if (mListeningToOnSwitchChange) {
mSwitchWidget.stopListening();
mListeningToOnSwitchChange = false;
}
mSwitchWidget.teardownView();
}
public void resume(Context context) {
mContext = context;
// Wi-Fi state is sticky, so just let the receiver update UI
mContext.registerReceiver(mReceiver, mIntentFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
if (!mListeningToOnSwitchChange) {
mSwitchWidget.startListening();
mListeningToOnSwitchChange = true;
}
}
public void pause() {
mContext.unregisterReceiver(mReceiver);
if (mListeningToOnSwitchChange) {
mSwitchWidget.stopListening();
mListeningToOnSwitchChange = false;
}
}
private void handleWifiStateChanged(int state) {
// Clear any previous state
mSwitchWidget.setDisabledByAdmin(null);
switch (state) {
case WifiManager.WIFI_STATE_ENABLING:
break;
case WifiManager.WIFI_STATE_ENABLED:
setSwitchBarChecked(true);
mSwitchWidget.setEnabled(true);
break;
case WifiManager.WIFI_STATE_DISABLING:
break;
case WifiManager.WIFI_STATE_DISABLED:
setSwitchBarChecked(false);
mSwitchWidget.setEnabled(true);
break;
default:
setSwitchBarChecked(false);
mSwitchWidget.setEnabled(true);
}
}
private void setSwitchBarChecked(boolean checked) {
mStateMachineEvent = true;
mSwitchWidget.setChecked(checked);
mStateMachineEvent = false;
}
private void handleStateChanged(@SuppressWarnings("unused") NetworkInfo.DetailedState state) {
// After the refactoring from a CheckBoxPreference to a Switch, this method is useless since
// there is nowhere to display a summary.
// This code is kept in case a future change re-introduces an associated text.
/*
// WifiInfo is valid if and only if Wi-Fi is enabled.
// Here we use the state of the switch as an optimization.
if (state != null && mSwitch.isChecked()) {
WifiInfo info = mWifiManager.getConnectionInfo();
if (info != null) {
//setSummary(Summary.get(mContext, info.getSSID(), state));
}
}
*/
}
@Override
public boolean onSwitchToggled(boolean isChecked) {
//Do nothing if called as a result of a state machine event
if (mStateMachineEvent) {
return true;
}
// Show toast message if Wi-Fi is not allowed in airplane mode
if (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) {
Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
// Reset switch to off. No infinite check/listener loop.
mSwitchWidget.setChecked(false);
return false;
}
if (isChecked) {
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_WIFI_ON);
} else {
// Log if user was connected at the time of switching off.
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_WIFI_OFF,
mConnected.get());
}
if (!mWifiManager.setWifiEnabled(isChecked)) {
// Error
mSwitchWidget.setEnabled(true);
Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
}
return true;
}
}

View File

@@ -0,0 +1,335 @@
/*
* 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;
import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.text.TextUtils;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.R;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.Utils;
import com.android.settingslib.wifi.WifiUtils;
import com.android.wifitrackerlib.HotspotNetworkEntry;
import com.android.wifitrackerlib.WifiEntry;
/**
* Preference to display a WifiEntry in a wifi picker.
*/
public class WifiEntryPreference extends RestrictedPreference implements
WifiEntry.WifiEntryCallback,
View.OnClickListener {
private static final int[] STATE_SECURED = {
R.attr.state_encrypted
};
private static final int[] FRICTION_ATTRS = {
R.attr.wifi_friction
};
// These values must be kept within [WifiEntry.WIFI_LEVEL_MIN, WifiEntry.WIFI_LEVEL_MAX]
private static final int[] WIFI_CONNECTION_STRENGTH = {
R.string.accessibility_no_wifi,
R.string.accessibility_wifi_one_bar,
R.string.accessibility_wifi_two_bars,
R.string.accessibility_wifi_three_bars,
R.string.accessibility_wifi_signal_full
};
// StateListDrawable to display secured lock / metered "$" icon
@Nullable private final StateListDrawable mFrictionSld;
private final WifiUtils.InternetIconInjector mIconInjector;
private WifiEntry mWifiEntry;
private int mLevel = -1;
private boolean mShowX; // Shows the Wi-Fi signl icon of Pie+x when it's true.
private CharSequence mContentDescription;
private OnButtonClickListener mOnButtonClickListener;
public WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry) {
this(context, wifiEntry, new WifiUtils.InternetIconInjector(context));
}
@VisibleForTesting
WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry,
@NonNull WifiUtils.InternetIconInjector iconInjector) {
super(context);
setLayoutResource(R.layout.preference_access_point);
mFrictionSld = getFrictionStateListDrawable();
mIconInjector = iconInjector;
setWifiEntry(wifiEntry);
}
/**
* Set updated {@link WifiEntry} to refresh the preference
*
* @param wifiEntry An instance of {@link WifiEntry}
*/
public void setWifiEntry(@NonNull WifiEntry wifiEntry) {
mWifiEntry = wifiEntry;
mWifiEntry.setListener(this);
refresh();
}
public WifiEntry getWifiEntry() {
return mWifiEntry;
}
@Override
public void onBindViewHolder(final PreferenceViewHolder view) {
super.onBindViewHolder(view);
if (mWifiEntry.isVerboseSummaryEnabled()) {
TextView summary = (TextView) view.findViewById(android.R.id.summary);
if (summary != null) {
summary.setMaxLines(100);
}
}
final Drawable drawable = getIcon();
if (drawable != null) {
drawable.setLevel(mLevel);
}
view.itemView.setContentDescription(mContentDescription);
// Turn off divider
view.findViewById(com.android.settingslib.widget.preference.twotarget.R.id.two_target_divider)
.setVisibility(View.INVISIBLE);
// Enable the icon button when the help string in this WifiEntry is not null.
final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button);
final ImageView frictionImageView = (ImageView) view.findViewById(
R.id.friction_icon);
if (mWifiEntry.getHelpUriString() != null
&& mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
final Drawable drawablehelp = getDrawable(R.drawable.ic_help);
drawablehelp.setTintList(
Utils.getColorAttr(getContext(), android.R.attr.colorControlNormal));
((ImageView) imageButton).setImageDrawable(drawablehelp);
imageButton.setVisibility(View.VISIBLE);
imageButton.setOnClickListener(this);
imageButton.setContentDescription(
getContext().getText(R.string.help_label));
if (frictionImageView != null) {
frictionImageView.setVisibility(View.GONE);
}
} else {
imageButton.setVisibility(View.GONE);
if (frictionImageView != null) {
frictionImageView.setVisibility(View.VISIBLE);
bindFrictionImage(frictionImageView);
}
}
}
/**
* Updates the title and summary; may indirectly call notifyChanged().
*/
public void refresh() {
setTitle(mWifiEntry.getTitle());
if (mWifiEntry instanceof HotspotNetworkEntry) {
updateHotspotIcon(((HotspotNetworkEntry) mWifiEntry).getDeviceType());
} else {
mLevel = mWifiEntry.getLevel();
mShowX = mWifiEntry.shouldShowXLevelIcon();
updateIcon(mShowX, mLevel);
}
setSummary(mWifiEntry.getSummary(false /* concise */));
mContentDescription = buildContentDescription();
}
/**
* Indicates the state of the WifiEntry has changed and clients may retrieve updates through
* the WifiEntry getter methods.
*/
public void onUpdated() {
// TODO(b/70983952): Fill this method in
refresh();
}
/**
* Result of the connect request indicated by the WifiEntry.CONNECT_STATUS constants.
*/
public void onConnectResult(int status) {
// TODO(b/70983952): Fill this method in
}
/**
* Result of the disconnect request indicated by the WifiEntry.DISCONNECT_STATUS constants.
*/
public void onDisconnectResult(int status) {
// TODO(b/70983952): Fill this method in
}
/**
* Result of the forget request indicated by the WifiEntry.FORGET_STATUS constants.
*/
public void onForgetResult(int status) {
// TODO(b/70983952): Fill this method in
}
/**
* Result of the sign-in request indecated by the WifiEntry.SIGNIN_STATUS constants
*/
public void onSignInResult(int status) {
// TODO(b/70983952): Fill this method in
}
protected int getIconColorAttr() {
final boolean accent = (mWifiEntry.hasInternetAccess()
&& mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED);
return accent ? android.R.attr.colorAccent : android.R.attr.colorControlNormal;
}
private void setIconWithTint(Drawable drawable) {
if (drawable != null) {
// Must use Drawable#setTintList() instead of Drawable#setTint() to show the grey
// icon when the preference is disabled.
drawable.setTintList(Utils.getColorAttr(getContext(), getIconColorAttr()));
setIcon(drawable);
} else {
setIcon(null);
}
}
@VisibleForTesting
void updateIcon(boolean showX, int level) {
if (level == -1) {
setIcon(null);
return;
}
setIconWithTint(mIconInjector.getIcon(showX, level));
}
@VisibleForTesting
void updateHotspotIcon(int deviceType) {
setIconWithTint(getContext().getDrawable(getHotspotIconResource(deviceType)));
}
@Nullable
private StateListDrawable getFrictionStateListDrawable() {
TypedArray frictionSld;
try {
frictionSld = getContext().getTheme().obtainStyledAttributes(FRICTION_ATTRS);
} catch (Resources.NotFoundException e) {
// Fallback for platforms that do not need friction icon resources.
frictionSld = null;
}
return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
}
/**
* Binds the friction icon drawable using a StateListDrawable.
*
* <p>Friction icons will be rebound when notifyChange() is called, and therefore
* do not need to be managed in refresh()</p>.
*/
private void bindFrictionImage(ImageView frictionImageView) {
if (frictionImageView == null || mFrictionSld == null) {
return;
}
if ((mWifiEntry.getSecurity() != WifiEntry.SECURITY_NONE)
&& (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)) {
mFrictionSld.setState(STATE_SECURED);
}
frictionImageView.setImageDrawable(mFrictionSld.getCurrent());
}
/**
* Helper method to generate content description string.
*/
@VisibleForTesting
CharSequence buildContentDescription() {
final Context context = getContext();
CharSequence contentDescription = getTitle();
final CharSequence summary = getSummary();
if (!TextUtils.isEmpty(summary)) {
contentDescription = TextUtils.concat(contentDescription, ",", summary);
}
int level = mWifiEntry.getLevel();
if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) {
contentDescription = TextUtils.concat(contentDescription, ",",
context.getString(WIFI_CONNECTION_STRENGTH[level]));
}
return TextUtils.concat(contentDescription, ",",
mWifiEntry.getSecurity() == WifiEntry.SECURITY_NONE
? context.getString(R.string.accessibility_wifi_security_type_none)
: context.getString(R.string.accessibility_wifi_security_type_secured));
}
/**
* Set listeners, who want to listen the button client event.
*/
public void setOnButtonClickListener(OnButtonClickListener listener) {
mOnButtonClickListener = listener;
notifyChanged();
}
@Override
protected int getSecondTargetResId() {
return R.layout.access_point_friction_widget;
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.icon_button) {
if (mOnButtonClickListener != null) {
mOnButtonClickListener.onButtonClick(this);
}
}
}
/**
* Callback to inform the caller that the icon button is clicked.
*/
public interface OnButtonClickListener {
/**
* Register to listen the button click event.
*/
void onButtonClick(WifiEntryPreference preference);
}
private Drawable getDrawable(@DrawableRes int iconResId) {
Drawable buttonIcon = null;
try {
buttonIcon = getContext().getDrawable(iconResId);
} catch (Resources.NotFoundException exception) {
// Do nothing
}
return buttonIcon;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2009 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;
import android.app.settings.SettingsEnums;
import android.os.Bundle;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
/**
* Wifi information menu item on the diagnostic screen
*/
public class WifiInfo extends SettingsPreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.testing_wifi_settings);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.TESTING;
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2015 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;
import static android.net.ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
import static android.net.ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
import static android.net.ConnectivityManager.ACTION_PROMPT_UNVALIDATED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.wifi.WifiInfo;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import androidx.annotation.VisibleForTesting;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.settings.R;
/**
* To display a dialog that asks the user whether to connect to a network that is not validated.
*/
public class WifiNoInternetDialog extends AlertActivity implements
DialogInterface.OnClickListener {
private static final String TAG = "WifiNoInternetDialog";
private ConnectivityManager mCM;
private Network mNetwork;
private String mNetworkName;
private ConnectivityManager.NetworkCallback mNetworkCallback;
@VisibleForTesting CheckBox mAlwaysAllow;
private String mAction;
private boolean mButtonClicked;
private boolean isKnownAction(Intent intent) {
return ACTION_PROMPT_UNVALIDATED.equals(intent.getAction())
|| ACTION_PROMPT_LOST_VALIDATION.equals(intent.getAction())
|| ACTION_PROMPT_PARTIAL_CONNECTIVITY.equals(intent.getAction());
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
if (intent == null || !isKnownAction(intent)) {
Log.e(TAG, "Unexpected intent " + intent + ", exiting");
finish();
return;
}
mAction = intent.getAction();
mNetwork = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
if (mNetwork == null) {
Log.e(TAG, "Can't determine network from intent extra, exiting");
finish();
return;
}
// TODO: add a registerNetworkCallback(Network network, NetworkCallback networkCallback) and
// simplify this.
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
mNetworkCallback = new NetworkCallback() {
@Override
public void onLost(Network network) {
// Close the dialog if the network disconnects.
if (mNetwork.equals(network)) {
Log.d(TAG, "Network " + mNetwork + " disconnected");
finish();
}
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
// Close the dialog if the network validates.
if (mNetwork.equals(network) && nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
Log.d(TAG, "Network " + mNetwork + " validated");
finish();
}
}
};
mCM = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
mCM.registerNetworkCallback(request, mNetworkCallback);
final NetworkInfo ni = mCM.getNetworkInfo(mNetwork);
final NetworkCapabilities nc = mCM.getNetworkCapabilities(mNetwork);
if (ni == null || !ni.isConnectedOrConnecting() || nc == null) {
Log.d(TAG, "Network " + mNetwork + " is not connected: " + ni);
finish();
return;
}
mNetworkName = nc.getSsid();
if (mNetworkName != null) {
mNetworkName = WifiInfo.sanitizeSsid(mNetworkName);
}
createDialog();
}
private void createDialog() {
mAlert.setIcon(R.drawable.ic_settings_wireless);
final AlertController.AlertParams ap = mAlertParams;
if (ACTION_PROMPT_UNVALIDATED.equals(mAction)) {
ap.mTitle = mNetworkName;
ap.mMessage = getString(R.string.no_internet_access_text);
ap.mPositiveButtonText = getString(R.string.yes);
ap.mNegativeButtonText = getString(R.string.no);
} else if (ACTION_PROMPT_PARTIAL_CONNECTIVITY.equals(mAction)) {
ap.mTitle = mNetworkName;
ap.mMessage = getString(R.string.partial_connectivity_text);
ap.mPositiveButtonText = getString(R.string.yes);
ap.mNegativeButtonText = getString(R.string.no);
} else {
ap.mTitle = getString(R.string.lost_internet_access_title);
ap.mMessage = getString(R.string.lost_internet_access_text);
ap.mPositiveButtonText = getString(R.string.lost_internet_access_switch);
ap.mNegativeButtonText = getString(R.string.lost_internet_access_cancel);
}
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
final LayoutInflater inflater = LayoutInflater.from(ap.mContext);
final View checkbox = inflater.inflate(
com.android.internal.R.layout.always_use_checkbox, null);
ap.mView = checkbox;
mAlwaysAllow = (CheckBox) checkbox.findViewById(com.android.internal.R.id.alwaysUse);
if (ACTION_PROMPT_UNVALIDATED.equals(mAction)
|| ACTION_PROMPT_PARTIAL_CONNECTIVITY.equals(mAction)) {
mAlwaysAllow.setText(getString(R.string.no_internet_access_remember));
} else {
mAlwaysAllow.setText(getString(R.string.lost_internet_access_persist));
}
setupAlert();
}
@Override
protected void onDestroy() {
if (mNetworkCallback != null) {
mCM.unregisterNetworkCallback(mNetworkCallback);
mNetworkCallback = null;
}
// If the user exits the no Internet or partial connectivity dialog without taking any
// action, disconnect the network, because once the dialog has been dismissed there is no
// way to use the network.
//
// Unfortunately, AlertDialog does not seem to offer any good way to get an onCancel or
// onDismiss callback. So we implement this ourselves.
if (isFinishing() && !mButtonClicked) {
if (ACTION_PROMPT_PARTIAL_CONNECTIVITY.equals(mAction)) {
mCM.setAcceptPartialConnectivity(mNetwork, false /* accept */, false /* always */);
} else if (ACTION_PROMPT_UNVALIDATED.equals(mAction)) {
mCM.setAcceptUnvalidated(mNetwork, false /* accept */, false /* always */);
}
}
super.onDestroy();
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which != BUTTON_NEGATIVE && which != BUTTON_POSITIVE) return;
final boolean always = mAlwaysAllow.isChecked();
final String what, action;
mButtonClicked = true;
if (ACTION_PROMPT_UNVALIDATED.equals(mAction)) {
what = "NO_INTERNET";
final boolean accept = (which == BUTTON_POSITIVE);
action = (accept ? "Connect" : "Ignore");
mCM.setAcceptUnvalidated(mNetwork, accept, always);
} else if (ACTION_PROMPT_PARTIAL_CONNECTIVITY.equals(mAction)) {
what = "PARTIAL_CONNECTIVITY";
final boolean accept = (which == BUTTON_POSITIVE);
action = (accept ? "Connect" : "Ignore");
mCM.setAcceptPartialConnectivity(mNetwork, accept, always);
} else {
what = "LOST_INTERNET";
final boolean avoid = (which == BUTTON_POSITIVE);
action = (avoid ? "Switch away" : "Get stuck");
if (always) {
Settings.Global.putString(mAlertParams.mContext.getContentResolver(),
Settings.Global.NETWORK_AVOID_BAD_WIFI, avoid ? "1" : "0");
} else if (avoid) {
mCM.setAvoidUnvalidated(mNetwork);
}
}
Log.d(TAG, what + ": " + action + " network=" + mNetwork +
(always ? " and remember" : ""));
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2011 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;
import android.content.Intent;
import com.android.settings.ButtonBarHandler;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.network.NetworkProviderSettings;
import com.android.settings.wifi.p2p.WifiP2pSettings;
import com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsWifiSettings2;
public class WifiPickerActivity extends SettingsActivity implements ButtonBarHandler {
@Override
public Intent getIntent() {
Intent modIntent = new Intent(super.getIntent());
if (!modIntent.hasExtra(EXTRA_SHOW_FRAGMENT)) {
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, NetworkProviderSettings.class.getName());
modIntent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.wifi_select_network);
}
return modIntent;
}
@Override
protected boolean isValidFragment(String fragmentName) {
return NetworkProviderSettings.class.getName().equals(fragmentName)
|| WifiP2pSettings.class.getName().equals(fragmentName)
|| SavedAccessPointsWifiSettings2.class.getName().equals(fragmentName);
}
}

View File

@@ -0,0 +1,187 @@
/*
* 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;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import android.telephony.CarrierConfigManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.network.CarrierConfigCache;
import com.android.settings.overlay.FeatureFactory;
import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
import java.time.ZoneOffset;
public class WifiPickerTrackerHelper implements LifecycleObserver {
private static final String TAG = "WifiPickerTrackerHelper";
// Max age of tracked WifiEntries
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating WifiPickerTracker scans
private static final long SCAN_INTERVAL_MILLIS = 10_000;
// Clock used for evaluating the age of scans
private static final Clock ELAPSED_REALTIME_CLOCK = new SimpleClock(ZoneOffset.UTC) {
@Override
public long millis() {
return SystemClock.elapsedRealtime();
}
};
protected WifiPickerTracker mWifiPickerTracker;
// Worker thread used for WifiPickerTracker work
protected HandlerThread mWorkerThread;
protected final WifiManager mWifiManager;
protected final CarrierConfigCache mCarrierConfigCache;
public WifiPickerTrackerHelper(@NonNull Lifecycle lifecycle, @NonNull Context context,
@Nullable WifiPickerTracker.WifiPickerTrackerCallback listener) {
if (lifecycle == null) {
throw new IllegalArgumentException("lifecycle must be non-null.");
}
lifecycle.addObserver(this);
mWorkerThread = new HandlerThread(TAG
+ "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
Process.THREAD_PRIORITY_BACKGROUND);
mWorkerThread.start();
mWifiPickerTracker = FeatureFactory.getFeatureFactory()
.getWifiTrackerLibProvider()
.createWifiPickerTracker(lifecycle, context,
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
ELAPSED_REALTIME_CLOCK,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
listener);
mWifiManager = context.getSystemService(WifiManager.class);
mCarrierConfigCache = CarrierConfigCache.getInstance(context);
}
/** @OnLifecycleEvent(ON_DESTROY) */
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy() {
mWorkerThread.quit();
}
/** Return the WifiPickerTracker class */
public @NonNull WifiPickerTracker getWifiPickerTracker() {
return mWifiPickerTracker;
}
/** Return the enabled/disabled state of the carrier network provision */
public boolean isCarrierNetworkProvisionEnabled(int subId) {
final PersistableBundle config = mCarrierConfigCache.getConfigForSubId(subId);
if (config == null) {
Log.e(TAG, "Could not get carrier config, subId:" + subId);
return false;
}
final boolean enabled = config.getBoolean(
CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL);
Log.i(TAG, "isCarrierNetworkProvisionEnabled:" + enabled);
return enabled;
}
/** Return the enabled/disabled state of the carrier network */
public boolean isCarrierNetworkEnabled() {
final MergedCarrierEntry mergedCarrierEntry = mWifiPickerTracker.getMergedCarrierEntry();
if (mergedCarrierEntry == null) {
Log.e(TAG, "Failed to get MergedCarrierEntry to query enabled status");
return false;
}
final boolean isCarrierNetworkEnabled = mergedCarrierEntry.isEnabled();
Log.i(TAG, "isCarrierNetworkEnabled:" + isCarrierNetworkEnabled);
return isCarrierNetworkEnabled;
}
/** Enables/disables the carrier network */
public void setCarrierNetworkEnabled(boolean enabled) {
final MergedCarrierEntry mergedCarrierEntry = mWifiPickerTracker.getMergedCarrierEntry();
if (mergedCarrierEntry == null) {
Log.e(TAG, "Unable to get MergedCarrierEntry to set enabled status");
return;
}
Log.i(TAG, "setCarrierNetworkEnabled:" + enabled);
mergedCarrierEntry.setEnabled(enabled);
}
/** Connect to the carrier network */
public boolean connectCarrierNetwork(@Nullable WifiEntry.ConnectCallback callback) {
final MergedCarrierEntry mergedCarrierEntry = mWifiPickerTracker.getMergedCarrierEntry();
if (mergedCarrierEntry == null || !mergedCarrierEntry.canConnect()) {
return false;
}
mergedCarrierEntry.connect(callback);
return true;
}
/** Confirms connection of the carrier network connected with the internet access */
public boolean isCarrierNetworkActive() {
final MergedCarrierEntry mergedCarrierEntry = mWifiPickerTracker.getMergedCarrierEntry();
return (mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork());
}
/** Return the carrier network ssid */
public String getCarrierNetworkSsid() {
final MergedCarrierEntry mergedCarrierEntry = mWifiPickerTracker.getMergedCarrierEntry();
if (mergedCarrierEntry == null) {
return null;
}
return mergedCarrierEntry.getSsid();
}
/** Return the carrier network level */
public int getCarrierNetworkLevel() {
final MergedCarrierEntry mergedCarrierEntry = mWifiPickerTracker.getMergedCarrierEntry();
if (mergedCarrierEntry == null) return WifiEntry.WIFI_LEVEL_MIN;
int level = mergedCarrierEntry.getLevel();
// To avoid icons not found with WIFI_LEVEL_UNREACHABLE(-1), use WIFI_LEVEL_MIN(0) instead.
if (level < WifiEntry.WIFI_LEVEL_MIN) level = WifiEntry.WIFI_LEVEL_MIN;
return level;
}
@VisibleForTesting
void setWifiPickerTracker(@NonNull WifiPickerTracker wifiPickerTracker) {
mWifiPickerTracker = wifiPickerTracker;
}
@VisibleForTesting
void setWorkerThread(@NonNull HandlerThread workerThread) {
mWorkerThread = workerThread;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2017 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;
import android.content.Context;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.GenericSwitchController;
import com.android.settings.widget.SummaryUpdater;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
/**
* PreferenceController to update the wifi state.
*/
// TODO(b/167474581): Should clean up this controller when Provider Model finished.
public class WifiPrimarySwitchPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, SummaryUpdater.OnSummaryChangeListener,
LifecycleObserver, OnResume, OnPause, OnStart, OnStop {
//TODO(b/151133650): Replace AbstractPreferenceController with BasePreferenceController.
public static final String KEY_TOGGLE_WIFI = "main_toggle_wifi";
private PrimarySwitchPreference mWifiPreference;
private WifiEnabler mWifiEnabler;
private final WifiSummaryUpdater mSummaryHelper;
private final MetricsFeatureProvider mMetricsFeatureProvider;
public WifiPrimarySwitchPreferenceController(Context context,
MetricsFeatureProvider metricsFeatureProvider) {
super(context);
mMetricsFeatureProvider = metricsFeatureProvider;
mSummaryHelper = new WifiSummaryUpdater(mContext, this);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mWifiPreference = screen.findPreference(KEY_TOGGLE_WIFI);
}
@Override
public boolean isAvailable() {
return mContext.getResources().getBoolean(R.bool.config_show_wifi_settings);
}
@Override
public String getPreferenceKey() {
return KEY_TOGGLE_WIFI;
}
@Override
public void onResume() {
mSummaryHelper.register(true);
if (mWifiEnabler != null) {
mWifiEnabler.resume(mContext);
}
}
@Override
public void onPause() {
if (mWifiEnabler != null) {
mWifiEnabler.pause();
}
mSummaryHelper.register(false);
}
@Override
public void onStart() {
mWifiEnabler = new WifiEnabler(mContext, new GenericSwitchController(mWifiPreference),
mMetricsFeatureProvider);
}
@Override
public void onStop() {
if (mWifiEnabler != null) {
mWifiEnabler.teardownSwitchController();
}
}
@Override
public void onSummaryChanged(String summary) {
if (mWifiPreference != null) {
mWifiPreference.setSummary(summary);
}
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (C) 2013 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;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.wifi.WifiPermissionChecker;
/** This activity requests users permission to allow scanning even when Wi-Fi is turned off */
public class WifiScanModeActivity extends FragmentActivity {
private static final String TAG = "WifiScanModeActivity";
private DialogFragment mDialog;
@VisibleForTesting String mApp;
@VisibleForTesting WifiPermissionChecker mWifiPermissionChecker;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow()
.addSystemFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
Intent intent = getIntent();
if (savedInstanceState == null) {
if (intent != null
&& WifiManager.ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE.equals(
intent.getAction())) {
refreshAppLabel();
} else {
finish();
return;
}
} else {
mApp = savedInstanceState.getString("app");
}
createDialog();
}
@VisibleForTesting
void refreshAppLabel() {
if (mWifiPermissionChecker == null) {
mWifiPermissionChecker = new WifiPermissionChecker(this);
}
String packageName = mWifiPermissionChecker.getLaunchedPackage();
if (TextUtils.isEmpty(packageName)) {
mApp = null;
return;
}
mApp = Utils.getApplicationLabel(getApplicationContext(), packageName).toString();
}
@VisibleForTesting
void createDialog() {
if (isGuestUser(getApplicationContext())) {
Log.e(TAG, "Guest user is not allowed to configure Wi-Fi Scan Mode!");
EventLog.writeEvent(0x534e4554, "235601169", -1 /* UID */, "User is a guest");
finish();
return;
}
if (!isWifiScanModeConfigAllowed(getApplicationContext())) {
Log.e(TAG, "This user is not allowed to configure Wi-Fi Scan Mode!");
finish();
return;
}
if (mDialog == null) {
mDialog = AlertDialogFragment.newInstance(mApp);
mDialog.show(getSupportFragmentManager(), "dialog");
}
}
private void dismissDialog() {
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
}
private void doPositiveClick() {
getApplicationContext().getSystemService(WifiManager.class).setScanAlwaysAvailable(true);
setResult(RESULT_OK);
finish();
}
private void doNegativeClick() {
setResult(RESULT_CANCELED);
finish();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("app", mApp);
}
@Override
public void onPause() {
super.onPause();
dismissDialog();
}
public void onResume() {
super.onResume();
createDialog();
}
public static class AlertDialogFragment extends InstrumentedDialogFragment {
static AlertDialogFragment newInstance(String app) {
AlertDialogFragment frag = new AlertDialogFragment(app);
return frag;
}
private final String mApp;
public AlertDialogFragment(String app) {
super();
mApp = app;
}
public AlertDialogFragment() {
super();
mApp = null;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_WIFI_SCAN_MODE;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(
TextUtils.isEmpty(mApp)
? getString(R.string.wifi_scan_always_turn_on_message_unknown)
: getString(R.string.wifi_scan_always_turnon_message, mApp))
.setPositiveButton(
R.string.wifi_scan_always_confirm_allow,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((WifiScanModeActivity) getActivity()).doPositiveClick();
}
})
.setNegativeButton(
R.string.wifi_scan_always_confirm_deny,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
((WifiScanModeActivity) getActivity()).doNegativeClick();
}
})
.create();
}
@Override
public void onCancel(DialogInterface dialog) {
((WifiScanModeActivity) getActivity()).doNegativeClick();
}
}
private static boolean isGuestUser(Context context) {
final UserManager userManager = context.getSystemService(UserManager.class);
if (userManager == null) return false;
return userManager.isGuestUser();
}
private static boolean isWifiScanModeConfigAllowed(Context context) {
final UserManager userManager = context.getSystemService(UserManager.class);
if (userManager == null) return true;
return !userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_LOCATION);
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2018 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;
import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.HelpUtils;
public class WifiScanningRequiredFragment extends InstrumentedDialogFragment implements
DialogInterface.OnClickListener {
private static final String TAG = "WifiScanReqFrag";
public static WifiScanningRequiredFragment newInstance() {
WifiScanningRequiredFragment fragment = new WifiScanningRequiredFragment();
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
.setTitle(R.string.wifi_settings_scanning_required_title)
.setView(R.layout.wifi_settings_scanning_required_view)
.setPositiveButton(R.string.wifi_settings_scanning_required_turn_on, this)
.setNegativeButton(R.string.cancel, null);
addButtonIfNeeded(builder);
return builder.create();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.WIFI_SCANNING_NEEDED_DIALOG;
}
@Override
public void onClick(DialogInterface dialog, int which) {
Context context = getContext();
ContentResolver contentResolver = context.getContentResolver();
switch(which) {
case DialogInterface.BUTTON_POSITIVE:
context.getSystemService(WifiManager.class).setScanAlwaysAvailable(true);
Toast.makeText(
context,
context.getString(R.string.wifi_settings_scanning_required_enabled),
Toast.LENGTH_SHORT).show();
getTargetFragment().onActivityResult(
getTargetRequestCode(),
Activity.RESULT_OK,
null);
break;
case DialogInterface.BUTTON_NEUTRAL:
openHelpPage();
break;
case DialogInterface.BUTTON_NEGATIVE:
default:
// do nothing
}
}
void addButtonIfNeeded(AlertDialog.Builder builder) {
// Only show "learn more" if there is a help page to show
if (!TextUtils.isEmpty(getContext().getString(R.string.help_uri_wifi_scanning_required))) {
builder.setNeutralButton(R.string.learn_more, this);
}
}
private void openHelpPage() {
Intent intent = getHelpIntent(getContext());
if (intent != null) {
try {
getActivity().startActivityForResult(intent, 0);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Activity was not found for intent, " + intent.toString());
}
}
}
@VisibleForTesting
Intent getHelpIntent(Context context) {
return HelpUtils.getHelpIntent(
context,
context.getString(R.string.help_uri_wifi_scanning_required),
context.getClass().getName());
}
}

View File

@@ -0,0 +1,383 @@
/*
* Copyright (C) 2009 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;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settingslib.wifi.AccessPoint;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
/**
* Show the current status details of Wifi related fields
*/
public class WifiStatusTest extends Activity {
private static final String TAG = "WifiStatusTest";
private Button updateButton;
private TextView mWifiState;
private TextView mNetworkState;
private TextView mSupplicantState;
private TextView mRSSI;
private TextView mBSSID;
private TextView mSSID;
private TextView mHiddenSSID;
private TextView mIPAddr;
private TextView mMACAddr;
private TextView mNetworkId;
private TextView mTxLinkSpeed;
private TextView mRxLinkSpeed;
private TextView mScanList;
private TextView mPingHostname;
private TextView mHttpClientTest;
private Button pingTestButton;
private String mPingHostnameResult;
private String mHttpClientTestResult;
private WifiManager mWifiManager;
private IntentFilter mWifiStateFilter;
//============================
// Activity lifecycle
//============================
private final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN));
} else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
handleNetworkStateChanged(
(NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
} else if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
handleScanResultsAvailable();
} else if (intent.getAction().equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
/* TODO: handle supplicant connection change later */
} else if (intent.getAction().equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
handleSupplicantStateChanged(
(SupplicantState) intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE),
intent.hasExtra(WifiManager.EXTRA_SUPPLICANT_ERROR),
intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0));
} else if (intent.getAction().equals(WifiManager.RSSI_CHANGED_ACTION)) {
handleSignalChanged(intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, 0));
} else if (intent.getAction().equals(WifiManager.NETWORK_IDS_CHANGED_ACTION)) {
/* TODO: handle network id change info later */
} else {
Log.e(TAG, "Received an unknown Wifi Intent");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
mWifiStateFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
mWifiStateFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mWifiStateFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
mWifiStateFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mWifiStateFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
mWifiStateFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
registerReceiver(mWifiStateReceiver, mWifiStateFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
setContentView(R.layout.wifi_status_test);
updateButton = (Button) findViewById(R.id.update);
updateButton.setOnClickListener(updateButtonHandler);
mWifiState = (TextView) findViewById(R.id.wifi_state);
mNetworkState = (TextView) findViewById(R.id.network_state);
mSupplicantState = (TextView) findViewById(R.id.supplicant_state);
mRSSI = (TextView) findViewById(R.id.rssi);
mBSSID = (TextView) findViewById(R.id.bssid);
mSSID = (TextView) findViewById(R.id.ssid);
mHiddenSSID = (TextView) findViewById(R.id.hidden_ssid);
mIPAddr = (TextView) findViewById(R.id.ipaddr);
mMACAddr = (TextView) findViewById(R.id.macaddr);
mNetworkId = (TextView) findViewById(R.id.networkid);
mTxLinkSpeed = (TextView) findViewById(R.id.tx_link_speed);
mRxLinkSpeed = (TextView) findViewById(R.id.rx_link_speed);
mScanList = (TextView) findViewById(R.id.scan_list);
mPingHostname = (TextView) findViewById(R.id.pingHostname);
mHttpClientTest = (TextView) findViewById(R.id.httpClientTest);
pingTestButton = (Button) findViewById(R.id.ping_test);
pingTestButton.setOnClickListener(mPingButtonHandler);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(mWifiStateReceiver, mWifiStateFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mWifiStateReceiver);
}
OnClickListener mPingButtonHandler = new OnClickListener() {
public void onClick(View v) {
updatePingState();
}
};
OnClickListener updateButtonHandler = new OnClickListener() {
public void onClick(View v) {
final WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
setWifiStateText(mWifiManager.getWifiState());
mBSSID.setText(wifiInfo.getBSSID());
mHiddenSSID.setText(String.valueOf(wifiInfo.getHiddenSSID()));
int ipAddr = wifiInfo.getIpAddress();
StringBuffer ipBuf = new StringBuffer();
ipBuf.append(ipAddr & 0xff).append('.').
append((ipAddr >>>= 8) & 0xff).append('.').
append((ipAddr >>>= 8) & 0xff).append('.').
append((ipAddr >>>= 8) & 0xff);
mIPAddr.setText(ipBuf);
mTxLinkSpeed.setText(String.valueOf(wifiInfo.getTxLinkSpeedMbps())+" Mbps");
mRxLinkSpeed.setText(String.valueOf(wifiInfo.getRxLinkSpeedMbps())+" Mbps");
mMACAddr.setText(wifiInfo.getMacAddress());
mNetworkId.setText(String.valueOf(wifiInfo.getNetworkId()));
mRSSI.setText(String.valueOf(wifiInfo.getRssi()));
mSSID.setText(wifiInfo.getSSID());
SupplicantState supplicantState = wifiInfo.getSupplicantState();
setSupplicantStateText(supplicantState);
}
};
private void setSupplicantStateText(SupplicantState supplicantState) {
if(SupplicantState.FOUR_WAY_HANDSHAKE.equals(supplicantState)) {
mSupplicantState.setText("FOUR WAY HANDSHAKE");
} else if(SupplicantState.ASSOCIATED.equals(supplicantState)) {
mSupplicantState.setText("ASSOCIATED");
} else if(SupplicantState.ASSOCIATING.equals(supplicantState)) {
mSupplicantState.setText("ASSOCIATING");
} else if(SupplicantState.COMPLETED.equals(supplicantState)) {
mSupplicantState.setText("COMPLETED");
} else if(SupplicantState.DISCONNECTED.equals(supplicantState)) {
mSupplicantState.setText("DISCONNECTED");
} else if(SupplicantState.DORMANT.equals(supplicantState)) {
mSupplicantState.setText("DORMANT");
} else if(SupplicantState.GROUP_HANDSHAKE.equals(supplicantState)) {
mSupplicantState.setText("GROUP HANDSHAKE");
} else if(SupplicantState.INACTIVE.equals(supplicantState)) {
mSupplicantState.setText("INACTIVE");
} else if(SupplicantState.INVALID.equals(supplicantState)) {
mSupplicantState.setText("INVALID");
} else if(SupplicantState.SCANNING.equals(supplicantState)) {
mSupplicantState.setText("SCANNING");
} else if(SupplicantState.UNINITIALIZED.equals(supplicantState)) {
mSupplicantState.setText("UNINITIALIZED");
} else {
mSupplicantState.setText("BAD");
Log.e(TAG, "supplicant state is bad");
}
}
private void setWifiStateText(int wifiState) {
String wifiStateString;
switch(wifiState) {
case WifiManager.WIFI_STATE_DISABLING:
wifiStateString = getString(R.string.wifi_state_disabling);
break;
case WifiManager.WIFI_STATE_DISABLED:
wifiStateString = getString(R.string.wifi_state_disabled);
break;
case WifiManager.WIFI_STATE_ENABLING:
wifiStateString = getString(R.string.wifi_state_enabling);
break;
case WifiManager.WIFI_STATE_ENABLED:
wifiStateString = getString(R.string.wifi_state_enabled);
break;
case WifiManager.WIFI_STATE_UNKNOWN:
wifiStateString = getString(R.string.wifi_state_unknown);
break;
default:
wifiStateString = "BAD";
Log.e(TAG, "wifi state is bad");
break;
}
mWifiState.setText(wifiStateString);
}
private void handleSignalChanged(int rssi) {
mRSSI.setText(String.valueOf(rssi));
}
private void handleWifiStateChanged(int wifiState) {
setWifiStateText(wifiState);
}
private void handleScanResultsAvailable() {
List<ScanResult> list = mWifiManager.getScanResults();
StringBuffer scanList = new StringBuffer();
if (list != null) {
for (int i = list.size() - 1; i >= 0; i--) {
final ScanResult scanResult = list.get(i);
if (scanResult == null) {
continue;
}
if (TextUtils.isEmpty(scanResult.SSID)) {
continue;
}
scanList.append(scanResult.SSID+" ");
}
}
mScanList.setText(scanList);
}
private void handleSupplicantStateChanged(SupplicantState state, boolean hasError, int error) {
if (hasError) {
mSupplicantState.setText("ERROR AUTHENTICATING");
} else {
setSupplicantStateText(state);
}
}
private void handleNetworkStateChanged(NetworkInfo networkInfo) {
if (mWifiManager.isWifiEnabled()) {
WifiInfo info = mWifiManager.getConnectionInfo();
String summary = AccessPoint.getSummary(this, info.getSSID(),
networkInfo.getDetailedState(),
info.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID,
/* suggestionOrSpecifierPackageName */ null);
mNetworkState.setText(summary);
}
}
private final void updatePingState() {
final Handler handler = new Handler();
// Set all to unknown since the threads will take a few secs to update.
mPingHostnameResult = getResources().getString(R.string.radioInfo_unknown);
mHttpClientTestResult = getResources().getString(R.string.radioInfo_unknown);
mPingHostname.setText(mPingHostnameResult);
mHttpClientTest.setText(mHttpClientTestResult);
final Runnable updatePingResults = new Runnable() {
public void run() {
mPingHostname.setText(mPingHostnameResult);
mHttpClientTest.setText(mHttpClientTestResult);
}
};
Thread hostnameThread = new Thread() {
@Override
public void run() {
pingHostname();
handler.post(updatePingResults);
}
};
hostnameThread.start();
Thread httpClientThread = new Thread() {
@Override
public void run() {
httpClientTest();
handler.post(updatePingResults);
}
};
httpClientThread.start();
}
private final void pingHostname() {
try {
// TODO: Hardcoded for now, make it UI configurable
Process p = Runtime.getRuntime().exec("ping -c 1 -w 100 www.google.com");
int status = p.waitFor();
if (status == 0) {
mPingHostnameResult = "Pass";
} else {
mPingHostnameResult = "Fail: Host unreachable";
}
} catch (UnknownHostException e) {
mPingHostnameResult = "Fail: Unknown Host";
} catch (IOException e) {
mPingHostnameResult= "Fail: IOException";
} catch (InterruptedException e) {
mPingHostnameResult = "Fail: InterruptedException";
}
}
private void httpClientTest() {
HttpURLConnection urlConnection = null;
try {
// TODO: Hardcoded for now, make it UI configurable
URL url = new URL("https://www.google.com");
urlConnection = (HttpURLConnection) url.openConnection();
if (urlConnection.getResponseCode() == 200) {
mHttpClientTestResult = "Pass";
} else {
mHttpClientTestResult = "Fail: Code: " + urlConnection.getResponseMessage();
}
} catch (IOException e) {
mHttpClientTestResult = "Fail: IOException";
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2017 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;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.widget.SummaryUpdater;
import com.android.settingslib.wifi.WifiStatusTracker;
/**
* Helper class that listeners to wifi callback and notify client when there is update in
* wifi summary info.
*/
public class WifiSummaryUpdater extends SummaryUpdater {
private final WifiStatusTracker mWifiTracker;
private final BroadcastReceiver mReceiver;
private static final IntentFilter INTENT_FILTER;
static {
INTENT_FILTER = new IntentFilter();
INTENT_FILTER.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
INTENT_FILTER.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
INTENT_FILTER.addAction(WifiManager.RSSI_CHANGED_ACTION);
}
public WifiSummaryUpdater(Context context, OnSummaryChangeListener listener) {
this(context, listener, null);
}
@VisibleForTesting
public WifiSummaryUpdater(Context context, OnSummaryChangeListener listener,
WifiStatusTracker wifiTracker) {
super(context, listener);
mWifiTracker = wifiTracker != null ? wifiTracker :
new WifiStatusTracker(context, context.getSystemService(WifiManager.class),
context.getSystemService(NetworkScoreManager.class),
context.getSystemService(ConnectivityManager.class),
this::notifyChangeIfNeeded);
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mWifiTracker.handleBroadcast(intent);
notifyChangeIfNeeded();
}
};
}
@Override
public void register(boolean register) {
if (register) {
mWifiTracker.fetchInitialState();
notifyChangeIfNeeded();
mContext.registerReceiver(mReceiver, INTENT_FILTER,
Context.RECEIVER_EXPORTED_UNAUDITED);
} else {
mContext.unregisterReceiver(mReceiver);
}
mWifiTracker.setListening(register);
}
@Override
public String getSummary() {
if (!mWifiTracker.enabled) {
return mContext.getString(R.string.switch_off_text);
}
if (!mWifiTracker.connected) {
return mContext.getString(R.string.disconnected);
}
String ssid = WifiInfo.sanitizeSsid(mWifiTracker.ssid);
if (TextUtils.isEmpty(mWifiTracker.statusLabel)) {
return ssid;
}
return mContext.getResources().getString(
com.android.settingslib.R.string.preference_summary_default_combination,
ssid, mWifiTracker.statusLabel);
}
/**
* return true if Wi-Fi connected.
*/
public boolean isWifiConnected() {
return mWifiTracker.connected;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 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;
import android.content.Context;
import android.os.Handler;
import androidx.lifecycle.Lifecycle;
import com.android.wifitrackerlib.NetworkDetailsTracker;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
/**
* Provides the objects instances from the AOSP WifiTrackerLib.
*/
public interface WifiTrackerLibProvider {
/** Create a new instance of WifiPickerTracker */
WifiPickerTracker createWifiPickerTracker(
Lifecycle lifecycle, Context context,
Handler mainHandler, Handler workerHandler, Clock clock,
long maxScanAgeMillis, long scanIntervalMillis,
WifiPickerTracker.WifiPickerTrackerCallback listener);
/** Create a new instance of NetworkDetailsTracker */
NetworkDetailsTracker createNetworkDetailsTracker(
Lifecycle lifecycle, Context context,
Handler mainHandler, Handler workerHandler, Clock clock,
long maxScanAgeMillis, long scanIntervalMillis,
String key);
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 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;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
import androidx.lifecycle.Lifecycle;
import com.android.wifitrackerlib.NetworkDetailsTracker;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
/**
* Implementation of AOSP WifiTrackerLibProvider.
*/
public class WifiTrackerLibProviderImpl implements WifiTrackerLibProvider {
/**
* Create an instance of WifiPickerTracker.
*/
@Override
public WifiPickerTracker createWifiPickerTracker(
Lifecycle lifecycle, Context context,
Handler mainHandler, Handler workerHandler, Clock clock,
long maxScanAgeMillis, long scanIntervalMillis,
WifiPickerTracker.WifiPickerTrackerCallback listener) {
return new WifiPickerTracker(
lifecycle, context,
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
mainHandler, workerHandler, clock,
maxScanAgeMillis, scanIntervalMillis,
listener);
}
/**
* Create an instance of NetworkDetailsTracker.
*/
@Override
public NetworkDetailsTracker createNetworkDetailsTracker(
Lifecycle lifecycle, Context context,
Handler mainHandler, Handler workerHandler, Clock clock,
long maxScanAgeMillis, long scanIntervalMillis,
String key) {
return NetworkDetailsTracker.createNetworkDetailsTracker(
lifecycle, context,
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
mainHandler, workerHandler, clock,
maxScanAgeMillis, scanIntervalMillis,
key);
}
}

View File

@@ -0,0 +1,311 @@
/*
* Copyright (C) 2017 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;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.NetworkCapabilities;
import android.net.TetheringManager;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.wifitrackerlib.WifiEntry;
import java.nio.charset.StandardCharsets;
/** A utility class for Wi-Fi functions. */
public class WifiUtils extends com.android.settingslib.wifi.WifiUtils {
static final String TAG = "WifiUtils";
private static final int SSID_ASCII_MIN_LENGTH = 1;
private static final int SSID_ASCII_MAX_LENGTH = 32;
private static final int PSK_PASSPHRASE_ASCII_MIN_LENGTH = 8;
private static final int PSK_PASSPHRASE_ASCII_MAX_LENGTH = 63;
private static Boolean sCanShowWifiHotspotCached;
public static boolean isSSIDTooLong(String ssid) {
if (TextUtils.isEmpty(ssid)) {
return false;
}
return ssid.getBytes(StandardCharsets.UTF_8).length > SSID_ASCII_MAX_LENGTH;
}
public static boolean isSSIDTooShort(String ssid) {
if (TextUtils.isEmpty(ssid)) {
return true;
}
return ssid.length() < SSID_ASCII_MIN_LENGTH;
}
/**
* Check if the hotspot password is valid.
*/
public static boolean isHotspotPasswordValid(String password, int securityType) {
final SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
try {
if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
|| securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
if (password.length() < PSK_PASSPHRASE_ASCII_MIN_LENGTH
|| password.length() > PSK_PASSPHRASE_ASCII_MAX_LENGTH) {
return false;
}
}
configBuilder.setPassphrase(password, securityType);
} catch (Exception e) {
return false;
}
return true;
}
/**
* This method is a stripped and negated version of WifiConfigStore.canModifyNetwork.
*
* @param context Context of caller
* @param config The WiFi config.
* @return true if Settings cannot modify the config due to lockDown.
*/
public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) {
if (context == null || config == null) {
return false;
}
final DevicePolicyManager dpm =
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
final PackageManager pm = context.getPackageManager();
final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
// Check if device has DPM capability. If it has and dpm is still null, then we
// treat this case with suspicion and bail out.
if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
return true;
}
boolean isConfigEligibleForLockdown = false;
if (dpm != null) {
final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
if (deviceOwner != null) {
final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
try {
final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
deviceOwnerUserId);
isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
} catch (PackageManager.NameNotFoundException e) {
// don't care
}
} else if (dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
int profileOwnerUserId = Utils.getManagedProfileId(um, UserHandle.myUserId());
final ComponentName profileOwner = dpm.getProfileOwnerAsUser(profileOwnerUserId);
if (profileOwner != null) {
try {
final int profileOwnerUid = pm.getPackageUidAsUser(
profileOwner.getPackageName(), profileOwnerUserId);
isConfigEligibleForLockdown = profileOwnerUid == config.creatorUid;
} catch (PackageManager.NameNotFoundException e) {
// don't care
}
}
}
}
if (!isConfigEligibleForLockdown) {
return false;
}
final ContentResolver resolver = context.getContentResolver();
final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
return isLockdownFeatureEnabled;
}
/** Returns true if the provided NetworkCapabilities indicate a captive portal network. */
public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) {
return (capabilities != null
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL));
}
/**
* Provides a simple way to generate a new {@link WifiConfiguration} obj from
* {@link ScanResult} or {@link WifiEntry}. Either {@code wifiEntry} or {@code scanResult
* } input should be not null for retrieving information, otherwise will throw
* IllegalArgumentException.
* This method prefers to take {@link WifiEntry} input in priority. Therefore this method
* will take {@link WifiEntry} input as preferred data extraction source when you input
* both {@link WifiEntry} and {@link ScanResult}, and ignore {@link ScanResult} input.
*
* Duplicated and simplified method from {@link WifiConfigController#getConfig()}.
* TODO(b/120827021): Should be removed if the there is have a common one in shared place (e.g.
* SettingsLib).
*
* @param wifiEntry Input data for retrieving WifiConfiguration.
* @param scanResult Input data for retrieving WifiConfiguration.
* @return WifiConfiguration obj based on input.
*/
public static WifiConfiguration getWifiConfig(WifiEntry wifiEntry, ScanResult scanResult) {
if (wifiEntry == null && scanResult == null) {
throw new IllegalArgumentException(
"At least one of WifiEntry and ScanResult input is required.");
}
final WifiConfiguration config = new WifiConfiguration();
final int security;
if (wifiEntry == null) {
config.SSID = "\"" + scanResult.SSID + "\"";
security = getWifiEntrySecurity(scanResult);
} else {
if (wifiEntry.getWifiConfiguration() == null) {
config.SSID = "\"" + wifiEntry.getSsid() + "\"";
} else {
config.networkId = wifiEntry.getWifiConfiguration().networkId;
config.hiddenSSID = wifiEntry.getWifiConfiguration().hiddenSSID;
}
security = wifiEntry.getSecurity();
}
switch (security) {
case WifiEntry.SECURITY_NONE:
config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
break;
case WifiEntry.SECURITY_WEP:
config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
break;
case WifiEntry.SECURITY_PSK:
config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
break;
case WifiEntry.SECURITY_EAP_SUITE_B:
config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
break;
case WifiEntry.SECURITY_EAP:
config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
break;
case WifiEntry.SECURITY_SAE:
config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
break;
case WifiEntry.SECURITY_OWE:
config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
break;
default:
break;
}
return config;
}
/**
* Gets security value from ScanResult.
*
* @param result ScanResult
* @return Related security value based on {@link WifiEntry}.
*/
public static int getWifiEntrySecurity(ScanResult result) {
if (result.capabilities.contains("WEP")) {
return WifiEntry.SECURITY_WEP;
} else if (result.capabilities.contains("SAE")) {
return WifiEntry.SECURITY_SAE;
} else if (result.capabilities.contains("PSK")) {
return WifiEntry.SECURITY_PSK;
} else if (result.capabilities.contains("EAP_SUITE_B_192")) {
return WifiEntry.SECURITY_EAP_SUITE_B;
} else if (result.capabilities.contains("EAP")) {
return WifiEntry.SECURITY_EAP;
} else if (result.capabilities.contains("OWE")) {
return WifiEntry.SECURITY_OWE;
}
return WifiEntry.SECURITY_NONE;
}
/**
* Check if Wi-Fi hotspot settings can be displayed.
*
* @param context Context of caller
* @return true if Wi-Fi hotspot settings can be displayed
*/
public static boolean checkShowWifiHotspot(Context context) {
if (context == null) return false;
boolean showWifiHotspotSettings =
context.getResources().getBoolean(R.bool.config_show_wifi_hotspot_settings);
if (!showWifiHotspotSettings) {
Log.w(TAG, "config_show_wifi_hotspot_settings:false");
return false;
}
WifiManager wifiManager = context.getSystemService(WifiManager.class);
if (wifiManager == null) {
Log.e(TAG, "WifiManager is null");
return false;
}
TetheringManager tetheringManager = context.getSystemService(TetheringManager.class);
if (tetheringManager == null) {
Log.e(TAG, "TetheringManager is null");
return false;
}
String[] wifiRegexs = tetheringManager.getTetherableWifiRegexs();
if (wifiRegexs == null || wifiRegexs.length == 0) {
Log.w(TAG, "TetherableWifiRegexs is empty");
return false;
}
return true;
}
/**
* Return the cached result to see if Wi-Fi hotspot settings can be displayed.
*
* @param context Context of caller
* @return true if Wi-Fi hotspot settings can be displayed
*/
public static boolean canShowWifiHotspot(Context context) {
if (sCanShowWifiHotspotCached == null) {
sCanShowWifiHotspotCached = checkShowWifiHotspot(context);
}
return sCanShowWifiHotspotCached;
}
/**
* Sets the sCanShowWifiHotspotCached for testing purposes.
*
* @param cached Cached value for #canShowWifiHotspot()
*/
@VisibleForTesting
public static void setCanShowWifiHotspotCached(Boolean cached) {
sCanShowWifiHotspotCached = cached;
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2017 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;
import static com.android.settings.wifi.ConfigureWifiSettings.WIFI_WAKEUP_REQUEST_CODE;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.net.wifi.WifiManager;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.utils.AnnotationSpan;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
/**
* {@link TogglePreferenceController} that controls whether the Wi-Fi Wakeup feature should be
* enabled.
*/
public class WifiWakeupPreferenceController extends TogglePreferenceController implements
LifecycleObserver, OnPause, OnResume {
private static final String TAG = "WifiWakeupPrefController";
private static final String KEY_ENABLE_WIFI_WAKEUP = "enable_wifi_wakeup";
private Fragment mFragment;
@VisibleForTesting
TwoStatePreference mPreference;
@VisibleForTesting
LocationManager mLocationManager;
@VisibleForTesting
WifiManager mWifiManager;
private final BroadcastReceiver mLocationReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateState(mPreference);
}
};
private final IntentFilter mLocationFilter =
new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
public WifiWakeupPreferenceController(Context context) {
super(context, KEY_ENABLE_WIFI_WAKEUP);
mLocationManager = (LocationManager) context.getSystemService(Service.LOCATION_SERVICE);
mWifiManager = context.getSystemService(WifiManager.class);
}
public void setFragment(Fragment hostFragment) {
mFragment = hostFragment;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public int getAvailabilityStatus() {
// Since mFragment is set only when entering Network preferences settings. So when
// mFragment == null, we can assume that the object is created by Search settings.
// When Search settings is called, if the dependent condition is not enabled, then
// return DISABLED_DEPENDENT_SETTING to hide the toggle.
if (mFragment == null && (!getLocationEnabled() || !getWifiScanningEnabled())) {
return DISABLED_DEPENDENT_SETTING;
}
return AVAILABLE;
}
@Override
public boolean isChecked() {
return getWifiWakeupEnabled()
&& getWifiScanningEnabled()
&& getLocationEnabled();
}
@Override
public boolean setChecked(boolean isChecked) {
if (isChecked) {
if (!getLocationEnabled()) {
if (mFragment == null) {
throw new IllegalStateException("No fragment to start activity");
}
final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
mFragment.startActivityForResult(intent, WIFI_WAKEUP_REQUEST_CODE);
return false;
} else if (!getWifiScanningEnabled()) {
showScanningDialog();
return false;
}
}
setWifiWakeupEnabled(isChecked);
return true;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
refreshSummary(preference);
}
@Override
public CharSequence getSummary() {
if (!getLocationEnabled()) {
return getNoLocationSummary();
} else {
return mContext.getText(R.string.wifi_wakeup_summary);
}
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_network;
}
@VisibleForTesting
CharSequence getNoLocationSummary() {
AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo("link", null);
CharSequence locationText = mContext.getText(R.string.wifi_wakeup_summary_no_location);
return AnnotationSpan.linkify(locationText, linkInfo);
}
public void onActivityResult(int requestCode, int resultCode) {
if (requestCode != WIFI_WAKEUP_REQUEST_CODE) {
return;
}
if (getLocationEnabled() && getWifiScanningEnabled()) {
setWifiWakeupEnabled(true);
updateState(mPreference);
}
}
private boolean getLocationEnabled() {
return mLocationManager.isLocationEnabled();
}
private boolean getWifiScanningEnabled() {
return mWifiManager.isScanAlwaysAvailable();
}
private void showScanningDialog() {
final WifiScanningRequiredFragment dialogFragment =
WifiScanningRequiredFragment.newInstance();
dialogFragment.setTargetFragment(mFragment, WIFI_WAKEUP_REQUEST_CODE /* requestCode */);
dialogFragment.show(mFragment.getFragmentManager(), TAG);
}
private boolean getWifiWakeupEnabled() {
return mWifiManager.isAutoWakeupEnabled();
}
private void setWifiWakeupEnabled(boolean enabled) {
mWifiManager.setAutoWakeupEnabled(enabled);
}
@Override
public void onResume() {
mContext.registerReceiver(mLocationReceiver, mLocationFilter);
}
@Override
public void onPause() {
mContext.unregisterReceiver(mLocationReceiver);
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2019 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.addappnetworks;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.view.Gravity;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
/**
* When apps send a new intent with a WifiConfiguration list extra to Settings APP. Settings APP
* will launch this activity, which contains {@code AddAppNetworksFragment}, with a UI panel pop
* up asking the user if they approve the network being proposed by the app to be saved on to
* the device. User can decide to save or cancel the request.,
*/
public class AddAppNetworksActivity extends FragmentActivity {
public static final String TAG = "AddAppNetworksActivity";
private AddAppNetworksFragment mFragment;
/** Key specifying the package name of the apps which requested the Panel. */
public static final String KEY_CALLING_PACKAGE_NAME = "panel_calling_package_name";
@VisibleForTesting
final Bundle mBundle = new Bundle();
@VisibleForTesting
IActivityManager mActivityManager = ActivityManager.getService();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_panel);
if (!showAddNetworksFragment()) {
finish();
return;
}
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
// Move the window to the bottom of screen, and make it take up the entire screen width.
final Window window = getWindow();
window.setGravity(Gravity.BOTTOM);
window.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
if (!showAddNetworksFragment()) {
finish();
return;
}
}
@VisibleForTesting
boolean showAddNetworksFragment() {
if (isGuestUser(getApplicationContext())) {
Log.e(TAG, "Guest user is not allowed to configure Wi-Fi!");
EventLog.writeEvent(0x534e4554, "224772678", -1 /* UID */, "User is a guest");
return false;
}
if (!isAddWifiConfigAllow()) {
Log.d(TAG, "Not allowed by Enterprise Restriction");
return false;
}
String packageName = getCallingAppPackageName();
if (TextUtils.isEmpty(packageName)) {
Log.d(TAG, "Package name is null");
return false;
}
// TODO: Check the new intent status.
mBundle.putString(KEY_CALLING_PACKAGE_NAME, packageName);
mBundle.putParcelableArrayList(Settings.EXTRA_WIFI_NETWORK_LIST,
getIntent().getParcelableArrayListExtra(Settings.EXTRA_WIFI_NETWORK_LIST));
final FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new AddAppNetworksFragment();
fragment.setArguments(mBundle);
fragmentManager.beginTransaction().add(R.id.main_content, fragment, TAG).commit();
} else {
((AddAppNetworksFragment) fragment).createContent(mBundle);
}
return true;
}
@VisibleForTesting
protected String getCallingAppPackageName() {
String packageName;
try {
packageName = mActivityManager.getLaunchedFromPackage(getActivityToken());
} catch (RemoteException e) {
Log.e(TAG, "Can not get the package from activity manager");
return null;
}
return packageName;
}
@VisibleForTesting
boolean isAddWifiConfigAllow() {
return WifiEnterpriseRestrictionUtils.isAddWifiConfigAllowed(this);
}
private static boolean isGuestUser(Context context) {
if (context == null) return false;
final UserManager userManager = context.getSystemService(UserManager.class);
if (userManager == null) return false;
return userManager.isGuestUser();
}
}

View File

@@ -0,0 +1,838 @@
/*
* Copyright (C) 2019 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.addappnetworks;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.internal.PreferenceImageView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.wifi.WifiUtils;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* The Fragment list those networks, which is proposed by other app, to user, and handle user's
* choose on either saving those networks or rejecting the request.
*/
public class AddAppNetworksFragment extends InstrumentedFragment implements
WifiPickerTracker.WifiPickerTrackerCallback {
public static final String TAG = "AddAppNetworksFragment";
// Possible result values in each item of the returned result list, which is used
// to inform the caller APP the processed result of each specified network.
@VisibleForTesting
static final int RESULT_NETWORK_SUCCESS = 0;
private static final int RESULT_NETWORK_ADD_ERROR = 1;
@VisibleForTesting
static final int RESULT_NETWORK_ALREADY_EXISTS = 2;
// Handler messages for controlling different state and delay showing the status message.
@VisibleForTesting static final int MESSAGE_START_SAVING_NETWORK = 1;
@VisibleForTesting static final int MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK = 2;
@VisibleForTesting static final int MESSAGE_SHOW_SAVE_FAILED = 3;
private static final int MESSAGE_FINISH = 4;
// Signal level for the initial signal icon.
@VisibleForTesting
static final int INITIAL_RSSI_SIGNAL_LEVEL = 0;
// Max networks count within one request.
private static final int MAX_SPECIFIC_NETWORKS_COUNT = 5;
// Duration for showing different status message.
private static final long SHOW_SAVING_INTERVAL_MILLIS = 500L;
private static final long SHOW_SAVED_INTERVAL_MILLIS = 1000L;
// Max age of tracked WifiEntries.
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating WifiPickerTracker scans.
private static final long SCAN_INTERVAL_MILLIS = 10_000;
@VisibleForTesting
FragmentActivity mActivity;
@VisibleForTesting
View mLayoutView;
@VisibleForTesting
Button mCancelButton;
@VisibleForTesting
Button mSaveButton;
@VisibleForTesting
String mCallingPackageName;
@VisibleForTesting
List<WifiNetworkSuggestion> mAllSpecifiedNetworksList;
@VisibleForTesting
List<UiConfigurationItem> mUiToRequestedList;
@VisibleForTesting
List<Integer> mResultCodeArrayList;
@VisibleForTesting
WifiPickerTracker mWifiPickerTracker;
// Worker thread used for WifiPickerTracker work
@VisibleForTesting
HandlerThread mWorkerThread;
private boolean mIsSingleNetwork;
private boolean mAnyNetworkSavedSuccess;
private TextView mSummaryView;
private TextView mSingleNetworkProcessingStatusView;
private int mSavingIndex;
private UiConfigurationItemAdapter mUiConfigurationItemAdapter;
private WifiManager.ActionListener mSaveListener;
private WifiManager mWifiManager;
@VisibleForTesting
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
showSaveStatusByState(msg.what);
switch (msg.what) {
case MESSAGE_START_SAVING_NETWORK:
mSaveButton.setEnabled(false);
// Save the proposed networks, start from first one.
mSavingIndex = 0;
saveNetwork(mSavingIndex);
break;
case MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK:
// For the single network case, we need to call connection after saved.
if (mIsSingleNetwork) {
connectNetwork(0);
}
sendEmptyMessageDelayed(MESSAGE_FINISH,
SHOW_SAVED_INTERVAL_MILLIS);
break;
case MESSAGE_SHOW_SAVE_FAILED:
mSaveButton.setEnabled(true);
break;
case MESSAGE_FINISH:
finishWithResult(RESULT_OK, mResultCodeArrayList);
break;
default:
// Do nothing.
break;
}
}
};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
mActivity = getActivity();
mWifiManager = mActivity.getSystemService(WifiManager.class);
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();
}
};
mWifiPickerTracker = FeatureFactory.getFeatureFactory()
.getWifiTrackerLibProvider()
.createWifiPickerTracker(getSettingsLifecycle(), mActivity,
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
this);
return inflater.inflate(R.layout.wifi_add_app_networks, container, false);
}
@Override
public void onDestroy() {
mWorkerThread.quit();
super.onDestroy();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Initial UI variables.
mLayoutView = view;
mCancelButton = view.findViewById(R.id.cancel);
mSaveButton = view.findViewById(R.id.save);
mSummaryView = view.findViewById(R.id.app_summary);
mSingleNetworkProcessingStatusView = view.findViewById(R.id.single_status);
// Assigns button listeners and network save listener.
mCancelButton.setOnClickListener(getCancelClickListener());
mSaveButton.setOnClickListener(getSaveClickListener());
prepareSaveResultListener();
// Prepare the non-UI variables.
final Bundle bundle = getArguments();
createContent(bundle);
}
/**
* Updates the UI contents to be aligned with the parameters in Bundle. This API may be called
* by the Activity directly when get a new intent.
*/
@VisibleForTesting
void createContent(Bundle bundle) {
// For new intent case, if device is saving those networks specified in old intent, just
// ignore this new intent for preventing status error.
if (mSaveButton != null && !mSaveButton.isEnabled()) {
Log.d(TAG, "Network saving, ignore new intent");
return;
}
mAllSpecifiedNetworksList =
bundle.getParcelableArrayList(Settings.EXTRA_WIFI_NETWORK_LIST);
// If there is no network in the request intent or the requested networks exceed the
// maximum limit, then just finish activity.
if (mAllSpecifiedNetworksList == null || mAllSpecifiedNetworksList.isEmpty()
|| mAllSpecifiedNetworksList.size() > MAX_SPECIFIC_NETWORKS_COUNT) {
finishWithResult(RESULT_CANCELED, null /* resultArrayList */);
return;
}
// Initial the result arry.
initializeResultCodeArray();
// Filter the saved networks, and prepare a not saved networks list for UI to present.
filterSavedNetworks(mWifiManager.getPrivilegedConfiguredNetworks());
// If all the specific networks are all exist, we just need to finish with result.
if (mUiToRequestedList.size() == 0) {
finishWithResult(RESULT_OK, mResultCodeArrayList);
return;
}
if (mAllSpecifiedNetworksList.size() == 1) {
mIsSingleNetwork = true;
// Set the multiple networks related layout to be gone, and the single network layout
// items to be visible.
mLayoutView.findViewById(R.id.multiple_networks).setVisibility(View.GONE);
mLayoutView.findViewById(R.id.single_network).setVisibility(View.VISIBLE);
// Show signal icon for single network case.
updateSingleNetworkSignalIcon(INITIAL_RSSI_SIGNAL_LEVEL);
// Show the SSID of the proposed network.
((TextView) mLayoutView.findViewById(R.id.single_ssid)).setText(
mUiToRequestedList.get(0).mDisplayedSsid);
// Set the status view as gone when UI is initialized.
mSingleNetworkProcessingStatusView.setVisibility(View.GONE);
} else {
// Multiple networks request case.
mIsSingleNetwork = false;
// Set the single network related layout to be gone, and the multiple networks layout
// items to be visible.
mLayoutView.findViewById(R.id.single_network).setVisibility(View.GONE);
mLayoutView.findViewById(R.id.multiple_networks).setVisibility(View.VISIBLE);
if (mUiConfigurationItemAdapter == null) {
// Prepare a UI adapter and set to UI listview.
final ListView uiNetworkListView = mLayoutView.findViewById(R.id.config_list);
mUiConfigurationItemAdapter = new UiConfigurationItemAdapter(mActivity,
com.android.settingslib.R.layout.preference_access_point,
mUiToRequestedList);
uiNetworkListView.setAdapter(mUiConfigurationItemAdapter);
} else {
mUiConfigurationItemAdapter.notifyDataSetChanged();
}
}
// Assigns caller app icon, title, and summary.
mCallingPackageName =
bundle.getString(AddAppNetworksActivity.KEY_CALLING_PACKAGE_NAME);
assignAppIcon(mActivity, mCallingPackageName);
assignTitleAndSummary(mActivity, mCallingPackageName);
}
private void initializeResultCodeArray() {
final int networksSize = mAllSpecifiedNetworksList.size();
mResultCodeArrayList = new ArrayList<>();
for (int i = 0; i < networksSize; i++) {
mResultCodeArrayList.add(RESULT_NETWORK_SUCCESS);
}
}
private String getWepKey(WifiConfiguration config) {
return (config.wepTxKeyIndex >= 0 && config.wepTxKeyIndex < config.wepKeys.length)
? config.wepKeys[config.wepTxKeyIndex] : null;
}
private boolean isSavedPasspointConfiguration(
PasspointConfiguration specifiecPassPointConfiguration) {
return mWifiManager.getPasspointConfigurations().stream()
.filter(config -> config.equals(specifiecPassPointConfiguration))
.findFirst()
.isPresent();
}
private boolean isSavedWifiConfiguration(WifiConfiguration specifiedConfig,
List<WifiConfiguration> savedWifiConfigurations) {
final String ssidWithQuotation = addQuotationIfNeeded(specifiedConfig.SSID);
final int authType = specifiedConfig.getAuthType();
// TODO: reformat to use lambda
for (WifiConfiguration privilegedWifiConfiguration : savedWifiConfigurations) {
// If SSID or security type is different, should be new network or need to be
// updated network, continue to check others.
if (!ssidWithQuotation.equals(privilegedWifiConfiguration.SSID)
|| authType != privilegedWifiConfiguration.getAuthType()) {
continue;
}
// If specified network and saved network have same security types, we'll check
// more information according to their security type to judge if they are same.
switch (authType) {
case KeyMgmt.NONE:
final String wep = getWepKey(specifiedConfig);
final String savedWep = getWepKey(privilegedWifiConfiguration);
return TextUtils.equals(wep, savedWep);
case KeyMgmt.OWE:
return true;
case KeyMgmt.WPA_PSK:
case KeyMgmt.WPA2_PSK:
case KeyMgmt.SAE:
if (specifiedConfig.preSharedKey.equals(
privilegedWifiConfiguration.preSharedKey)) {
return true;
}
break;
// TODO: Check how to judge enterprise type.
default:
break;
}
}
return false;
}
/**
* For the APP specified networks, filter saved ones and mark those saved as existed. And
* prepare a new UiConfigurationItem list, which contains those new or need to be updated
* networks, for creating UI to user.
*/
@VisibleForTesting
void filterSavedNetworks(List<WifiConfiguration> savedWifiConfigurations) {
if (mUiToRequestedList == null) {
mUiToRequestedList = new ArrayList<>();
} else {
mUiToRequestedList.clear();
}
int networkPositionInBundle = 0;
for (WifiNetworkSuggestion suggestion : mAllSpecifiedNetworksList) {
String displayedName = null;
boolean foundInSavedList = false;
/*
* If specified is passpoint network, need to check with the existing passpoint
* networks.
*/
final PasspointConfiguration passpointConfig = suggestion.getPasspointConfig();
if (passpointConfig != null) {
foundInSavedList = isSavedPasspointConfiguration(passpointConfig);
displayedName = passpointConfig.getHomeSp().getFriendlyName();
} else {
final WifiConfiguration specifiedConfig = suggestion.getWifiConfiguration();
displayedName = removeDoubleQuotes(specifiedConfig.SSID);
foundInSavedList = isSavedWifiConfiguration(specifiedConfig,
savedWifiConfigurations);
}
if (foundInSavedList) {
// If this requested network already in the saved networks, mark this item in the
// result code list as existed.
mResultCodeArrayList.set(networkPositionInBundle, RESULT_NETWORK_ALREADY_EXISTS);
} else {
// Prepare to add to UI list to show to user
UiConfigurationItem uiConfigurationItem = new UiConfigurationItem(displayedName,
suggestion, networkPositionInBundle, INITIAL_RSSI_SIGNAL_LEVEL);
mUiToRequestedList.add(uiConfigurationItem);
}
networkPositionInBundle++;
}
}
private void updateSingleNetworkSignalIcon(int level) {
if (level == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
return;
}
// TODO: Check level of the network to show signal icon.
final Drawable wifiIcon = mActivity.getDrawable(getWifiIconResource(level)).mutate();
final Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate();
wifiIconDark.setTintList(
Utils.getColorAttr(mActivity, android.R.attr.colorControlNormal));
((ImageView) mLayoutView.findViewById(R.id.signal_strength)).setImageDrawable(wifiIconDark);
}
private String addQuotationIfNeeded(String input) {
if (TextUtils.isEmpty(input)) {
return "";
}
if (input.length() >= 2 && input.startsWith("\"") && input.endsWith("\"")) {
return input;
}
StringBuilder sb = new StringBuilder();
sb.append("\"").append(input).append("\"");
return sb.toString();
}
static String removeDoubleQuotes(String string) {
if (TextUtils.isEmpty(string)) {
return "";
}
int length = string.length();
if ((length > 1) && (string.charAt(0) == '"')
&& (string.charAt(length - 1) == '"')) {
return string.substring(1, length - 1);
}
return string;
}
private void assignAppIcon(Context context, String callingPackageName) {
final Drawable drawable = loadPackageIconDrawable(context, callingPackageName);
((ImageView) mLayoutView.findViewById(R.id.app_icon)).setImageDrawable(drawable);
}
private Drawable loadPackageIconDrawable(Context context, String callingPackageName) {
Drawable icon = null;
try {
icon = context.getPackageManager().getApplicationIcon(callingPackageName);
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "Cannot get application icon", e);
}
return icon;
}
private void assignTitleAndSummary(Context context, String callingPackageName) {
// Assigns caller app name to title
((TextView) mLayoutView.findViewById(R.id.app_title)).setText(getTitle());
// Set summary
mSummaryView.setText(getAddNetworkRequesterSummary(
Utils.getApplicationLabel(context, callingPackageName)));
}
private CharSequence getAddNetworkRequesterSummary(CharSequence appName) {
return getString(mIsSingleNetwork ? R.string.wifi_add_app_single_network_summary
: R.string.wifi_add_app_networks_summary, appName);
}
private CharSequence getTitle() {
return getString(mIsSingleNetwork ? R.string.wifi_add_app_single_network_title
: R.string.wifi_add_app_networks_title);
}
View.OnClickListener getCancelClickListener() {
return (v) -> {
Log.d(TAG, "User rejected to add network");
finishWithResult(RESULT_CANCELED, null /* resultArrayList */);
};
}
View.OnClickListener getSaveClickListener() {
return (v) -> {
Log.d(TAG, "User agree to add networks");
// Start to process saving networks.
final Message message = mHandler.obtainMessage(MESSAGE_START_SAVING_NETWORK);
message.sendToTarget();
};
}
/**
* This class used to show network items to user, each item contains one specific (@Code
* WifiConfiguration} and one index to mapping this UI item to the item in the APP request
* network list.
*/
@VisibleForTesting
static class UiConfigurationItem {
public final String mDisplayedSsid;
public final WifiNetworkSuggestion mWifiNetworkSuggestion;
public final int mIndex;
public int mLevel;
UiConfigurationItem(String displayedSsid, WifiNetworkSuggestion wifiNetworkSuggestion,
int index, int level) {
if (displayedSsid.contains("\n") || displayedSsid.contains("\r")) {
mDisplayedSsid = displayedSsid.replaceAll("\\r|\\n", "");
Log.e(TAG, "Ignore CRLF strings in display SSIDs to avoid display errors!");
EventLog.writeEvent(0x534e4554, "224545390", -1 /* UID */, "CRLF injection");
} else {
mDisplayedSsid = displayedSsid;
}
mWifiNetworkSuggestion = wifiNetworkSuggestion;
mIndex = index;
mLevel = level;
}
}
private class UiConfigurationItemAdapter extends ArrayAdapter<UiConfigurationItem> {
private final int mResourceId;
private final LayoutInflater mInflater;
UiConfigurationItemAdapter(Context context, int resourceId,
List<UiConfigurationItem> objects) {
super(context, resourceId, objects);
mResourceId = resourceId;
mInflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
view = mInflater.inflate(mResourceId, parent, false /* attachToRoot */);
}
final View divider = view.findViewById(
com.android.settingslib.widget.preference.twotarget.R.id.two_target_divider);
if (divider != null) {
divider.setVisibility(View.GONE);
}
final UiConfigurationItem uiConfigurationItem = getItem(position);
final TextView titleView = view.findViewById(android.R.id.title);
if (titleView != null) {
// Shows whole SSID for better UX.
titleView.setSingleLine(false);
titleView.setText(uiConfigurationItem.mDisplayedSsid);
}
final PreferenceImageView imageView = view.findViewById(android.R.id.icon);
if (imageView != null) {
final Drawable drawable = getContext().getDrawable(
getWifiIconResource(uiConfigurationItem.mLevel));
drawable.setTintList(
com.android.settingslib.Utils.getColorAttr(getContext(),
android.R.attr.colorControlNormal));
imageView.setImageDrawable(drawable);
}
final TextView summaryView = view.findViewById(android.R.id.summary);
if (summaryView != null) {
summaryView.setVisibility(View.GONE);
}
return view;
}
}
private void prepareSaveResultListener() {
mSaveListener = new WifiManager.ActionListener() {
@Override
public void onSuccess() {
mAnyNetworkSavedSuccess = true;
if (saveNextNetwork()) {
return;
}
// Show saved or failed according to all results
showSavedOrFail();
}
@Override
public void onFailure(int reason) {
// Set result code of this network to be failed in the return list.
mResultCodeArrayList.set(mUiToRequestedList.get(mSavingIndex).mIndex,
RESULT_NETWORK_ADD_ERROR);
if (saveNextNetwork()) {
return;
}
// Show saved or failed according to all results
showSavedOrFail();
}
};
}
/**
* For multiple networks case, we need to check if there is other network need to save.
*/
private boolean saveNextNetwork() {
// Save the next network if have.
if (!mIsSingleNetwork && mSavingIndex < (mUiToRequestedList.size() - 1)) {
mSavingIndex++;
saveNetwork(mSavingIndex);
return true;
}
return false;
}
/**
* If any one of the specified networks is success, then we show saved and return all results
* list back to caller APP, otherwise we show failed to indicate all networks saved failed.
*/
private void showSavedOrFail() {
Message nextStateMessage;
if (mAnyNetworkSavedSuccess) {
// Enter next state after all networks are saved.
nextStateMessage = mHandler.obtainMessage(
MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK);
} else {
nextStateMessage = mHandler.obtainMessage(MESSAGE_SHOW_SAVE_FAILED);
}
// Delay to change to next state for showing saving mesage for a period.
mHandler.sendMessageDelayed(nextStateMessage, SHOW_SAVING_INTERVAL_MILLIS);
}
/**
* Call framework API to save single network.
*/
@VisibleForTesting
void saveNetwork(int index) {
final PasspointConfiguration passpointConfig =
mUiToRequestedList.get(index).mWifiNetworkSuggestion.getPasspointConfig();
if (passpointConfig != null) {
// Save passpoint, if no IllegalArgumentException, then treat it as success.
try {
mWifiManager.addOrUpdatePasspointConfiguration(passpointConfig);
mAnyNetworkSavedSuccess = true;
// (force) enable MAC randomization on new credentials
mWifiManager.setMacRandomizationSettingPasspointEnabled(
passpointConfig.getHomeSp().getFqdn(), true);
} catch (IllegalArgumentException e) {
mResultCodeArrayList.set(mUiToRequestedList.get(index).mIndex,
RESULT_NETWORK_ADD_ERROR);
}
if (saveNextNetwork()) {
return;
}
// Show saved or failed according to all results.
showSavedOrFail();
} else {
final WifiConfiguration wifiConfiguration =
mUiToRequestedList.get(index).mWifiNetworkSuggestion.getWifiConfiguration();
wifiConfiguration.SSID = addQuotationIfNeeded(wifiConfiguration.SSID);
// (force) enable MAC randomization on new credentials
wifiConfiguration.setMacRandomizationSetting(
WifiConfiguration.RANDOMIZATION_PERSISTENT);
mWifiManager.save(wifiConfiguration, mSaveListener);
}
}
private void connectNetwork(int index) {
final WifiConfiguration wifiConfiguration =
mUiToRequestedList.get(index).mWifiNetworkSuggestion.getWifiConfiguration();
mWifiManager.connect(wifiConfiguration, null /* ActionListener */);
}
private void finishWithResult(int resultCode, List<Integer> resultArrayList) {
if (mActivity == null) {
return;
}
if (resultArrayList != null) {
Intent intent = new Intent();
intent.putIntegerArrayListExtra(Settings.EXTRA_WIFI_NETWORK_RESULT_LIST,
(ArrayList<Integer>) resultArrayList);
mActivity.setResult(resultCode, intent);
}
mActivity.finish();
}
@Override
public int getMetricsCategory() {
return SettingsEnums.PANEL_ADD_WIFI_NETWORKS;
}
@VisibleForTesting
void showSaveStatusByState(int status) {
switch (status) {
case MESSAGE_START_SAVING_NETWORK:
if (mIsSingleNetwork) {
// Set the initial text color for status message.
mSingleNetworkProcessingStatusView.setTextColor(
com.android.settingslib.Utils.getColorAttr(mActivity,
android.R.attr.textColorSecondary));
mSingleNetworkProcessingStatusView.setText(
getString(R.string.wifi_add_app_single_network_saving_summary));
mSingleNetworkProcessingStatusView.setVisibility(View.VISIBLE);
} else {
mSummaryView.setTextColor(
com.android.settingslib.Utils.getColorAttr(mActivity,
android.R.attr.textColorSecondary));
mSummaryView.setText(
getString(R.string.wifi_add_app_networks_saving_summary,
mUiToRequestedList.size()));
}
break;
case MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK:
if (mIsSingleNetwork) {
mSingleNetworkProcessingStatusView.setText(
getString(R.string.wifi_add_app_single_network_saved_summary));
} else {
mSummaryView.setText(
getString(R.string.wifi_add_app_networks_saved_summary));
}
break;
case MESSAGE_SHOW_SAVE_FAILED:
if (mIsSingleNetwork) {
// Error message need to use colorError attribute to show.
mSingleNetworkProcessingStatusView.setTextColor(
com.android.settingslib.Utils.getColorAttr(mActivity,
android.R.attr.colorError));
mSingleNetworkProcessingStatusView.setText(
getString(R.string.wifi_add_app_network_save_failed_summary));
} else {
// Error message need to use colorError attribute to show.
mSummaryView.setTextColor(
com.android.settingslib.Utils.getColorAttr(mActivity,
android.R.attr.colorError));
mSummaryView.setText(
getString(R.string.wifi_add_app_network_save_failed_summary));
}
break;
default:
// Do nothing.
break;
}
}
@VisibleForTesting
void updateScanResultsToUi() {
if (mUiToRequestedList == null) {
// Nothing need to be updated.
return;
}
List<WifiEntry> reachableWifiEntries = null;
if (mWifiPickerTracker.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
reachableWifiEntries = mWifiPickerTracker.getWifiEntries();
final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
if (connectedWifiEntry != null) {
reachableWifiEntries.add(connectedWifiEntry);
}
}
// Update the signal level of the UI networks.
for (UiConfigurationItem uiConfigurationItem : mUiToRequestedList) {
uiConfigurationItem.mLevel = 0;
if (reachableWifiEntries != null) {
final Optional<WifiEntry> matchedWifiEntry = reachableWifiEntries.stream()
.filter(wifiEntry -> TextUtils.equals(
uiConfigurationItem.mWifiNetworkSuggestion.getSsid(),
wifiEntry.getSsid()))
.findFirst();
uiConfigurationItem.mLevel =
matchedWifiEntry.isPresent() ? matchedWifiEntry.get().getLevel() : 0;
}
}
if (mIsSingleNetwork) {
updateSingleNetworkSignalIcon(mUiToRequestedList.get(0).mLevel);
} else {
if (mUiConfigurationItemAdapter != null) {
mUiConfigurationItemAdapter.notifyDataSetChanged();
}
}
}
@Override
public void onResume() {
super.onResume();
onWifiEntriesChanged();
}
/** Called when the state of Wifi has changed. */
@Override
public void onWifiStateChanged() {
onWifiEntriesChanged();
}
/**
* Update the results when data changes
*/
@Override
public void onWifiEntriesChanged() {
updateScanResultsToUi();
}
@Override
public void onNumSavedSubscriptionsChanged() {
// Do nothing.
}
@Override
public void onNumSavedNetworksChanged() {
// Do nothing.
}
@VisibleForTesting
static int getWifiIconResource(int level) {
return WifiUtils.getInternetIconResource(level, false /* noInternet */);
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2018 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.calling;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
/**
* Interface to control disclaimer item from {@link WifiCallingDisclaimerFragment}.
*/
@VisibleForTesting
public abstract class DisclaimerItem {
private static final String SHARED_PREFERENCES_NAME = "wfc_disclaimer_prefs";
protected final Context mContext;
protected final int mSubId;
private final CarrierConfigManager mCarrierConfigManager;
DisclaimerItem(Context context, int subId) {
mContext = context;
mSubId = subId;
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
}
/**
* Called by the {@link WifiCallingDisclaimerFragment} when a user has clicked the agree button.
*/
void onAgreed() {
setBooleanSharedPrefs(getPrefKey(), true);
}
/**
* Checks whether the disclaimer item need to be displayed or not.
*
* @return Returns {@code true} if disclaimer item need to be displayed,
* {@code false} if not displayed.
*/
boolean shouldShow() {
if (getBooleanSharedPrefs(getPrefKey(), false)) {
logd("shouldShow: false due to a user has already agreed.");
return false;
}
logd("shouldShow: true");
return true;
}
/**
* Gets the configuration values for a particular sub id.
*
* @return The {@link PersistableBundle} instance containing the config value for a
* particular phone id, or default values.
*/
protected PersistableBundle getCarrierConfig() {
PersistableBundle config = mCarrierConfigManager.getConfigForSubId(mSubId);
if (config != null) {
return config;
}
// Return static default defined in CarrierConfigManager.
return CarrierConfigManager.getDefaultConfig();
}
protected void logd(String msg) {
Log.d(getName(), "[" + mSubId + "] " + msg);
}
/**
* Gets a title id for disclaimer item.
*
* @return Title id for disclaimer item.
*/
protected abstract int getTitleId();
/**
* Gets a message id for disclaimer item.
*
* @return Message id for disclaimer item.
*/
protected abstract int getMessageId();
/**
* Gets a name of disclaimer item.
*
* @return Name of disclaimer item.
*/
protected abstract String getName();
/**
* Gets a preference key to keep user's consent.
*
* @return Preference key to keep user's consent.
*/
protected abstract String getPrefKey();
/**
* Gets the boolean value from shared preferences.
*
* @param key The key for the preference item.
* @param defValue Value to return if this preference does not exist.
* @return The boolean value of corresponding key, or defValue.
*/
private boolean getBooleanSharedPrefs(String key, boolean defValue) {
SharedPreferences prefs = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
return prefs.getBoolean(key + mSubId, defValue);
}
/**
* Sets the boolean value to shared preferences.
*
* @param key The key for the preference item.
* @param value The value to be set for shared preferences.
*/
private void setBooleanSharedPrefs(String key, boolean value) {
SharedPreferences prefs = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
prefs.edit().putBoolean(key + mSubId, value).apply();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2018 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.calling;
import android.content.Context;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Factory class to create disclaimer items list.
*/
@VisibleForTesting
public final class DisclaimerItemFactory {
/**
* Creates disclaimer items list.
*
* @param context The application context
* @param subId The subscription id.
* @return The {@link DisclaimerItem} list instance, if there are no items, return empty list.
*/
public static List<DisclaimerItem> create(Context context, int subId) {
List<DisclaimerItem> itemList = getDisclaimerItemList(context, subId);
Iterator itr = itemList.iterator();
while (itr.hasNext()) {
DisclaimerItem item = (DisclaimerItem) itr.next();
if (!item.shouldShow()) {
itr.remove();
}
}
return itemList;
}
private static List<DisclaimerItem> getDisclaimerItemList(Context context, int subId) {
List<DisclaimerItem> itemList = new ArrayList<DisclaimerItem>();
itemList.add(new LocationPolicyDisclaimer(context, subId));
itemList.add(new EmergencyCallLimitationDisclaimer(context, subId));
return itemList;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2018 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.calling;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import java.util.List;
/**
* Adapter for disclaimer items list.
*/
public class DisclaimerItemListAdapter extends
RecyclerView.Adapter<DisclaimerItemListAdapter.DisclaimerItemViewHolder> {
private List<DisclaimerItem> mDisclaimerItemList;
public DisclaimerItemListAdapter(List<DisclaimerItem> list) {
mDisclaimerItemList = list;
}
@Override
public DisclaimerItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.wfc_simple_disclaimer_item, null, false);
return new DisclaimerItemViewHolder(view);
}
@Override
public void onBindViewHolder(DisclaimerItemViewHolder holder, int position) {
holder.titleView.setText(mDisclaimerItemList.get(position).getTitleId());
holder.descriptionView.setText(mDisclaimerItemList.get(position).getMessageId());
}
@Override
public int getItemCount() {
return mDisclaimerItemList.size();
}
public static class DisclaimerItemViewHolder extends RecyclerView.ViewHolder {
@VisibleForTesting
static final int ID_DISCLAIMER_ITEM_TITLE = R.id.disclaimer_title;
@VisibleForTesting
static final int ID_DISCLAIMER_ITEM_DESCRIPTION = R.id.disclaimer_desc;
public final TextView titleView;
public final TextView descriptionView;
public DisclaimerItemViewHolder(View itemView) {
super(itemView);
titleView = itemView.findViewById(ID_DISCLAIMER_ITEM_TITLE);
descriptionView = itemView.findViewById(ID_DISCLAIMER_ITEM_DESCRIPTION);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2018 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.calling;
import android.content.Context;
import android.telephony.CarrierConfigManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
/**
* Disclaimer item class for displaying emergency call limitation UI on
* {@link WifiCallingDisclaimerFragment}.
*/
public class EmergencyCallLimitationDisclaimer extends DisclaimerItem {
private static final String DISCLAIMER_ITEM_NAME = "EmergencyCallLimitationDisclaimer";
@VisibleForTesting
static final String KEY_HAS_AGREED_EMERGENCY_LIMITATION_DISCLAIMER =
"key_has_agreed_emergency_limitation_disclaimer";
private static final int UNINITIALIZED_DELAY_VALUE = -1;
public EmergencyCallLimitationDisclaimer(Context context, int subId) {
super(context, subId);
}
/**
* {@inheritDoc}
*/
@Override
boolean shouldShow() {
final int notificationDelay = getCarrierConfig().getInt(
CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT);
if (notificationDelay == UNINITIALIZED_DELAY_VALUE) {
logd("shouldShow: false due to carrier config is default(-1).");
return false;
}
return super.shouldShow();
}
/**
* {@inheritDoc}
*/
@Override
protected String getName() {
return DISCLAIMER_ITEM_NAME;
}
/**
* {@inheritDoc}
*/
@Override
protected int getTitleId() {
return R.string.wfc_disclaimer_emergency_limitation_title_text;
}
/**
* {@inheritDoc}
*/
@Override
protected int getMessageId() {
return R.string.wfc_disclaimer_emergency_limitation_desc_text;
}
/**
* {@inheritDoc}
*/
@Override
protected String getPrefKey() {
return KEY_HAS_AGREED_EMERGENCY_LIMITATION_DISCLAIMER;
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.calling;
import android.content.Context;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import androidx.core.text.util.LinkifyCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
/** A preference which supports linkify text as a description in the summary **/
public class LinkifyDescriptionPreference extends Preference {
public LinkifyDescriptionPreference(Context context) {
this(context, null);
}
public LinkifyDescriptionPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
if (summaryView == null || summaryView.getVisibility() != View.VISIBLE) {
return;
}
final CharSequence summary = getSummary();
if (TextUtils.isEmpty(summary)) {
return;
}
summaryView.setMaxLines(Integer.MAX_VALUE);
final SpannableString spannableSummary = new SpannableString(summary);
if (spannableSummary.getSpans(0, spannableSummary.length(), ClickableSpan.class)
.length > 0) {
summaryView.setMovementMethod(LinkMovementMethod.getInstance());
}
LinkifyCompat.addLinks(summaryView,
Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS);
}
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright (C) 2018 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.calling;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.RadioButton;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog.Builder;
import com.android.settings.CustomListPreference;
import com.android.settings.R;
/**
* ListPreference contain the entry summary.
*/
public class ListWithEntrySummaryPreference extends CustomListPreference {
private static final String LOG_TAG = "ListWithEntrySummaryPreference";
private final Context mContext;
private CharSequence[] mSummaries;
/**
* ListWithEntrySummaryPreference constructor.
*
* @param context The context of view.
* @param attrs The attributes of the XML tag that is inflating the linear layout.
*/
public ListWithEntrySummaryPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
TypedArray array = context.obtainStyledAttributes(attrs,
R.styleable.ListWithEntrySummaryPreference, 0, 0);
mSummaries = array.getTextArray(R.styleable.ListWithEntrySummaryPreference_entrySummaries);
array.recycle();
}
/**
* Sets the summaries of mode items to be shown in the mode select dialog.
*
* @param summariesResId The summaries of mode items.
*/
public void setEntrySummaries(int summariesResId) {
mSummaries = getContext().getResources().getTextArray(summariesResId);
}
/**
* Sets the summaries of mode items to be shown in the mode select dialog.
*
* @param summaries The summaries of mode items.
*/
public void setEntrySummaries(CharSequence[] summaries) {
mSummaries = summaries;
}
private CharSequence getEntrySummary(int index) {
if (mSummaries == null) {
Log.w(LOG_TAG, "getEntrySummary : mSummaries is null");
return "";
}
return mSummaries[index];
}
@Override
protected void onPrepareDialogBuilder(Builder builder,
DialogInterface.OnClickListener listener) {
ListAdapter la = (ListAdapter) new SelectorAdapter(mContext,
R.xml.single_choice_list_item_2, this);
builder.setSingleChoiceItems(la, findIndexOfValue(getValue()), listener);
super.onPrepareDialogBuilder(builder, listener);
}
private static class SelectorAdapter extends ArrayAdapter<CharSequence> {
private final Context mContext;
private ListWithEntrySummaryPreference mSelector;
/**
* SelectorAdapter constructor.
*
* @param context The current context.
* @param rowResourceId The resource id of the XML tag that is inflating the linear layout.
* @param listPreference The instance of ListWithEntrySummaryPreference.
*/
public SelectorAdapter(Context context, int rowResourceId,
ListWithEntrySummaryPreference listPreference) {
super(context, rowResourceId, listPreference.getEntryValues());
mContext = context;
mSelector = listPreference;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View row = inflater.inflate(R.xml.single_choice_list_item_2, parent, false);
TextView title = (TextView) row.findViewById(R.id.title);
title.setText(mSelector.getEntries()[position]);
TextView summary = (TextView) row.findViewById(R.id.summary);
summary.setText(mSelector.getEntrySummary(position));
RadioButton rb = (RadioButton) row.findViewById(R.id.radio);
if (position == mSelector.findIndexOfValue(mSelector.getValue())) {
rb.setChecked(true);
}
return row;
}
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
final SavedState myState = new SavedState(superState);
myState.mEntries = getEntries();
myState.mEntryValues = getEntryValues();
myState.mSummaries = mSummaries;
return myState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
setEntries(myState.mEntries);
setEntryValues(myState.mEntryValues);
mSummaries = myState.mSummaries;
}
/**
* We save entries, entryValues and summaries into bundle.
* At onCreate of fragment, dialog will be restored if it was open. In this case,
* we need to restore entries, entryValues and summaries. Without those information,
* crash when entering multi window during wfc modes dialog shown.
*/
private static class SavedState extends BaseSavedState {
private CharSequence[] mEntries;
private CharSequence[] mEntryValues;
private CharSequence[] mSummaries;
public SavedState(Parcel source) {
super(source);
mEntries = source.readCharSequenceArray();
mEntryValues = source.readCharSequenceArray();
mSummaries = source.readCharSequenceArray();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeCharSequenceArray(mEntries);
dest.writeCharSequenceArray(mEntryValues);
dest.writeCharSequenceArray(mSummaries);
}
public SavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2018 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.calling;
import android.content.Context;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
/**
* Disclaimer item class for displaying location privacy policy UI on
* {@link WifiCallingDisclaimerFragment}.
*/
class LocationPolicyDisclaimer extends DisclaimerItem {
private static final String DISCLAIMER_ITEM_NAME = "LocationPolicyDisclaimer";
@VisibleForTesting
static final String KEY_HAS_AGREED_LOCATION_DISCLAIMER
= "key_has_agreed_location_disclaimer";
LocationPolicyDisclaimer(Context context, int subId) {
super(context, subId);
}
/**
* {@inheritDoc}
*/
@Override
boolean shouldShow() {
PersistableBundle config = getCarrierConfig();
if (!config.getBoolean(CarrierConfigManager.KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL)) {
logd("shouldShow: false due to carrier config is false.");
return false;
}
if (config.getBoolean(CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL)) {
logd("shouldShow: false due to WFC is on as default.");
return false;
}
return super.shouldShow();
}
/**
* {@inheritDoc}
*/
@Override
protected String getName() {
return DISCLAIMER_ITEM_NAME;
}
/**
* {@inheritDoc}
*/
@Override
protected int getTitleId() {
return R.string.wfc_disclaimer_location_title_text;
}
/**
* {@inheritDoc}
*/
@Override
protected int getMessageId() {
return R.string.wfc_disclaimer_location_desc_text;
}
/**
* {@inheritDoc}
*/
@Override
protected String getPrefKey() {
return KEY_HAS_AGREED_LOCATION_DISCLAIMER;
}
}

View File

@@ -0,0 +1,9 @@
# Default reviewers for this and subdirectories.
allenwtsu@google.com
andychou@google.com
bonianchen@google.com
leechou@google.com
songferngwang@google.com
tomhsu@google.com
# Emergency approvers in case the above are not available

View File

@@ -0,0 +1,148 @@
/*
* Copyright (C) 2018 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.calling;
import android.app.Activity;
import android.os.Bundle;
import android.telephony.SubscriptionManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import java.util.ArrayList;
import java.util.List;
/**
* Fragment for displaying disclaimers for WFC.
*/
public class WifiCallingDisclaimerFragment extends InstrumentedFragment
implements View.OnClickListener {
private static final String TAG = "WifiCallingDisclaimerFragment";
private static final String STATE_IS_SCROLL_TO_BOTTOM = "state_is_scroll_to_bottom";
private List<DisclaimerItem> mDisclaimerItemList = new ArrayList<DisclaimerItem>();
private Button mAgreeButton;
private Button mDisagreeButton;
private boolean mScrollToBottom;
@Override
public int getMetricsCategory() {
return MetricsEvent.WIFI_CALLING;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle args = getArguments();
final int subId = (args != null) ? args.getInt(WifiCallingSettingsForSub.EXTRA_SUB_ID)
: SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
mDisclaimerItemList = DisclaimerItemFactory.create(getActivity(), subId);
if (mDisclaimerItemList.isEmpty()) {
finish(Activity.RESULT_OK);
return;
}
if (savedInstanceState != null) {
mScrollToBottom = savedInstanceState.getBoolean(
STATE_IS_SCROLL_TO_BOTTOM, mScrollToBottom);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.wfc_disclaimer_fragment, container, false);
mAgreeButton = view.findViewById(R.id.agree_button);
mAgreeButton.setOnClickListener(this);
mDisagreeButton = view.findViewById(R.id.disagree_button);
mDisagreeButton.setOnClickListener(this);
final RecyclerView recyclerView = (RecyclerView) view.findViewById(
R.id.disclaimer_item_list);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(new DisclaimerItemListAdapter(mDisclaimerItemList));
RecyclerView.ItemDecoration itemDecoration =
new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(itemDecoration);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1 /* scrolling down */)) {
mScrollToBottom = true;
updateButtonState();
recyclerView.removeOnScrollListener(this);
}
}
});
return view;
}
@Override
public void onResume() {
super.onResume();
updateButtonState();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_IS_SCROLL_TO_BOTTOM, mScrollToBottom);
}
private void updateButtonState() {
mAgreeButton.setEnabled(mScrollToBottom);
}
@Override
public void onClick(View v) {
if (v == mAgreeButton) {
for (DisclaimerItem item : mDisclaimerItemList) {
item.onAgreed();
}
finish(Activity.RESULT_OK);
} else if (v == mDisagreeButton) {
finish(Activity.RESULT_CANCELED);
}
}
@VisibleForTesting
void finish(int result) {
Activity activity = getActivity();
activity.setResult(result, null);
activity.finish();
}
}

View File

@@ -0,0 +1,345 @@
/*
* Copyright (C) 2015 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.calling;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import com.android.internal.util.CollectionUtils;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.network.ActiveSubscriptionsListener;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settings.search.actionbar.SearchMenuController;
import com.android.settings.support.actionbar.HelpResourceProvider;
import com.android.settings.widget.RtlCompatibleViewPager;
import com.android.settings.widget.SlidingTabLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* "Wi-Fi Calling settings" screen. This is the container fragment which holds
* {@link WifiCallingSettingsForSub} fragments.
*/
public class WifiCallingSettings extends SettingsPreferenceFragment
implements HelpResourceProvider {
private static final String TAG = "WifiCallingSettings";
private int mConstructionSubId;
private List<SubscriptionInfo> mSil;
private ActiveSubscriptionsListener mSubscriptionChangeListener;
private static final int [] EMPTY_SUB_ID_LIST = new int[0];
//UI objects
private RtlCompatibleViewPager mViewPager;
private WifiCallingViewPagerAdapter mPagerAdapter;
private SlidingTabLayout mTabLayout;
private final class InternalViewPagerListener implements
RtlCompatibleViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// Do nothing.
}
@Override
public void onPageSelected(int position) {
updateTitleForCurrentSub();
}
@Override
public void onPageScrollStateChanged(int state) {
// Do nothing.
}
}
@Override
public int getMetricsCategory() {
return SettingsEnums.WIFI_CALLING;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (MobileNetworkUtils.isMobileNetworkUserRestricted(getActivity())) {
return new ViewStub(getActivity());
}
final View view = inflater.inflate(R.layout.wifi_calling_settings_tabs, container, false);
mTabLayout = view.findViewById(R.id.sliding_tabs);
mViewPager = (RtlCompatibleViewPager) view.findViewById(R.id.view_pager);
mPagerAdapter = new WifiCallingViewPagerAdapter(getChildFragmentManager(), mViewPager);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.addOnPageChangeListener(new InternalViewPagerListener());
maybeSetViewForSubId();
return view;
}
private int getConstructionSubId(Bundle bundle) {
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
Intent intent = getActivity().getIntent();
if (intent != null) {
subId = intent.getIntExtra(Settings.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
if ((subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) && (bundle != null)) {
subId = bundle.getInt(Settings.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
return subId;
}
private void maybeSetViewForSubId() {
if (mSil == null) {
return;
}
int subId = mConstructionSubId;
if (SubscriptionManager.isValidSubscriptionId(subId)) {
for (SubscriptionInfo subInfo : mSil) {
if (subId == subInfo.getSubscriptionId()) {
mViewPager.setCurrentItem(mSil.indexOf(subInfo));
break;
}
}
}
}
@Override
public void onCreate(Bundle icicle) {
mConstructionSubId = getConstructionSubId(icicle);
super.onCreate(icicle);
if (MobileNetworkUtils.isMobileNetworkUserRestricted(getActivity())) {
finish();
return;
}
Log.d(TAG, "SubId=" + mConstructionSubId);
if (mConstructionSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
// Only config Wfc if it's enabled by platform.
mSubscriptionChangeListener = getSubscriptionChangeListener(getContext());
}
mSil = updateSubList();
}
@Override
public void onStart() {
super.onStart();
if (mSil != null && mSil.size() > 1) {
mTabLayout.setViewPager(mViewPager);
} else {
mTabLayout.setVisibility(View.GONE);
}
updateTitleForCurrentSub();
if (mSubscriptionChangeListener != null) {
mSubscriptionChangeListener.start();
}
}
@Override
public void onStop() {
if (mSubscriptionChangeListener != null) {
mSubscriptionChangeListener.stop();
}
super.onStop();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// keep subscription ID for recreation
outState.putInt(Settings.EXTRA_SUB_ID, mConstructionSubId);
}
@Override
public int getHelpResource() {
return R.string.help_uri_wifi_calling;
}
@VisibleForTesting
final class WifiCallingViewPagerAdapter extends FragmentPagerAdapter {
private final RtlCompatibleViewPager mViewPager;
public WifiCallingViewPagerAdapter(FragmentManager fragmentManager,
RtlCompatibleViewPager viewPager) {
super(fragmentManager);
mViewPager = viewPager;
}
@Override
public CharSequence getPageTitle(int position) {
return String.valueOf(SubscriptionUtil.getUniqueSubscriptionDisplayName(
mSil.get(position), getContext()));
}
@Override
public Fragment getItem(int position) {
int subId = mSil.get(position).getSubscriptionId();
Log.d(TAG, "Adapter getItem " + position + " for subId=" + subId);
final Bundle args = new Bundle();
args.putBoolean(SearchMenuController.NEED_SEARCH_ICON_IN_ACTION_BAR, false);
args.putInt(WifiCallingSettingsForSub.FRAGMENT_BUNDLE_SUBID, subId);
final WifiCallingSettingsForSub fragment = new WifiCallingSettingsForSub();
fragment.setArguments(args);
return fragment;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Log.d(TAG, "Adapter instantiateItem " + position);
return super.instantiateItem(container,
mViewPager.getRtlAwareIndex(position));
}
@Override
public int getCount() {
if (mSil == null) {
Log.d(TAG, "Adapter getCount null mSil ");
return 0;
} else {
Log.d(TAG, "Adapter getCount " + mSil.size());
return mSil.size();
}
}
}
@VisibleForTesting
protected List<SubscriptionInfo> getSelectableSubscriptions(Context context) {
return SubscriptionUtil.getSelectableSubscriptionInfoList(context);
}
private List<SubscriptionInfo> updateSubList() {
List<SubscriptionInfo> subInfoList = getSelectableSubscriptions(getContext());
if (subInfoList == null) {
return Collections.emptyList();
}
List<SubscriptionInfo> selectedList = new ArrayList<SubscriptionInfo>();
for (SubscriptionInfo subInfo : subInfoList) {
int subId = subInfo.getSubscriptionId();
try {
if (MobileNetworkUtils.isWifiCallingEnabled(
getContext(),
subId,
queryImsState(subId))) {
selectedList.add(subInfo);
}
} catch (Exception exception) {}
}
return selectedList;
}
private void updateTitleForCurrentSub() {
if (CollectionUtils.size(mSil) > 1) {
final int subId = mSil.get(mViewPager.getCurrentItem()).getSubscriptionId();
final String title = SubscriptionManager.getResourcesForSubId(getContext(), subId)
.getString(R.string.wifi_calling_settings_title);
getActivity().getActionBar().setTitle(title);
}
}
@VisibleForTesting
protected WifiCallingQueryImsState queryImsState(int subId) {
return new WifiCallingQueryImsState(getContext(), subId);
}
@VisibleForTesting
protected ActiveSubscriptionsListener getSubscriptionChangeListener(Context context) {
return new ActiveSubscriptionsListener(context.getMainLooper(), context) {
public void onChanged() {
onSubscriptionChange(context);
}
};
}
protected void onSubscriptionChange(Context context) {
if (mSubscriptionChangeListener == null) {
return;
}
int [] previousSubIdList = subscriptionIdList(mSil);
List<SubscriptionInfo> updateList = updateSubList();
int [] currentSubIdList = subscriptionIdList(updateList);
if (currentSubIdList.length > 0) {
// only keep fragment when any provisioned subscription is available
if (previousSubIdList.length == 0) {
// initial loading of list
mSil = updateList;
return;
}
if (previousSubIdList.length == currentSubIdList.length) {
// same number of subscriptions
if ( (!containsSubId(previousSubIdList, mConstructionSubId))
// original request not yet appears in list
|| containsSubId(currentSubIdList, mConstructionSubId) )
// original request appears in list
{
mSil = updateList;
return;
}
}
}
Log.d(TAG, "Closed subId=" + mConstructionSubId
+ " due to subscription change: " + Arrays.toString(previousSubIdList)
+ " -> " + Arrays.toString(currentSubIdList));
// close this fragment when no provisioned subscriptions available
if (mSubscriptionChangeListener != null) {
mSubscriptionChangeListener.stop();
mSubscriptionChangeListener = null;
}
// close this fragment
finishFragment();
}
protected int [] subscriptionIdList(List<SubscriptionInfo> subInfoList) {
return (subInfoList == null) ? EMPTY_SUB_ID_LIST :
subInfoList.stream().mapToInt(subInfo -> (subInfo == null) ?
SubscriptionManager.INVALID_SUBSCRIPTION_ID : subInfo.getSubscriptionId())
.toArray();
}
protected boolean containsSubId(int [] subIdArray, int subIdLookUp) {
return Arrays.stream(subIdArray).anyMatch(subId -> (subId == subIdLookUp));
}
}

View File

@@ -0,0 +1,748 @@
/*
* Copyright (C) 2017 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.calling;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ProvisioningManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceScreen;
import com.android.ims.ImsConfig;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.flags.Flags;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settings.widget.SettingsMainSwitchPreference;
import java.util.List;
/**
* This is the inner class of {@link WifiCallingSettings} fragment.
* The preference screen lets you enable/disable Wi-Fi Calling and change Wi-Fi Calling mode.
*/
public class WifiCallingSettingsForSub extends SettingsPreferenceFragment
implements OnCheckedChangeListener,
Preference.OnPreferenceChangeListener {
private static final String TAG = "WifiCallingForSub";
//String keys for preference lookup
private static final String SWITCH_BAR = "wifi_calling_switch_bar";
private static final String BUTTON_WFC_MODE = "wifi_calling_mode";
private static final String BUTTON_WFC_ROAMING_MODE = "wifi_calling_roaming_mode";
private static final String PREFERENCE_EMERGENCY_ADDRESS = "emergency_address_key";
private static final String PREFERENCE_NO_OPTIONS_DESC = "no_options_description";
@VisibleForTesting
static final int REQUEST_CHECK_WFC_EMERGENCY_ADDRESS = 1;
@VisibleForTesting
static final int REQUEST_CHECK_WFC_DISCLAIMER = 2;
public static final String EXTRA_LAUNCH_CARRIER_APP = "EXTRA_LAUNCH_CARRIER_APP";
public static final String EXTRA_SUB_ID = "EXTRA_SUB_ID";
protected static final String FRAGMENT_BUNDLE_SUBID = "subId";
public static final int LAUNCH_APP_ACTIVATE = 0;
public static final int LAUNCH_APP_UPDATE = 1;
//UI objects
private SettingsMainSwitchPreference mSwitchBar;
private ListWithEntrySummaryPreference mButtonWfcMode;
private ListWithEntrySummaryPreference mButtonWfcRoamingMode;
private Preference mUpdateAddress;
private boolean mEditableWfcMode = true;
private boolean mEditableWfcRoamingMode = true;
private boolean mUseWfcHomeModeForRoaming = false;
private boolean mOverrideWfcRoamingModeWhileUsingNtn = false;
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private ImsMmTelManager mImsMmTelManager;
private ProvisioningManager mProvisioningManager;
private TelephonyManager mTelephonyManager;
private PhoneTelephonyCallback mTelephonyCallback;
private class PhoneTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.CallStateListener {
/*
* Enable/disable controls when in/out of a call and depending on
* TTY mode and TTY support over VoLTE.
* @see android.telephony.PhoneStateListener#onCallStateChanged(int,
* java.lang.String)
*/
@Override
public void onCallStateChanged(int state) {
final SettingsActivity activity = (SettingsActivity) getActivity();
boolean isWfcEnabled = false;
boolean isCallStateIdle = false;
final SettingsMainSwitchPreference prefSwitch = (SettingsMainSwitchPreference)
getPreferenceScreen().findPreference(SWITCH_BAR);
if (prefSwitch != null) {
isWfcEnabled = prefSwitch.isChecked();
isCallStateIdle = getTelephonyManagerForSub(
WifiCallingSettingsForSub.this.mSubId).getCallStateForSubscription()
== TelephonyManager.CALL_STATE_IDLE;
boolean isNonTtyOrTtyOnVolteEnabled = true;
if (isWfcEnabled || isCallStateIdle) {
isNonTtyOrTtyOnVolteEnabled =
queryImsState(WifiCallingSettingsForSub.this.mSubId)
.isAllowUserControl();
}
isWfcEnabled = isWfcEnabled && isNonTtyOrTtyOnVolteEnabled;
prefSwitch.setEnabled(isCallStateIdle && isNonTtyOrTtyOnVolteEnabled);
}
boolean isWfcModeEditable = true;
boolean isWfcRoamingModeEditable = false;
if (isWfcEnabled && isCallStateIdle) {
final CarrierConfigManager configManager = (CarrierConfigManager)
activity.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager != null) {
PersistableBundle b = configManager.getConfigForSubId(
WifiCallingSettingsForSub.this.mSubId);
if (b != null) {
isWfcModeEditable = b.getBoolean(
CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL);
isWfcRoamingModeEditable = b.getBoolean(
CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL);
}
}
} else {
isWfcModeEditable = false;
isWfcRoamingModeEditable = false;
}
final Preference pref = getPreferenceScreen().findPreference(BUTTON_WFC_MODE);
if (pref != null) {
pref.setEnabled(isWfcModeEditable);
}
final Preference pref_roam =
getPreferenceScreen().findPreference(BUTTON_WFC_ROAMING_MODE);
if (pref_roam != null) {
pref_roam.setEnabled(isWfcRoamingModeEditable
&& !overrideWfcRoamingModeWhileUsingNtn());
}
}
}
/*
* Launch carrier emergency address management activity
*/
private final OnPreferenceClickListener mUpdateAddressListener =
preference -> {
final Intent carrierAppIntent = getCarrierActivityIntent();
if (carrierAppIntent != null) {
carrierAppIntent.putExtra(EXTRA_LAUNCH_CARRIER_APP, LAUNCH_APP_UPDATE);
startActivity(carrierAppIntent);
}
return true;
};
private final ProvisioningManager.Callback mProvisioningCallback =
new ProvisioningManager.Callback() {
@Override
public void onProvisioningIntChanged(int item, int value) {
if (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
|| item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED) {
// The provisioning policy might have changed. Update the body to make sure
// this change takes effect if needed.
updateBody();
}
}
};
@VisibleForTesting
void showAlert(Intent intent) {
final Context context = getActivity();
final CharSequence title =
intent.getCharSequenceExtra(ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE);
final CharSequence message =
intent.getCharSequenceExtra(ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(message)
.setTitle(title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.ok, null);
final AlertDialog dialog = builder.create();
dialog.show();
}
private IntentFilter mIntentFilter;
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR)) {
// If this fragment is active then we are immediately
// showing alert on screen. There is no need to add
// notification in this case.
//
// In order to communicate to ImsPhone that it should
// not show notification, we are changing result code here.
setResultCode(Activity.RESULT_CANCELED);
showAlert(intent);
}
}
};
@Override
public int getMetricsCategory() {
return SettingsEnums.WIFI_CALLING_FOR_SUB;
}
@Override
public int getHelpResource() {
// Return 0 to suppress help icon. The help will be populated by parent page.
return 0;
}
@VisibleForTesting
TelephonyManager getTelephonyManagerForSub(int subId) {
if (mTelephonyManager == null) {
mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
}
return mTelephonyManager.createForSubscriptionId(subId);
}
@VisibleForTesting
WifiCallingQueryImsState queryImsState(int subId) {
return new WifiCallingQueryImsState(getContext(), subId);
}
@VisibleForTesting
ProvisioningManager getImsProvisioningManager() {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
return null;
}
return ProvisioningManager.createForSubscriptionId(mSubId);
}
@VisibleForTesting
ImsMmTelManager getImsMmTelManager() {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
return null;
}
return ImsMmTelManager.createForSubscriptionId(mSubId);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.wifi_calling_settings);
// SubId should always be specified when creating this fragment. Either through
// fragment.setArguments() or through savedInstanceState.
if (getArguments() != null && getArguments().containsKey(FRAGMENT_BUNDLE_SUBID)) {
mSubId = getArguments().getInt(FRAGMENT_BUNDLE_SUBID);
} else if (savedInstanceState != null) {
mSubId = savedInstanceState.getInt(
FRAGMENT_BUNDLE_SUBID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
mProvisioningManager = getImsProvisioningManager();
mImsMmTelManager = getImsMmTelManager();
mSwitchBar = (SettingsMainSwitchPreference) findPreference(SWITCH_BAR);
mButtonWfcMode = findPreference(BUTTON_WFC_MODE);
mButtonWfcMode.setOnPreferenceChangeListener(this);
mButtonWfcRoamingMode = findPreference(BUTTON_WFC_ROAMING_MODE);
mButtonWfcRoamingMode.setOnPreferenceChangeListener(this);
mUpdateAddress = findPreference(PREFERENCE_EMERGENCY_ADDRESS);
mUpdateAddress.setOnPreferenceClickListener(mUpdateAddressListener);
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(ImsManager.ACTION_WFC_IMS_REGISTRATION_ERROR);
updateDescriptionForOptions(
List.of(mButtonWfcMode, mButtonWfcRoamingMode, mUpdateAddress));
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(FRAGMENT_BUNDLE_SUBID, mSubId);
super.onSaveInstanceState(outState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(
R.layout.wifi_calling_settings_preferences, container, false);
final ViewGroup prefs_container = view.findViewById(android.R.id.tabcontent);
Utils.prepareCustomPreferencesList(container, view, prefs_container, false);
final View prefs = super.onCreateView(inflater, prefs_container, savedInstanceState);
prefs_container.addView(prefs);
return view;
}
@VisibleForTesting
boolean isWfcProvisionedOnDevice() {
return queryImsState(mSubId).isWifiCallingProvisioned();
}
private void updateBody() {
if (!isWfcProvisionedOnDevice()) {
// This screen is not allowed to be shown due to provisioning policy and should
// therefore be closed.
finish();
return;
}
final CarrierConfigManager configManager = (CarrierConfigManager)
getSystemService(Context.CARRIER_CONFIG_SERVICE);
boolean isWifiOnlySupported = true;
if (configManager != null) {
final PersistableBundle b = configManager.getConfigForSubId(mSubId);
if (b != null) {
mEditableWfcMode = b.getBoolean(
CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL);
mEditableWfcRoamingMode = b.getBoolean(
CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL);
mUseWfcHomeModeForRoaming = b.getBoolean(
CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL,
false);
isWifiOnlySupported = b.getBoolean(
CarrierConfigManager.KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true);
mOverrideWfcRoamingModeWhileUsingNtn = b.getBoolean(
CarrierConfigManager.KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL,
true);
}
}
final Resources res = getResourcesForSubId();
mButtonWfcMode.setTitle(res.getString(R.string.wifi_calling_mode_title));
mButtonWfcMode.setDialogTitle(res.getString(R.string.wifi_calling_mode_dialog_title));
mButtonWfcRoamingMode.setTitle(res.getString(R.string.wifi_calling_roaming_mode_title));
mButtonWfcRoamingMode.setDialogTitle(
res.getString(R.string.wifi_calling_roaming_mode_dialog_title));
if (isWifiOnlySupported) {
// Set string resources WITH option wifi only in mButtonWfcMode.
mButtonWfcMode.setEntries(
res.getStringArray(R.array.wifi_calling_mode_choices));
mButtonWfcMode.setEntryValues(res.getStringArray(R.array.wifi_calling_mode_values));
mButtonWfcMode.setEntrySummaries(
res.getStringArray(R.array.wifi_calling_mode_summaries));
// Set string resources WITH option wifi only in mButtonWfcRoamingMode.
mButtonWfcRoamingMode.setEntries(
res.getStringArray(R.array.wifi_calling_mode_choices_v2));
mButtonWfcRoamingMode.setEntryValues(
res.getStringArray(R.array.wifi_calling_mode_values));
mButtonWfcRoamingMode.setEntrySummaries(
res.getStringArray(R.array.wifi_calling_mode_summaries));
} else {
// Set string resources WITHOUT option wifi only in mButtonWfcMode.
mButtonWfcMode.setEntries(
res.getStringArray(R.array.wifi_calling_mode_choices_without_wifi_only));
mButtonWfcMode.setEntryValues(
res.getStringArray(R.array.wifi_calling_mode_values_without_wifi_only));
mButtonWfcMode.setEntrySummaries(
res.getStringArray(R.array.wifi_calling_mode_summaries_without_wifi_only));
// Set string resources WITHOUT option wifi only in mButtonWfcRoamingMode.
mButtonWfcRoamingMode.setEntries(
res.getStringArray(R.array.wifi_calling_mode_choices_v2_without_wifi_only));
mButtonWfcRoamingMode.setEntryValues(
res.getStringArray(R.array.wifi_calling_mode_values_without_wifi_only));
mButtonWfcRoamingMode.setEntrySummaries(
res.getStringArray(R.array.wifi_calling_mode_summaries_without_wifi_only));
}
// NOTE: Buttons will be enabled/disabled in mTelephonyCallback
final WifiCallingQueryImsState queryIms = queryImsState(mSubId);
final boolean wfcEnabled = queryIms.isEnabledByUser()
&& queryIms.isAllowUserControl();
mSwitchBar.setChecked(wfcEnabled);
int wfcMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
int wfcRoamingMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
boolean hasException = false;
try {
wfcMode = mImsMmTelManager.getVoWiFiModeSetting();
wfcRoamingMode = mImsMmTelManager.getVoWiFiRoamingModeSetting();
} catch (IllegalArgumentException e) {
hasException = true;
Log.e(TAG, "getResourceIdForWfcMode: Exception", e);
}
mButtonWfcMode.setValue(Integer.toString(wfcMode));
mButtonWfcRoamingMode.setValue(Integer.toString(wfcRoamingMode));
updateButtonWfcMode(wfcEnabled && !hasException, wfcMode, wfcRoamingMode);
}
@Override
public void onResume() {
super.onResume();
updateBody();
Context context = getActivity();
if (mTelephonyCallback == null && queryImsState(mSubId).isWifiCallingSupported()) {
mTelephonyCallback = new PhoneTelephonyCallback();
getTelephonyManagerForSub(mSubId).registerTelephonyCallback(
context.getMainExecutor(), mTelephonyCallback);
mSwitchBar.addOnSwitchChangeListener(this);
}
context.registerReceiver(mIntentReceiver, mIntentFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
final Intent intent = getActivity().getIntent();
if (intent.getBooleanExtra(Phone.EXTRA_KEY_ALERT_SHOW, false)) {
showAlert(intent);
}
// Register callback for provisioning changes.
registerProvisioningChangedCallback();
}
@Override
public void onPause() {
super.onPause();
Context context = getActivity();
if (mTelephonyCallback != null) {
getTelephonyManagerForSub(mSubId).unregisterTelephonyCallback(mTelephonyCallback);
mTelephonyCallback = null;
mSwitchBar.removeOnSwitchChangeListener(this);
}
context.unregisterReceiver(mIntentReceiver);
// Remove callback for provisioning changes.
unregisterProvisioningChangedCallback();
}
/**
* Listens to the state change of the switch.
*/
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.d(TAG, "onSwitchChanged(" + isChecked + ")");
if (!isChecked) {
updateWfcMode(false);
return;
}
// Launch disclaimer fragment before turning on WFC
final Context context = getActivity();
final Bundle args = new Bundle();
args.putInt(EXTRA_SUB_ID, mSubId);
new SubSettingLauncher(context)
.setDestination(WifiCallingDisclaimerFragment.class.getName())
.setArguments(args)
.setTitleRes(R.string.wifi_calling_settings_title)
.setSourceMetricsCategory(getMetricsCategory())
.setResultListener(this, REQUEST_CHECK_WFC_DISCLAIMER)
.launch();
}
/*
* Get the Intent to launch carrier emergency address management activity.
* Return null when no activity found.
*/
private Intent getCarrierActivityIntent() {
// Retrieve component name from carrier config
final CarrierConfigManager configManager =
getActivity().getSystemService(CarrierConfigManager.class);
if (configManager == null) return null;
final PersistableBundle bundle = configManager.getConfigForSubId(mSubId);
if (bundle == null) return null;
final String carrierApp = bundle.getString(
CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING);
if (TextUtils.isEmpty(carrierApp)) return null;
final ComponentName componentName = ComponentName.unflattenFromString(carrierApp);
if (componentName == null) return null;
// Build and return intent
final Intent intent = new Intent();
intent.setComponent(componentName);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, mSubId);
return intent;
}
/*
* Turn on/off WFC mode with ImsManager and update UI accordingly
*/
private void updateWfcMode(boolean wfcEnabled) {
Log.i(TAG, "updateWfcMode(" + wfcEnabled + ")");
boolean hasException = false;
try {
mImsMmTelManager.setVoWiFiSettingEnabled(wfcEnabled);
} catch (IllegalArgumentException e) {
Log.e(TAG, "updateWfcMode: Exception", e);
hasException = true;
}
int wfcMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
int wfcRoamingMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
if (!hasException) {
try {
wfcMode = mImsMmTelManager.getVoWiFiModeSetting();
wfcRoamingMode = mImsMmTelManager.getVoWiFiRoamingModeSetting();
} catch (IllegalArgumentException e) {
hasException = true;
Log.e(TAG, "updateWfcMode: Exception", e);
}
}
updateButtonWfcMode(wfcEnabled && !hasException, wfcMode, wfcRoamingMode);
if (wfcEnabled) {
mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), wfcMode);
} else {
mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), -1);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "WFC activity request = " + requestCode + " result = " + resultCode);
switch (requestCode) {
case REQUEST_CHECK_WFC_EMERGENCY_ADDRESS:
if (resultCode == Activity.RESULT_OK) {
updateWfcMode(true);
}
break;
case REQUEST_CHECK_WFC_DISCLAIMER:
if (resultCode == Activity.RESULT_OK) {
// Call address management activity before turning on WFC
final Intent carrierAppIntent = getCarrierActivityIntent();
if (carrierAppIntent != null) {
carrierAppIntent.putExtra(EXTRA_LAUNCH_CARRIER_APP, LAUNCH_APP_ACTIVATE);
startActivityForResult(carrierAppIntent,
REQUEST_CHECK_WFC_EMERGENCY_ADDRESS);
} else {
updateWfcMode(true);
}
}
break;
default:
Log.e(TAG, "Unexpected request: " + requestCode);
break;
}
}
private void updateButtonWfcMode(boolean wfcEnabled,
int wfcMode, int wfcRoamingMode) {
mButtonWfcMode.setSummary(getWfcModeSummary(wfcMode));
mButtonWfcMode.setEnabled(wfcEnabled && mEditableWfcMode);
// mButtonWfcRoamingMode.setSummary is not needed; summary is just selected value.
mButtonWfcRoamingMode.setEnabled(wfcEnabled && mEditableWfcRoamingMode
&& !overrideWfcRoamingModeWhileUsingNtn());
final PreferenceScreen preferenceScreen = getPreferenceScreen();
final boolean updateAddressEnabled = (getCarrierActivityIntent() != null);
if (wfcEnabled) {
// Don't show WFC (home) preference if it's not editable.
mButtonWfcMode.setVisible(mEditableWfcMode);
// Don't show WFC roaming preference if it's not editable.
mButtonWfcRoamingMode.setVisible(
mEditableWfcRoamingMode && !mUseWfcHomeModeForRoaming);
mUpdateAddress.setVisible(updateAddressEnabled);
} else {
mButtonWfcMode.setVisible(false);
mButtonWfcRoamingMode.setVisible(false);
mUpdateAddress.setVisible(false);
}
updateDescriptionForOptions(
List.of(mButtonWfcMode, mButtonWfcRoamingMode, mUpdateAddress));
}
private void updateDescriptionForOptions(List<Preference> visibleOptions) {
LinkifyDescriptionPreference pref = findPreference(PREFERENCE_NO_OPTIONS_DESC);
if (pref == null) {
return;
}
boolean optionsAvailable = visibleOptions.stream().anyMatch(Preference::isVisible);
if (!optionsAvailable) {
final Resources res = getResourcesForSubId();
String emptyViewText = res.getString(R.string.wifi_calling_off_explanation,
res.getString(R.string.wifi_calling_off_explanation_2));
pref.setSummary(emptyViewText);
}
pref.setVisible(!optionsAvailable);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean hasException = false;
if (preference == mButtonWfcMode) {
Log.d(TAG, "onPreferenceChange mButtonWfcMode " + newValue);
mButtonWfcMode.setValue((String) newValue);
final int buttonMode = Integer.valueOf((String) newValue);
int currentWfcMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
try {
currentWfcMode = mImsMmTelManager.getVoWiFiModeSetting();
} catch (IllegalArgumentException e) {
hasException = true;
Log.e(TAG, "onPreferenceChange: Exception", e);
}
if (buttonMode != currentWfcMode && !hasException) {
try {
mImsMmTelManager.setVoWiFiModeSetting(buttonMode);
mButtonWfcMode.setSummary(getWfcModeSummary(buttonMode));
mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), buttonMode);
if (mUseWfcHomeModeForRoaming) {
mImsMmTelManager.setVoWiFiRoamingModeSetting(buttonMode);
// mButtonWfcRoamingMode.setSummary is not needed; summary is selected value
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "onPreferenceChange: Exception", e);
}
}
} else if (preference == mButtonWfcRoamingMode) {
mButtonWfcRoamingMode.setValue((String) newValue);
final int buttonMode = Integer.valueOf((String) newValue);
int currentMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
try {
currentMode = mImsMmTelManager.getVoWiFiRoamingModeSetting();
} catch (IllegalArgumentException e) {
hasException = true;
Log.e(TAG, "updateWfcMode: Exception", e);
}
if (buttonMode != currentMode && !hasException) {
try {
mImsMmTelManager.setVoWiFiRoamingModeSetting(buttonMode);
// mButtonWfcRoamingMode.setSummary is not needed; summary is just selected
// value.
mMetricsFeatureProvider.action(getActivity(), getMetricsCategory(), buttonMode);
} catch (IllegalArgumentException e) {
Log.e(TAG, "onPreferenceChange: Exception", e);
}
}
}
return true;
}
private CharSequence getWfcModeSummary(int wfcMode) {
int resId = com.android.internal.R.string.wifi_calling_off_summary;
if (queryImsState(mSubId).isEnabledByUser()) {
switch (wfcMode) {
case ImsMmTelManager.WIFI_MODE_WIFI_ONLY:
resId = com.android.internal.R.string.wfc_mode_wifi_only_summary;
break;
case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED:
resId = com.android.internal.R.string.wfc_mode_cellular_preferred_summary;
break;
case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED:
resId = com.android.internal.R.string.wfc_mode_wifi_preferred_summary;
break;
default:
Log.e(TAG, "Unexpected WFC mode value: " + wfcMode);
}
}
return getResourcesForSubId().getString(resId);
}
@VisibleForTesting
Resources getResourcesForSubId() {
return SubscriptionManager.getResourcesForSubId(getContext(), mSubId);
}
@VisibleForTesting
void registerProvisioningChangedCallback() {
if (mProvisioningManager == null) {
return;
}
try {
mProvisioningManager.registerProvisioningChangedCallback(getContext().getMainExecutor(),
mProvisioningCallback);
} catch (Exception ex) {
Log.w(TAG, "onResume: Unable to register callback for provisioning changes.");
}
}
@VisibleForTesting
void unregisterProvisioningChangedCallback() {
if (mProvisioningManager == null) {
return;
}
mProvisioningManager.unregisterProvisioningChangedCallback(mProvisioningCallback);
}
/**
* Determine whether to override roaming Wi-Fi calling preference when device is connected to
* non-terrestrial network.
*
* @return {@code true} if phone is connected to non-terrestrial network and if
* {@link CarrierConfigManager#KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL} is true,
* {@code false} otherwise.
*/
private boolean overrideWfcRoamingModeWhileUsingNtn() {
if (!Flags.carrierEnabledSatelliteFlag()) {
return false;
}
TelephonyManager tm = getTelephonyManagerForSub(mSubId);
ServiceState serviceState = tm.getServiceState();
if (serviceState == null) {
return false;
}
if (!serviceState.isUsingNonTerrestrialNetwork()) {
return false;
}
return mOverrideWfcRoamingModeWhileUsingNtn;
}
}

View File

@@ -0,0 +1,606 @@
/*
* Copyright (C) 2018 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.calling;
import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import static com.android.settings.slices.CustomSliceRegistry.WIFI_CALLING_PREFERENCE_URI;
import static com.android.settings.slices.CustomSliceRegistry.WIFI_CALLING_URI;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.PersistableBundle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsMmTelManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;
import androidx.slice.builders.SliceAction;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.network.ims.WifiCallingQueryImsState;
import com.android.settings.slices.SliceBroadcastReceiver;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Helper class to control slices for wifi calling settings.
*/
public class WifiCallingSliceHelper {
private static final String TAG = "WifiCallingSliceHelper";
/**
* Settings slice path to wifi calling setting.
*/
public static final String PATH_WIFI_CALLING = "wifi_calling";
/**
* Settings slice path to wifi calling preference setting.
*/
public static final String PATH_WIFI_CALLING_PREFERENCE =
"wifi_calling_preference";
/**
* Action passed for changes to wifi calling slice (toggle).
*/
public static final String ACTION_WIFI_CALLING_CHANGED =
"com.android.settings.wifi.calling.action.WIFI_CALLING_CHANGED";
/**
* Action passed when user selects wifi only preference.
*/
public static final String ACTION_WIFI_CALLING_PREFERENCE_WIFI_ONLY =
"com.android.settings.slice.action.WIFI_CALLING_PREFERENCE_WIFI_ONLY";
/**
* Action passed when user selects wifi preferred preference.
*/
public static final String ACTION_WIFI_CALLING_PREFERENCE_WIFI_PREFERRED =
"com.android.settings.slice.action.WIFI_CALLING_PREFERENCE_WIFI_PREFERRED";
/**
* Action passed when user selects cellular preferred preference.
*/
public static final String ACTION_WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED =
"com.android.settings.slice.action.WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED";
/**
* Action for Wifi calling Settings activity which
* allows setting configuration for Wifi calling
* related settings
*/
public static final String ACTION_WIFI_CALLING_SETTINGS_ACTIVITY =
"android.settings.WIFI_CALLING_SETTINGS";
/**
* Timeout for querying wifi calling setting from ims manager.
*/
private static final int TIMEOUT_MILLIS = 2000;
private final Context mContext;
@VisibleForTesting
public WifiCallingSliceHelper(Context context) {
mContext = context;
}
/**
* Returns Slice object for wifi calling settings.
*
* If wifi calling is being turned on and if wifi calling activation is needed for the current
* carrier, this method will return Slice with instructions to go to Settings App.
*
* If wifi calling is not supported for the current carrier, this method will return slice with
* not supported message.
*
* If wifi calling setting can be changed, this method will return the slice to toggle wifi
* calling option with ACTION_WIFI_CALLING_CHANGED as endItem.
*/
public Slice createWifiCallingSlice(Uri sliceUri) {
final int subId = getDefaultVoiceSubId();
if (!queryImsState(subId).isReadyToWifiCalling()) {
Log.d(TAG, "Wifi calling is either not provisioned or not enabled by Platform");
return null;
}
final boolean isWifiCallingEnabled = isWifiCallingEnabled();
final Intent activationAppIntent =
getWifiCallingCarrierActivityIntent(subId);
// Send this actionable wifi calling slice to toggle the setting
// only when there is no need for wifi calling activation with the server
if (activationAppIntent != null && !isWifiCallingEnabled) {
Log.d(TAG, "Needs Activation");
// Activation needed for the next action of the user
// Give instructions to go to settings app
final Resources res = getResourcesForSubId(subId);
return getNonActionableWifiCallingSlice(
res.getText(R.string.wifi_calling_settings_title),
res.getText(R.string.wifi_calling_settings_activation_instructions),
sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY));
}
return getWifiCallingSlice(sliceUri, isWifiCallingEnabled, subId);
}
private boolean isWifiCallingEnabled() {
final WifiCallingQueryImsState queryState = queryImsState(getDefaultVoiceSubId());
return queryState.isEnabledByUser() && queryState.isAllowUserControl();
}
/**
* Builds a toggle slice where the intent takes you to the wifi calling page and the toggle
* enables/disables wifi calling.
*/
private Slice getWifiCallingSlice(Uri sliceUri, boolean isWifiCallingEnabled, int subId) {
final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
final Resources res = getResourcesForSubId(subId);
return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
.setAccentColor(Utils.getColorAccentDefaultColor(mContext))
.addRow(new RowBuilder()
.setTitle(res.getText(R.string.wifi_calling_settings_title))
.addEndItem(
SliceAction.createToggle(
getBroadcastIntent(ACTION_WIFI_CALLING_CHANGED,
isWifiCallingEnabled),
null /* actionTitle */, isWifiCallingEnabled))
.setPrimaryAction(SliceAction.createDeeplink(
getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY),
icon,
ListBuilder.ICON_IMAGE,
res.getText(R.string.wifi_calling_settings_title))))
.build();
}
/**
* Returns Slice object for wifi calling preference.
*
* If wifi calling is not turned on, this method will return a slice to turn on wifi calling.
*
* If wifi calling preference is not user editable, this method will return a slice to display
* appropriate message.
*
* If wifi calling preference can be changed, this method will return a slice with 3 or 4 rows:
* Header Row: current preference settings
* Row 1: wifi only option with ACTION_WIFI_CALLING_PREFERENCE_WIFI_ONLY, if wifi only option
* is editable
* Row 2: wifi preferred option with ACTION_WIFI_CALLING_PREFERENCE_WIFI_PREFERRED
* Row 3: cellular preferred option with ACTION_WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED
*/
public Slice createWifiCallingPreferenceSlice(Uri sliceUri) {
final int subId = getDefaultVoiceSubId();
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
Log.d(TAG, "Invalid Subscription Id");
return null;
}
final boolean isWifiCallingPrefEditable = isCarrierConfigManagerKeyEnabled(
CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL, subId, false);
final boolean isWifiOnlySupported = isCarrierConfigManagerKeyEnabled(
CarrierConfigManager.KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, subId, true);
if (!isWifiCallingPrefEditable) {
Log.d(TAG, "Wifi calling preference is not editable");
return null;
}
if (!queryImsState(subId).isReadyToWifiCalling()) {
Log.d(TAG, "Wifi calling is either not provisioned or not enabled by platform");
return null;
}
boolean isWifiCallingEnabled = false;
int wfcMode = -1;
try {
final ImsMmTelManager imsMmTelManager = getImsMmTelManager(subId);
isWifiCallingEnabled = isWifiCallingEnabled();
wfcMode = getWfcMode(imsMmTelManager);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Log.e(TAG, "Unable to get wifi calling preferred mode", e);
return null;
}
if (!isWifiCallingEnabled) {
// wifi calling is not enabled. Ask user to enable wifi calling
final Resources res = getResourcesForSubId(subId);
return getNonActionableWifiCallingSlice(
res.getText(R.string.wifi_calling_mode_title),
res.getText(R.string.wifi_calling_turn_on),
sliceUri, getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY));
}
// Return the slice to change wifi calling preference
return getWifiCallingPreferenceSlice(
isWifiOnlySupported, wfcMode, sliceUri, subId);
}
/**
* Returns actionable wifi calling preference slice.
*
* @param isWifiOnlySupported adds row for wifi only if this is true
* @param currentWfcPref current Preference {@link ImsConfig}
* @param sliceUri sliceUri
* @param subId subscription id
* @return Slice for actionable wifi calling preference settings
*/
private Slice getWifiCallingPreferenceSlice(boolean isWifiOnlySupported,
int currentWfcPref,
Uri sliceUri,
int subId) {
final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
final Resources res = getResourcesForSubId(subId);
// Top row shows information on current preference state
final ListBuilder listBuilder = new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
.setAccentColor(Utils.getColorAccentDefaultColor(mContext));
final ListBuilder.HeaderBuilder headerBuilder = new ListBuilder.HeaderBuilder()
.setTitle(res.getText(R.string.wifi_calling_mode_title))
.setPrimaryAction(SliceAction.createDeeplink(
getActivityIntent(ACTION_WIFI_CALLING_SETTINGS_ACTIVITY),
icon,
ListBuilder.ICON_IMAGE,
res.getText(R.string.wifi_calling_mode_title)));
if (!Utils.isSettingsIntelligence(mContext)) {
headerBuilder.setSubtitle(getWifiCallingPreferenceSummary(currentWfcPref, subId));
}
listBuilder.setHeader(headerBuilder);
if (isWifiOnlySupported) {
listBuilder.addRow(wifiPreferenceRowBuilder(listBuilder,
com.android.internal.R.string.wfc_mode_wifi_only_summary,
ACTION_WIFI_CALLING_PREFERENCE_WIFI_ONLY,
currentWfcPref == ImsMmTelManager.WIFI_MODE_WIFI_ONLY, subId));
}
listBuilder.addRow(wifiPreferenceRowBuilder(listBuilder,
com.android.internal.R.string.wfc_mode_wifi_preferred_summary,
ACTION_WIFI_CALLING_PREFERENCE_WIFI_PREFERRED,
currentWfcPref == ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED, subId));
listBuilder.addRow(wifiPreferenceRowBuilder(listBuilder,
com.android.internal.R.string.wfc_mode_cellular_preferred_summary,
ACTION_WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED,
currentWfcPref == ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED, subId));
return listBuilder.build();
}
/**
* Returns RowBuilder for a new row containing specific wifi calling preference.
*
* @param listBuilder ListBuilder that will be the parent for this RowBuilder
* @param preferenceTitleResId resource Id for the preference row title
* @param action action to be added for the row
* @param subId subscription id
* @return RowBuilder for the row
*/
private RowBuilder wifiPreferenceRowBuilder(ListBuilder listBuilder,
int preferenceTitleResId, String action, boolean checked, int subId) {
final IconCompat icon =
IconCompat.createWithResource(mContext, R.drawable.radio_button_check);
final Resources res = getResourcesForSubId(subId);
return new RowBuilder()
.setTitle(res.getText(preferenceTitleResId))
.setTitleItem(SliceAction.createToggle(getBroadcastIntent(action, checked),
icon, res.getText(preferenceTitleResId), checked));
}
/**
* Returns the String describing wifi calling preference mentioned in wfcMode
*
* @param wfcMode ImsConfig constant for the preference {@link ImsConfig}
* @return summary/name of the wifi calling preference
*/
private CharSequence getWifiCallingPreferenceSummary(int wfcMode, int subId) {
final Resources res = getResourcesForSubId(subId);
switch (wfcMode) {
case ImsMmTelManager.WIFI_MODE_WIFI_ONLY:
return res.getText(
com.android.internal.R.string.wfc_mode_wifi_only_summary);
case ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED:
return res.getText(
com.android.internal.R.string.wfc_mode_wifi_preferred_summary);
case ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED:
return res.getText(
com.android.internal.R.string.wfc_mode_cellular_preferred_summary);
default:
return null;
}
}
protected ImsMmTelManager getImsMmTelManager(int subId) {
return ImsMmTelManager.createForSubscriptionId(subId);
}
private int getWfcMode(ImsMmTelManager imsMmTelManager)
throws InterruptedException, ExecutionException, TimeoutException {
final FutureTask<Integer> wfcModeTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() {
int wfcMode = ImsMmTelManager.WIFI_MODE_UNKNOWN;
try {
wfcMode = imsMmTelManager.getVoWiFiModeSetting();
} catch (IllegalArgumentException e) {
Log.e(TAG, "getResourceIdForWfcMode: Exception", e);
}
return wfcMode;
}
});
final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(wfcModeTask);
return wfcModeTask.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
/**
* Handles wifi calling setting change from wifi calling slice and posts notification. Should be
* called when intent action is ACTION_WIFI_CALLING_CHANGED. Executed in @WorkerThread
*
* @param intent action performed
*/
public void handleWifiCallingChanged(Intent intent) {
final int subId = getDefaultVoiceSubId();
if (SubscriptionManager.isValidSubscriptionId(subId)
&& intent.hasExtra(EXTRA_TOGGLE_STATE)) {
final WifiCallingQueryImsState queryState = queryImsState(subId);
if (queryState.isWifiCallingProvisioned()) {
final boolean currentValue = isWifiCallingEnabled();
final boolean newValue = !(intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
currentValue));
final Intent activationAppIntent =
getWifiCallingCarrierActivityIntent(subId);
// 1. If activationApp is not null, users only can turn off WFC, or
// 2. Turn on/off directly if there is no activationApp.
if ((newValue != currentValue) && (activationAppIntent == null || !newValue)) {
// If either the action is to turn off wifi calling setting
// or there is no activation involved - Update the setting
final ImsMmTelManager imsMmTelManager = getImsMmTelManager(subId);
try {
imsMmTelManager.setVoWiFiSettingEnabled(newValue);
} catch (IllegalArgumentException e) {
Log.e(TAG, "handleWifiCallingChanged: Exception", e);
}
} else {
Log.w(TAG, "action not taken: subId " + subId
+ " from " + currentValue + " to " + newValue);
}
} else {
Log.w(TAG, "action not taken: subId " + subId + " needs provision");
}
} else {
Log.w(TAG, "action not taken: subId " + subId);
}
// notify change in slice in any case to get re-queried. This would result in displaying
// appropriate message with the updated setting.
mContext.getContentResolver().notifyChange(WIFI_CALLING_URI, null);
}
/**
* Handles wifi calling preference Setting change from wifi calling preference Slice and posts
* notification for the change. Should be called when intent action is one of the below
* ACTION_WIFI_CALLING_PREFERENCE_WIFI_ONLY
* ACTION_WIFI_CALLING_PREFERENCE_WIFI_PREFERRED
* ACTION_WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED
*
* @param intent intent
*/
public void handleWifiCallingPreferenceChanged(Intent intent) {
final int subId = getDefaultVoiceSubId();
final int errorValue = -1;
if (SubscriptionManager.isValidSubscriptionId(subId)) {
final boolean isWifiCallingPrefEditable = isCarrierConfigManagerKeyEnabled(
CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL, subId, false);
final boolean isWifiOnlySupported = isCarrierConfigManagerKeyEnabled(
CarrierConfigManager.KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, subId, true);
final WifiCallingQueryImsState queryState = queryImsState(subId);
if (isWifiCallingPrefEditable
&& queryState.isWifiCallingProvisioned()
&& queryState.isEnabledByUser()
&& queryState.isAllowUserControl()) {
// Change the preference only when wifi calling is enabled
// And when wifi calling preference is editable for the current carrier
final ImsMmTelManager imsMmTelManager = getImsMmTelManager(subId);
int currentValue = ImsMmTelManager.WIFI_MODE_UNKNOWN;
try {
currentValue = imsMmTelManager.getVoWiFiModeSetting();
} catch (IllegalArgumentException e) {
Log.e(TAG, "handleWifiCallingPreferenceChanged: Exception", e);
return;
}
int newValue = errorValue;
switch (intent.getAction()) {
case ACTION_WIFI_CALLING_PREFERENCE_WIFI_ONLY:
if (isWifiOnlySupported) {
// change to wifi_only when wifi_only is enabled.
newValue = ImsMmTelManager.WIFI_MODE_WIFI_ONLY;
}
break;
case ACTION_WIFI_CALLING_PREFERENCE_WIFI_PREFERRED:
newValue = ImsMmTelManager.WIFI_MODE_WIFI_PREFERRED;
break;
case ACTION_WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED:
newValue = ImsMmTelManager.WIFI_MODE_CELLULAR_PREFERRED;
break;
}
if (newValue != errorValue && newValue != currentValue) {
// Update the setting only when there is a valid update
try {
imsMmTelManager.setVoWiFiModeSetting(newValue);
} catch (IllegalArgumentException e) {
Log.e(TAG, "handleWifiCallingPreferenceChanged: Exception", e);
}
}
}
}
// notify change in slice in any case to get re-queried. This would result in displaying
// appropriate message.
mContext.getContentResolver().notifyChange(WIFI_CALLING_PREFERENCE_URI, null);
}
/**
* Returns Slice with the title and subtitle provided as arguments with wifi signal Icon.
*
* @param title Title of the slice
* @param subtitle Subtitle of the slice
* @param sliceUri slice uri
* @return Slice with title and subtitle
*/
private Slice getNonActionableWifiCallingSlice(CharSequence title, CharSequence subtitle,
Uri sliceUri, PendingIntent primaryActionIntent) {
final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.wifi_signal);
final RowBuilder rowBuilder = new RowBuilder()
.setTitle(title)
.setPrimaryAction(SliceAction.createDeeplink(
primaryActionIntent, icon, ListBuilder.SMALL_IMAGE,
title));
if (!Utils.isSettingsIntelligence(mContext)) {
rowBuilder.setSubtitle(subtitle);
}
return new ListBuilder(mContext, sliceUri, ListBuilder.INFINITY)
.setAccentColor(Utils.getColorAccentDefaultColor(mContext))
.addRow(rowBuilder)
.build();
}
/**
* Returns {@code true} when the key is enabled for the carrier, and {@code false} otherwise.
*/
protected boolean isCarrierConfigManagerKeyEnabled(String key, int subId,
boolean defaultValue) {
final CarrierConfigManager configManager = getCarrierConfigManager(mContext);
boolean ret = false;
if (configManager != null) {
final PersistableBundle bundle = configManager.getConfigForSubId(subId);
if (bundle != null) {
ret = bundle.getBoolean(key, defaultValue);
}
}
return ret;
}
protected CarrierConfigManager getCarrierConfigManager(Context mContext) {
return mContext.getSystemService(CarrierConfigManager.class);
}
/**
* Returns the current default voice subId obtained from SubscriptionManager
*/
protected int getDefaultVoiceSubId() {
return SubscriptionManager.getDefaultVoiceSubscriptionId();
}
/**
* Returns Intent of the activation app required to activate wifi calling or null if there is no
* need for activation.
*/
protected Intent getWifiCallingCarrierActivityIntent(int subId) {
final CarrierConfigManager configManager = getCarrierConfigManager(mContext);
if (configManager == null) {
return null;
}
final PersistableBundle bundle = configManager.getConfigForSubId(subId);
if (bundle == null) {
return null;
}
final String carrierApp = bundle.getString(
CarrierConfigManager.KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING);
if (TextUtils.isEmpty(carrierApp)) {
return null;
}
final ComponentName componentName = ComponentName.unflattenFromString(carrierApp);
if (componentName == null) {
return null;
}
final Intent intent = new Intent();
intent.setComponent(componentName);
return intent;
}
/**
* @return {@link PendingIntent} to the Settings home page.
*/
public static PendingIntent getSettingsIntent(Context context) {
final Intent intent = new Intent(Settings.ACTION_SETTINGS);
return PendingIntent.getActivity(context, 0 /* requestCode */, intent,
PendingIntent.FLAG_IMMUTABLE);
}
/**
* Create PendingIntent for Slice.
* Note: SliceAction#createDeeplink() didn't support toggle status so far,
* therefore, embedding toggle status within PendingIntent.
*
* @param action Slice action
* @param isChecked Status when Slice created.
* @return PendingIntent
*/
private PendingIntent getBroadcastIntent(String action, boolean isChecked) {
final Intent intent = new Intent(action);
intent.setClass(mContext, SliceBroadcastReceiver.class);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(EXTRA_TOGGLE_STATE, isChecked);
return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
/**
* Returns PendingIntent to start activity specified by action
*/
private PendingIntent getActivityIntent(String action) {
final Intent intent = new Intent(action);
intent.setPackage(SETTINGS_PACKAGE_NAME);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent,
PendingIntent.FLAG_IMMUTABLE);
}
private Resources getResourcesForSubId(int subId) {
return SubscriptionManager.getResourcesForSubId(mContext, subId);
}
@VisibleForTesting
WifiCallingQueryImsState queryImsState(int subId) {
return new WifiCallingQueryImsState(mContext, subId);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2017 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.calling;
import android.content.Context;
import android.telephony.SubscriptionManager;
import com.android.settings.SettingsActivity;
import com.android.settings.network.ims.WifiCallingQueryImsState;
public class WifiCallingSuggestionActivity extends SettingsActivity {
public static boolean isSuggestionComplete(Context context) {
final WifiCallingQueryImsState queryState =
new WifiCallingQueryImsState(context,
SubscriptionManager.getDefaultVoiceSubscriptionId());
return (!queryState.isWifiCallingProvisioned())
|| (queryState.isEnabledByUser() && queryState.isAllowUserControl());
}
}

View File

@@ -0,0 +1,454 @@
/*
* 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.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.
*
* <p>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<WifiDialog2.WifiDialog2Listener> mWifiDialogListeners = new ArrayList<>();
@VisibleForTesting
List<AbstractPreferenceController> 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);
use(WifiPrivacyPreferenceController.class)
.setWifiEntryKey(getArguments().getString(KEY_CHOSEN_WIFIENTRY_KEY));
}
@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<AbstractPreferenceController> 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));
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2023 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 android.app.Application;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.settings.overlay.FeatureFactory;
import com.android.wifitrackerlib.HotspotNetworkEntry;
import com.android.wifitrackerlib.WifiEntry;
import org.jetbrains.annotations.NotNull;
/**
* Wi-Fi Network Details ViewModel
*/
public class WifiNetworkDetailsViewModel extends AndroidViewModel {
private static final String TAG = "WifiNetworkDetailsViewModel";
@VisibleForTesting
MutableLiveData<HotspotNetworkData> mHotspotNetworkData = new MutableLiveData<>();
public WifiNetworkDetailsViewModel(@NotNull Application application) {
super(application);
}
/** Sets the {@link WifiEntry} class */
public void setWifiEntry(WifiEntry wifiEntry) {
if (!(wifiEntry instanceof HotspotNetworkEntry)) {
log("post HotspotNetworkData:null");
mHotspotNetworkData.postValue(null);
return;
}
HotspotNetworkEntry entry = (HotspotNetworkEntry) wifiEntry;
HotspotNetworkData data = new HotspotNetworkData(
entry.getNetworkType(),
entry.getUpstreamConnectionStrength(),
entry.getBatteryPercentage(),
entry.isBatteryCharging());
log("post HotspotNetworkData:" + data);
mHotspotNetworkData.postValue(data);
}
/** Gets the {@link HotspotNetworkData} LiveData */
public LiveData<HotspotNetworkData> getHotspotNetworkData() {
return mHotspotNetworkData;
}
/** The {@link HotspotNetworkData} class */
static class HotspotNetworkData {
private int mNetworkType;
private int mUpstreamConnectionStrength;
private int mBatteryPercentage;
private boolean mIsBatteryCharging;
HotspotNetworkData(int networkType, int upstreamConnectionStrength,
int batteryPercentage,
boolean isBatteryCharging) {
mNetworkType = networkType;
mUpstreamConnectionStrength = upstreamConnectionStrength;
mBatteryPercentage = batteryPercentage;
mIsBatteryCharging = isBatteryCharging;
}
/** Gets the network type */
public int getNetworkType() {
return mNetworkType;
}
/** Gets the upstream connection strength */
public int getUpstreamConnectionStrength() {
return mUpstreamConnectionStrength;
}
/** Gets the battery percentage */
public int getBatteryPercentage() {
return mBatteryPercentage;
}
/** Returns true if the battery is charging */
public boolean isBatteryCharging() {
return mIsBatteryCharging;
}
@Override
public String toString() {
return getClass().getSimpleName()
+ ":{networkType:" + mNetworkType
+ ", upstreamConnectionStrength:" + mUpstreamConnectionStrength
+ ", batteryPercentage:" + mBatteryPercentage
+ ", isBatteryCharging:" + mIsBatteryCharging
+ " }";
}
}
private void log(String msg) {
FeatureFactory.getFeatureFactory().getWifiFeatureProvider().verboseLog(TAG, msg);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2019 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.details2;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.util.Log;
import androidx.preference.Preference;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.wifi.dpp.WifiDppUtils;
import com.android.wifitrackerlib.WifiEntry;
/**
* {@link BasePreferenceController} that launches Wi-Fi Easy Connect configurator flow
*/
public class AddDevicePreferenceController2 extends BasePreferenceController {
private static final String TAG = "AddDevicePreferenceController2";
private static final String KEY_ADD_DEVICE = "add_device_to_network";
private WifiEntry mWifiEntry;
private WifiManager mWifiManager;
public AddDevicePreferenceController2(Context context) {
super(context, KEY_ADD_DEVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
public void setWifiEntry(WifiEntry wifiEntry) {
mWifiEntry = wifiEntry;
}
@Override
public int getAvailabilityStatus() {
return mWifiEntry.canEasyConnect() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY_ADD_DEVICE.equals(preference.getKey())) {
WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorQrCodeScanner());
return true; /* click is handled */
}
return false; /* click is not handled */
}
private void launchWifiDppConfiguratorQrCodeScanner() {
final Intent intent = WifiDppUtils.getConfiguratorQrCodeScannerIntentOrNull(mContext,
mWifiManager, mWifiEntry);
if (intent == null) {
Log.e(TAG, "Launch Wi-Fi QR code scanner with a wrong Wi-Fi network!");
} else {
mContext.startActivity(intent);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2020 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.details2;
import android.content.Context;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.wifitrackerlib.WifiEntry;
/**
* {@link TogglePreferenceController} that controls whether the Wi-Fi Auto-connect feature should be
* enabled.
*/
public class WifiAutoConnectPreferenceController2 extends TogglePreferenceController {
private static final String KEY_AUTO_CONNECT = "auto_connect";
private WifiEntry mWifiEntry;
public WifiAutoConnectPreferenceController2(Context context) {
super(context, KEY_AUTO_CONNECT);
}
public void setWifiEntry(WifiEntry wifiEntry) {
mWifiEntry = wifiEntry;
}
@Override
public int getAvailabilityStatus() {
return mWifiEntry.canSetAutoJoinEnabled() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public boolean isChecked() {
return mWifiEntry.isAutoJoinEnabled();
}
@Override
public boolean setChecked(boolean isChecked) {
mWifiEntry.setAutoJoinEnabled(isChecked);
return true;
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_network;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2019 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.details2;
import android.app.backup.BackupManager;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.android.settings.core.BasePreferenceController;
import com.android.wifitrackerlib.WifiEntry;
/**
* A controller that controls whether the Wi-Fi network is metered or not.
*/
public class WifiMeteredPreferenceController2 extends BasePreferenceController implements
Preference.OnPreferenceChangeListener {
private static final String KEY_WIFI_METERED = "metered";
private final WifiEntry mWifiEntry;
public WifiMeteredPreferenceController2(Context context, WifiEntry wifiEntry) {
super(context, KEY_WIFI_METERED);
mWifiEntry = wifiEntry;
}
@Override
public void updateState(Preference preference) {
final ListPreference listPreference = (ListPreference) preference;
final int meteredOverride = getMeteredOverride();
preference.setSelectable(mWifiEntry.canSetMeteredChoice());
listPreference.setValue(Integer.toString(meteredOverride));
updateSummary(listPreference, meteredOverride);
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
if (mWifiEntry.isSaved() || mWifiEntry.isSubscription()) {
mWifiEntry.setMeteredChoice(Integer.parseInt((String) newValue));
}
// Stage the backup of the SettingsProvider package which backs this up
BackupManager.dataChanged("com.android.providers.settings");
updateSummary((ListPreference) preference, getMeteredOverride());
return true;
}
@VisibleForTesting
int getMeteredOverride() {
if (mWifiEntry.isSaved() || mWifiEntry.isSubscription()) {
// Wrap the meteredOverride since robolectric cannot recognize it
return mWifiEntry.getMeteredChoice();
}
return WifiEntry.METERED_CHOICE_AUTO;
}
private void updateSummary(ListPreference preference, int meteredOverride) {
preference.setSummary(preference.getEntries()[meteredOverride]);
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright (C) 2024 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.details2
import android.content.Context
import android.net.wifi.WifiConfiguration
import android.net.wifi.WifiManager
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 androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settings.R
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.ListPreferenceModel
import com.android.settingslib.spa.widget.preference.ListPreferenceOption
import com.android.settingslib.spa.widget.preference.RadioPreferences
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.CategoryTitle
import com.android.wifitrackerlib.WifiEntry
import java.time.Clock
import java.time.ZoneOffset
const val WIFI_ENTRY_KEY = "wifiEntryKey"
object WifiPrivacyPageProvider : SettingsPageProvider {
override val name = "WifiPrivacy"
const val TAG = "WifiPrivacyPageProvider"
override val parameter = listOf(
navArgument(WIFI_ENTRY_KEY) { type = NavType.StringType },
)
@Composable
override fun Page(arguments: Bundle?) {
val wifiEntryKey = arguments!!.getString(WIFI_ENTRY_KEY)
if (wifiEntryKey != null) {
val context = LocalContext.current
val lifecycle = LocalLifecycleOwner.current.lifecycle
val wifiEntry = remember {
getWifiEntry(context, wifiEntryKey, lifecycle)
}
WifiPrivacyPage(wifiEntry)
}
}
fun getRoute(
wifiEntryKey: String,
): String = "${name}/$wifiEntryKey"
}
@Composable
fun WifiPrivacyPage(wifiEntry: WifiEntry) {
val isSelectable: Boolean = wifiEntry.canSetPrivacy()
RegularScaffold(
title = stringResource(id = R.string.wifi_privacy_settings)
) {
Column {
val title = stringResource(id = R.string.wifi_privacy_mac_settings)
val wifiPrivacyEntries = stringArrayResource(R.array.wifi_privacy_entries)
val wifiPrivacyValues = stringArrayResource(R.array.wifi_privacy_values)
val textsSelectedId = rememberSaveable { mutableIntStateOf(wifiEntry.privacy) }
val dataList = remember {
wifiPrivacyEntries.mapIndexed { index, text ->
ListPreferenceOption(id = wifiPrivacyValues[index].toInt(), text = text)
}
}
RadioPreferences(remember {
object : ListPreferenceModel {
override val title = title
override val options = dataList
override val selectedId = textsSelectedId
override val onIdSelected: (Int) -> Unit = {
textsSelectedId.intValue = it
onSelectedChange(wifiEntry, it)
}
override val enabled = { isSelectable }
}
})
wifiEntry.wifiConfiguration?.let {
DeviceNameSwitchPreference(it)
}
}
}
}
@Composable
fun DeviceNameSwitchPreference(wifiConfiguration: WifiConfiguration){
Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
CategoryTitle(title = stringResource(R.string.wifi_privacy_device_name_settings))
Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
var checked by remember {
mutableStateOf(wifiConfiguration.isSendDhcpHostnameEnabled)
}
val context = LocalContext.current
val wifiManager = context.getSystemService(WifiManager::class.java)!!
SwitchPreference(object : SwitchPreferenceModel {
override val title =
context.resources.getString(
R.string.wifi_privacy_send_device_name_toggle_title
)
override val summary =
{
context.resources.getString(
R.string.wifi_privacy_send_device_name_toggle_summary
)
}
override val checked = { checked }
override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
wifiConfiguration.isSendDhcpHostnameEnabled = newChecked
wifiManager.save(wifiConfiguration, null /* listener */)
checked = newChecked
}
})
}
fun onSelectedChange(wifiEntry: WifiEntry, privacy: Int) {
if (wifiEntry.privacy == privacy) {
// Prevent disconnection + reconnection if settings not changed.
return
}
wifiEntry.setPrivacy(privacy)
// To activate changing, we need to reconnect network. WiFi will auto connect to
// current network after disconnect(). Only needed when this is connected network.
// To activate changing, we need to reconnect network. WiFi will auto connect to
// current network after disconnect(). Only needed when this is connected network.
if (wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
wifiEntry.disconnect(null /* callback */)
wifiEntry.connect(null /* callback */)
}
}
fun getWifiEntry(
context: Context,
wifiEntryKey: String,
liftCycle: androidx.lifecycle.Lifecycle
): WifiEntry {
// Max age of tracked WifiEntries
val MAX_SCAN_AGE_MILLIS: Long = 15000
// Interval between initiating SavedNetworkTracker scans
val SCAN_INTERVAL_MILLIS: Long = 10000
val mWorkerThread = HandlerThread(
WifiPrivacyPageProvider.TAG,
Process.THREAD_PRIORITY_BACKGROUND
)
mWorkerThread.start()
val elapsedRealtimeClock: Clock = object : SimpleClock(ZoneOffset.UTC) {
override fun millis(): Long {
return android.os.SystemClock.elapsedRealtime()
}
}
val mNetworkDetailsTracker = featureFactory
.wifiTrackerLibProvider
.createNetworkDetailsTracker(
liftCycle,
context,
Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
wifiEntryKey
)
return mNetworkDetailsTracker.wifiEntry
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2024 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.details2
import android.content.Context
import android.net.wifi.WifiManager
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import com.android.settings.R
import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.wifi.flags.Flags
class WifiPrivacyPreferenceController(context: Context, preferenceKey: String) :
ComposePreferenceController(context, preferenceKey) {
private var wifiEntryKey: String? = null
var wifiManager = context.getSystemService(WifiManager::class.java)!!
fun setWifiEntryKey(key: String?) {
wifiEntryKey = key
}
override fun getAvailabilityStatus() =
if (Flags.androidVWifiApi() && wifiManager.isConnectedMacRandomizationSupported) AVAILABLE
else CONDITIONALLY_UNAVAILABLE
@Composable
override fun Content() {
Preference(object : PreferenceModel {
override val title = stringResource(R.string.wifi_privacy_settings)
override val icon = @Composable {
Icon(
ImageVector.vectorResource(R.drawable.ic_wifi_privacy_24dp),
contentDescription = null
)
}
override val onClick: () -> Unit =
{
wifiEntryKey?.let {
mContext.startSpaActivity(WifiPrivacyPageProvider.getRoute(it))
}
}
})
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2019 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.details2;
import android.content.Context;
import android.net.wifi.WifiManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.wifi.flags.Flags;
import com.android.wifitrackerlib.WifiEntry;
/**
* A controller that controls whether the Wi-Fi network is mac randomized or not.
*/
public class WifiPrivacyPreferenceController2 extends BasePreferenceController implements
Preference.OnPreferenceChangeListener {
private static final String KEY_WIFI_PRIVACY = "privacy";
private final WifiManager mWifiManager;
private WifiEntry mWifiEntry;
public WifiPrivacyPreferenceController2(Context context) {
super(context, KEY_WIFI_PRIVACY);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
public void setWifiEntry(WifiEntry wifiEntry) {
mWifiEntry = wifiEntry;
}
@Override
public int getAvailabilityStatus() {
return (!Flags.androidVWifiApi() && mWifiManager.isConnectedMacRandomizationSupported())
? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public void updateState(Preference preference) {
final ListPreference listPreference = (ListPreference) preference;
final int randomizationLevel = getRandomizationValue();
final boolean isSelectable = mWifiEntry.canSetPrivacy();
preference.setSelectable(isSelectable);
listPreference.setValue(Integer.toString(randomizationLevel));
updateSummary(listPreference, randomizationLevel);
// If the preference cannot be selectable, display a temporary network in the summary.
if (!isSelectable) {
listPreference.setSummary(R.string.wifi_privacy_settings_ephemeral_summary);
}
}
@Override
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
final int privacy = Integer.parseInt((String) newValue);
if (mWifiEntry.getPrivacy() == privacy) {
// Prevent disconnection + reconnection if settings not changed.
return true;
}
mWifiEntry.setPrivacy(privacy);
// To activate changing, we need to reconnect network. WiFi will auto connect to
// current network after disconnect(). Only needed when this is connected network.
if (mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
mWifiEntry.disconnect(null /* callback */);
mWifiEntry.connect(null /* callback */);
}
updateSummary((ListPreference) preference, privacy);
return true;
}
@VisibleForTesting
int getRandomizationValue() {
return mWifiEntry.getPrivacy();
}
private static final int PREF_RANDOMIZATION_PERSISTENT = 0;
private static final int PREF_RANDOMIZATION_NONE = 1;
/**
* Returns preference index value.
*
* @param macRandomized is mac randomized value
* @return index value of preference
*/
public static int translateMacRandomizedValueToPrefValue(int macRandomized) {
return (macRandomized == WifiEntry.PRIVACY_RANDOMIZED_MAC)
? PREF_RANDOMIZATION_PERSISTENT : PREF_RANDOMIZATION_NONE;
}
/**
* Returns mac randomized value.
*
* @param prefMacRandomized is preference index value
* @return mac randomized value
*/
public static int translatePrefValueToMacRandomizedValue(int prefMacRandomized) {
return (prefMacRandomized == PREF_RANDOMIZATION_PERSISTENT)
? WifiEntry.PRIVACY_RANDOMIZED_MAC : WifiEntry.PRIVACY_DEVICE_MAC;
}
private void updateSummary(ListPreference preference, int macRandomized) {
// Translates value here to set RANDOMIZATION_PERSISTENT as first item in UI for better UX.
final int prefMacRandomized = translateMacRandomizedValueToPrefValue(macRandomized);
preference.setSummary(preference.getEntries()[prefMacRandomized]);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 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.details2;
import android.content.Context;
import android.text.TextUtils;
import com.android.settings.core.BasePreferenceController;
import com.android.wifitrackerlib.WifiEntry;
/**
* {@link BasePreferenceController} that display the second summary. If users click the preference,
* @link ClickableSpan#onClick} of the first {@link ClickableSpan} in the summary will be called.
*/
public class WifiSecondSummaryController2 extends BasePreferenceController {
private static final String KEY_WIFI_SECOND_SUMMARY = "second_summary";
private CharSequence mSecondSummary;
public WifiSecondSummaryController2(Context context) {
super(context, KEY_WIFI_SECOND_SUMMARY);
}
public void setWifiEntry(WifiEntry wifiEntry) {
mSecondSummary = wifiEntry.getSecondSummary();
}
@Override
public int getAvailabilityStatus() {
return TextUtils.isEmpty(mSecondSummary) ? CONDITIONALLY_UNAVAILABLE : AVAILABLE;
}
@Override
public CharSequence getSummary() {
return mSecondSummary;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2020 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.details2;
import android.content.Context;
import androidx.preference.Preference;
import com.android.settings.core.BasePreferenceController;
import com.android.wifitrackerlib.WifiEntry;
/**
* {@link BasePreferenceController} that controls show the subscription detail preference item.
* or not
*/
public class WifiSubscriptionDetailPreferenceController2 extends BasePreferenceController {
private static final String KEY_WIFI_SUBSCRIPTION_DETAIL = "subscription_detail";
private WifiEntry mWifiEntry;
public WifiSubscriptionDetailPreferenceController2(Context context) {
super(context, KEY_WIFI_SUBSCRIPTION_DETAIL);
}
public void setWifiEntry(WifiEntry wifiEntry) {
mWifiEntry = wifiEntry;
}
@Override
public int getAvailabilityStatus() {
return mWifiEntry.canManageSubscription() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY_WIFI_SUBSCRIPTION_DETAIL.equals(preference.getKey())) {
mWifiEntry.manageSubscription();
return true; /* click is handled */
}
return false; /* click is not handled */
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2020 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.dpp;
import android.content.Context;
import android.text.TextUtils;
/**
* Extension of WifiQrCode to support ADB QR code format.
* It will be based on the ZXing format:
*
* WIFI:T:ADB;S:myname;P:mypassword;;
*/
public class AdbQrCode extends WifiQrCode {
static final String SECURITY_ADB = "ADB";
private WifiNetworkConfig mAdbConfig;
public AdbQrCode(String qrCode) throws IllegalArgumentException {
super(qrCode);
// Only accept the zxing format.
if (!WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG.equals(getScheme())) {
throw new IllegalArgumentException("DPP format not supported for ADB QR code");
}
mAdbConfig = getWifiNetworkConfig();
if (!SECURITY_ADB.equals(mAdbConfig.getSecurity())) {
throw new IllegalArgumentException("Invalid security type");
}
if (TextUtils.isEmpty(mAdbConfig.getSsid())) {
throw new IllegalArgumentException("Empty service name");
}
if (TextUtils.isEmpty(mAdbConfig.getPreSharedKey())) {
throw new IllegalArgumentException("Empty password");
}
}
public WifiNetworkConfig getAdbNetworkConfig() {
return mAdbConfig;
}
/**
* Triggers a vibration to notify of a valid QR code.
*
* @param context The context to use
*/
public static void triggerVibrationForQrCodeRecognition(Context context) {
WifiDppUtils.triggerVibrationForQrCodeRecognition(context);
}
}

View File

@@ -0,0 +1,443 @@
/*
* Copyright (C) 2018 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.dpp;
import static android.provider.Settings.EXTRA_EASY_CONNECT_ATTEMPTED_SSID;
import static android.provider.Settings.EXTRA_EASY_CONNECT_BAND_LIST;
import static android.provider.Settings.EXTRA_EASY_CONNECT_CHANNEL_LIST;
import static android.provider.Settings.EXTRA_EASY_CONNECT_ERROR_CODE;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.EasyConnectStatusCallback;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import androidx.lifecycle.ViewModelProvider;
import com.android.settings.R;
import com.google.android.setupcompat.template.FooterButton;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* After getting Wi-Fi network information and(or) QR code, this fragment config a device to connect
* to the Wi-Fi network.
*/
public class WifiDppAddDeviceFragment extends WifiDppQrCodeBaseFragment {
private static final String TAG = "WifiDppAddDeviceFragment";
private ImageView mWifiApPictureView;
private Button mChooseDifferentNetwork;
private int mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE;
// Key for Bundle usage
private static final String KEY_LATEST_STATUS_CODE = "key_latest_status_code";
private class EasyConnectConfiguratorStatusCallback extends EasyConnectStatusCallback {
@Override
public void onEnrolleeSuccess(int newNetworkId) {
// Do nothing
}
@Override
public void onConfiguratorSuccess(int code) {
showSuccessUi(/* isConfigurationChange */ false);
}
@Override
public void onFailure(int code, String ssid, SparseArray<int[]> channelListArray,
int[] operatingClassArray) {
Log.d(TAG, "EasyConnectConfiguratorStatusCallback.onFailure: " + code);
if (!TextUtils.isEmpty(ssid)) {
Log.d(TAG, "Tried SSID: " + ssid);
}
if (channelListArray.size() != 0) {
Log.d(TAG, "Tried channels: " + channelListArray);
}
if (operatingClassArray != null && operatingClassArray.length > 0) {
StringBuilder sb = new StringBuilder("Supported bands: ");
for (int i = 0; i < operatingClassArray.length; i++) {
sb.append(operatingClassArray[i] + " ");
}
Log.d(TAG, sb.toString());
}
showErrorUi(code, getResultIntent(code, ssid, channelListArray,
operatingClassArray), /* isConfigurationChange */ false);
}
@Override
public void onProgress(int code) {
// Do nothing
}
}
private void showSuccessUi(boolean isConfigurationChange) {
setHeaderIconImageResource(R.drawable.ic_devices_check_circle_green_32dp);
setHeaderTitle(R.string.wifi_dpp_wifi_shared_with_device);
setProgressBarShown(isEasyConnectHandshaking());
mSummary.setVisibility(View.INVISIBLE);
mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_success);
mChooseDifferentNetwork.setVisibility(View.INVISIBLE);
mLeftButton.setText(getContext(), R.string.wifi_dpp_add_another_device);
mLeftButton.setOnClickListener(v -> getFragmentManager().popBackStack());
mRightButton.setText(getContext(), R.string.done);
mRightButton.setOnClickListener(v -> {
final Activity activity = getActivity();
activity.setResult(Activity.RESULT_OK);
activity.finish();
});
mRightButton.setVisibility(View.VISIBLE);
if (!isConfigurationChange) {
mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS;
}
}
private Intent getResultIntent(int code, String ssid, SparseArray<int[]> channelListArray,
int[] operatingClassArray) {
Intent intent = new Intent();
intent.putExtra(EXTRA_EASY_CONNECT_ERROR_CODE, code);
if (!TextUtils.isEmpty(ssid)) {
intent.putExtra(EXTRA_EASY_CONNECT_ATTEMPTED_SSID, ssid);
}
if (channelListArray != null && channelListArray.size() != 0) {
int key;
int index = 0;
JSONObject formattedChannelList = new JSONObject();
// Build a JSON array of operating classes, with an array of channels for each
// operating class.
do {
try {
key = channelListArray.keyAt(index);
} catch (java.lang.ArrayIndexOutOfBoundsException e) {
break;
}
JSONArray channelsInClassArray = new JSONArray();
int[] output = channelListArray.get(key);
for (int i = 0; i < output.length; i++) {
channelsInClassArray.put(output[i]);
}
try {
formattedChannelList.put(Integer.toString(key), channelsInClassArray);
} catch (org.json.JSONException e) {
formattedChannelList = new JSONObject();
break;
}
index++;
} while (true);
intent.putExtra(EXTRA_EASY_CONNECT_CHANNEL_LIST,
formattedChannelList.toString());
}
if (operatingClassArray != null && operatingClassArray.length != 0) {
intent.putExtra(EXTRA_EASY_CONNECT_BAND_LIST, operatingClassArray);
}
return intent;
}
private void showErrorUi(int code, Intent resultIntent, boolean isConfigurationChange) {
CharSequence summaryCharSequence;
switch (code) {
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI:
summaryCharSequence = getText(R.string.wifi_dpp_qr_code_is_not_valid_format);
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION:
summaryCharSequence = getText(
R.string.wifi_dpp_failure_authentication_or_configuration);
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE:
summaryCharSequence = getText(R.string.wifi_dpp_failure_not_compatible);
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION:
summaryCharSequence = getText(
R.string.wifi_dpp_failure_authentication_or_configuration);
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY:
if (isConfigurationChange) {
return;
}
if (code == mLatestStatusCode) {
throw (new IllegalStateException("Tried restarting EasyConnectSession but still"
+ "receiving EASY_CONNECT_EVENT_FAILURE_BUSY"));
}
mLatestStatusCode = code;
final WifiManager wifiManager =
getContext().getSystemService(WifiManager.class);
wifiManager.stopEasyConnectSession();
startWifiDppConfiguratorInitiator();
return;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT:
summaryCharSequence = getText(R.string.wifi_dpp_failure_timeout);
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC:
summaryCharSequence = getText(R.string.wifi_dpp_failure_generic);
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED:
summaryCharSequence = getString(
R.string.wifi_dpp_failure_not_supported, getSsid());
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK:
throw (new IllegalStateException("Wi-Fi DPP configurator used a non-PSK/non-SAE"
+ "network to handshake"));
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK:
summaryCharSequence = getText(R.string.wifi_dpp_failure_cannot_find_network);
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION:
summaryCharSequence = getText(R.string.wifi_dpp_failure_enrollee_authentication);
break;
case EasyConnectStatusCallback
.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION:
summaryCharSequence =
getText(R.string.wifi_dpp_failure_enrollee_rejected_configuration);
break;
default:
throw (new IllegalStateException("Unexpected Wi-Fi DPP error"));
}
setHeaderTitle(R.string.wifi_dpp_could_not_add_device);
mSummary.setText(summaryCharSequence);
mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_error);
mChooseDifferentNetwork.setVisibility(View.INVISIBLE);
FooterButton finishingButton = mLeftButton;
if (hasRetryButton(code)) {
mRightButton.setText(getContext(), R.string.retry);
} else {
mRightButton.setText(getContext(), R.string.done);
finishingButton = mRightButton;
mLeftButton.setVisibility(View.INVISIBLE);
}
finishingButton.setOnClickListener(v -> {
getActivity().setResult(Activity.RESULT_CANCELED, resultIntent);
getActivity().finish();
});
if (isEasyConnectHandshaking()) {
mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device);
}
setProgressBarShown(isEasyConnectHandshaking());
mRightButton.setVisibility(isEasyConnectHandshaking() ? View.INVISIBLE : View.VISIBLE);
if (!isConfigurationChange) {
mLatestStatusCode = code;
}
}
private boolean hasRetryButton(int code) {
switch (code) {
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI:
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE:
return false;
default:
break;
}
return true;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mLatestStatusCode = savedInstanceState.getInt(KEY_LATEST_STATUS_CODE);
}
final WifiDppInitiatorViewModel model =
new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class);
model.getStatusCode().observe(this, statusCode -> {
// After configuration change, observe callback will be triggered,
// do nothing for this case if a handshake does not end
if (model.isWifiDppHandshaking()) {
return;
}
int code = statusCode.intValue();
if (code == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) {
new EasyConnectConfiguratorStatusCallback().onConfiguratorSuccess(code);
} else {
new EasyConnectConfiguratorStatusCallback().onFailure(code, model.getTriedSsid(),
model.getTriedChannels(), model.getBandArray());
}
});
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.wifi_dpp_add_device_fragment, container,
/* attachToRoot */ false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setHeaderIconImageResource(R.drawable.ic_devices_other_32dp);
final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity())
.getWifiDppQrCode();
final String information = wifiQrCode.getInformation();
if (TextUtils.isEmpty(information)) {
setHeaderTitle(R.string.wifi_dpp_device_found);
} else {
setHeaderTitle(information);
}
updateSummary();
mWifiApPictureView = view.findViewById(R.id.wifi_ap_picture_view);
mChooseDifferentNetwork = view.findViewById(R.id.choose_different_network);
mChooseDifferentNetwork.setOnClickListener(v ->
mClickChooseDifferentNetworkListener.onClickChooseDifferentNetwork()
);
mLeftButton.setText(getContext(), R.string.cancel);
mLeftButton.setOnClickListener(v -> getActivity().finish());
mRightButton.setText(getContext(), R.string.wifi_dpp_share_wifi);
mRightButton.setOnClickListener(v -> {
setProgressBarShown(true);
mRightButton.setVisibility(View.INVISIBLE);
startWifiDppConfiguratorInitiator();
updateSummary();
});
if (savedInstanceState != null) {
if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) {
showSuccessUi(/* isConfigurationChange */ true);
} else if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE) {
setProgressBarShown(isEasyConnectHandshaking());
mRightButton.setVisibility(isEasyConnectHandshaking() ?
View.INVISIBLE : View.VISIBLE);
} else {
showErrorUi(mLatestStatusCode, /* reslutIntent */ null, /* isConfigurationChange */
true);
}
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(KEY_LATEST_STATUS_CODE, mLatestStatusCode);
super.onSaveInstanceState(outState);
}
private String getSsid() {
final WifiNetworkConfig wifiNetworkConfig = ((WifiDppConfiguratorActivity) getActivity())
.getWifiNetworkConfig();
if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) {
throw new IllegalStateException("Invalid Wi-Fi network for configuring");
}
return wifiNetworkConfig.getSsid();
}
private void startWifiDppConfiguratorInitiator() {
final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity())
.getWifiDppQrCode();
final String qrCode = wifiQrCode.getQrCode();
final int networkId =
((WifiDppConfiguratorActivity) getActivity()).getWifiNetworkConfig().getNetworkId();
final WifiDppInitiatorViewModel model =
new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class);
model.startEasyConnectAsConfiguratorInitiator(qrCode, networkId);
}
// Container Activity must implement this interface
public interface OnClickChooseDifferentNetworkListener {
void onClickChooseDifferentNetwork();
}
private OnClickChooseDifferentNetworkListener mClickChooseDifferentNetworkListener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
mClickChooseDifferentNetworkListener = (OnClickChooseDifferentNetworkListener) context;
}
@Override
public void onDetach() {
mClickChooseDifferentNetworkListener = null;
super.onDetach();
}
// Check is Easy Connect handshaking or not
private boolean isEasyConnectHandshaking() {
final WifiDppInitiatorViewModel model =
new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class);
return model.isWifiDppHandshaking();
}
private void updateSummary() {
if (isEasyConnectHandshaking()) {
mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device);
} else {
mSummary.setText(getString(R.string.wifi_dpp_add_device_to_wifi, getSsid()));
}
}
@Override
protected boolean isFooterAvailable() {
return true;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2019 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.dpp;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import androidx.fragment.app.FragmentManager;
import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
import com.android.settings.core.InstrumentedActivity;
import com.google.android.setupdesign.util.ThemeHelper;
public abstract class WifiDppBaseActivity extends InstrumentedActivity {
protected FragmentManager mFragmentManager;
protected abstract void handleIntent(Intent intent);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
applyTheme();
setContentView(R.layout.wifi_dpp_activity);
mFragmentManager = getSupportFragmentManager();
if (savedInstanceState == null) {
handleIntent(getIntent());
}
}
@Override
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
super.onApplyThemeResource(theme, resid, first);
}
private void applyTheme() {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
setTheme(R.style.SettingsPreferenceTheme_SetupWizard);
ThemeHelper.trySetDynamicColor(this);
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2018 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.dpp;
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.android.settings.R;
/**
* After a camera APP scanned a Wi-Fi DPP QR code, it can trigger
* {@code WifiDppConfiguratorActivity} to start with this fragment to choose a saved Wi-Fi network.
*/
public class WifiDppChooseSavedWifiNetworkFragment extends WifiDppQrCodeBaseFragment {
private static final String TAG_FRAGMENT_WIFI_NETWORK_LIST = "wifi_network_list_fragment";
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Embedded WifiNetworkListFragment as child fragment within
// WifiDppChooseSavedWifiNetworkFragment.
final FragmentManager fragmentManager = getChildFragmentManager();
final WifiNetworkListFragment fragment = new WifiNetworkListFragment();
final Bundle args = getArguments();
if (args != null) {
fragment.setArguments(args);
}
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.wifi_network_list_container, fragment,
TAG_FRAGMENT_WIFI_NETWORK_LIST);
fragmentTransaction.commit();
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.wifi_dpp_choose_saved_wifi_network_fragment, container,
/* attachToRoot */ false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setHeaderTitle(R.string.wifi_dpp_choose_network);
mSummary.setText(R.string.wifi_dpp_choose_network_to_connect_device);
mLeftButton.setText(getContext(), R.string.cancel);
mLeftButton.setOnClickListener(v -> {
String action = null;
final Intent intent = getActivity().getIntent();
if (intent != null) {
action = intent.getAction();
}
if (WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_SCANNER.equals(action) ||
WifiDppConfiguratorActivity
.ACTION_CONFIGURATOR_QR_CODE_GENERATOR.equals(action)) {
getFragmentManager().popBackStack();
} else {
getActivity().finish();
}
});
mRightButton.setVisibility(View.GONE);
}
@Override
protected boolean isFooterAvailable() {
return true;
}
}

View File

@@ -0,0 +1,425 @@
/*
* Copyright (C) 2018 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.dpp;
import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentTransaction;
import com.android.settings.R;
import java.util.List;
/**
* To provision "other" device with specified Wi-Fi network.
*
* Uses different intents to specify different provisioning ways.
*
* For intent action {@code ACTION_CONFIGURATOR_QR_CODE_SCANNER} and
* {@code android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR}, specify the Wi-Fi network to be
* provisioned in:
*
* {@code WifiDppUtils.EXTRA_WIFI_SECURITY}
* {@code WifiDppUtils.EXTRA_WIFI_SSID}
* {@code WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY}
* {@code WifiDppUtils.EXTRA_WIFI_HIDDEN_SSID}
*
* For intent action {@link Settings#ACTION_PROCESS_WIFI_EASY_CONNECT_URI}, specify Wi-Fi
* Easy Connect bootstrapping information string in Intent's data URI.
*/
public class WifiDppConfiguratorActivity extends WifiDppBaseActivity implements
WifiNetworkConfig.Retriever,
WifiDppQrCodeScannerFragment.OnScanWifiDppSuccessListener,
WifiDppAddDeviceFragment.OnClickChooseDifferentNetworkListener,
WifiNetworkListFragment.OnChooseNetworkListener {
private static final String TAG = "WifiDppConfiguratorActivity";
static final String ACTION_CONFIGURATOR_QR_CODE_SCANNER =
"android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_SCANNER";
static final String ACTION_CONFIGURATOR_QR_CODE_GENERATOR =
"android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR";
// Key for Bundle usage
private static final String KEY_QR_CODE = "key_qr_code";
private static final String KEY_WIFI_SECURITY = "key_wifi_security";
private static final String KEY_WIFI_SSID = "key_wifi_ssid";
private static final String KEY_WIFI_PRESHARED_KEY = "key_wifi_preshared_key";
private static final String KEY_WIFI_HIDDEN_SSID = "key_wifi_hidden_ssid";
private static final String KEY_WIFI_NETWORK_ID = "key_wifi_network_id";
private static final String KEY_IS_HOTSPOT = "key_is_hotspot";
/** The Wi-Fi network which will be configured */
private WifiNetworkConfig mWifiNetworkConfig;
/** The Wi-Fi DPP QR code from intent ACTION_PROCESS_WIFI_EASY_CONNECT_URI */
private WifiQrCode mWifiDppQrCode;
/**
* The remote device's band support obtained as an (optional) extra
* EXTRA_EASY_CONNECT_BAND_LIST from the intent ACTION_PROCESS_WIFI_EASY_CONNECT_URI.
*
* The band support is provided as IEEE 802.11 Global Operating Classes. There may be a single
* or multiple operating classes specified. The array may also be a null if the extra wasn't
* specified.
*/
private int[] mWifiDppRemoteBandSupport;
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!isAddWifiConfigAllowed(getApplicationContext())) {
finish();
return;
}
if (savedInstanceState != null) {
String qrCode = savedInstanceState.getString(KEY_QR_CODE);
mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(qrCode);
final String security = savedInstanceState.getString(KEY_WIFI_SECURITY);
final String ssid = savedInstanceState.getString(KEY_WIFI_SSID);
final String preSharedKey = savedInstanceState.getString(KEY_WIFI_PRESHARED_KEY);
final boolean hiddenSsid = savedInstanceState.getBoolean(KEY_WIFI_HIDDEN_SSID);
final int networkId = savedInstanceState.getInt(KEY_WIFI_NETWORK_ID);
final boolean isHotspot = savedInstanceState.getBoolean(KEY_IS_HOTSPOT);
mWifiNetworkConfig = WifiNetworkConfig.getValidConfigOrNull(security, ssid,
preSharedKey, hiddenSsid, networkId, isHotspot);
}
}
@Override
protected void handleIntent(Intent intent) {
if (!isAddWifiConfigAllowed(getApplicationContext())) {
finish();
return;
}
if (isGuestUser(getApplicationContext())) {
Log.e(TAG, "Guest user is not allowed to configure Wi-Fi!");
EventLog.writeEvent(0x534e4554, "224772890", -1 /* UID */, "User is a guest");
finish();
return;
}
String action = intent != null ? intent.getAction() : null;
if (action == null) {
finish();
return;
}
boolean cancelActivity = false;
WifiNetworkConfig config;
switch (action) {
case ACTION_CONFIGURATOR_QR_CODE_SCANNER:
config = WifiNetworkConfig.getValidConfigOrNull(intent);
if (config == null) {
cancelActivity = true;
} else {
mWifiNetworkConfig = config;
showQrCodeScannerFragment();
}
break;
case ACTION_CONFIGURATOR_QR_CODE_GENERATOR:
config = WifiNetworkConfig.getValidConfigOrNull(intent);
if (config == null) {
cancelActivity = true;
} else {
mWifiNetworkConfig = config;
showQrCodeGeneratorFragment();
}
break;
case Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_URI:
WifiDppUtils.showLockScreen(this,
() -> handleActionProcessWifiEasyConnectUriIntent(intent));
break;
default:
cancelActivity = true;
Log.e(TAG, "Launch with an invalid action");
}
if (cancelActivity) {
finish();
}
}
private void handleActionProcessWifiEasyConnectUriIntent(Intent intent) {
final Uri uri = intent.getData();
final String uriString = (uri == null) ? null : uri.toString();
mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(uriString);
mWifiDppRemoteBandSupport = intent.getIntArrayExtra(
Settings.EXTRA_EASY_CONNECT_BAND_LIST); // returns null if none
final boolean isDppSupported = WifiDppUtils.isWifiDppEnabled(this);
if (!isDppSupported) {
Log.e(TAG,
"ACTION_PROCESS_WIFI_EASY_CONNECT_URI for a device that doesn't "
+ "support Wifi DPP - use WifiManager#isEasyConnectSupported");
}
if (mWifiDppQrCode == null) {
Log.e(TAG, "ACTION_PROCESS_WIFI_EASY_CONNECT_URI with null URI!");
}
if (mWifiDppQrCode == null || !isDppSupported) {
finish();
} else {
final WifiNetworkConfig connectedConfig = getConnectedWifiNetworkConfigOrNull();
if (connectedConfig == null || !connectedConfig.isSupportWifiDpp(this)) {
showChooseSavedWifiNetworkFragment(/* addToBackStack */ false);
} else {
mWifiNetworkConfig = connectedConfig;
showAddDeviceFragment(/* addToBackStack */ false);
}
}
}
@VisibleForTesting
void showQrCodeScannerFragment() {
WifiDppQrCodeScannerFragment fragment =
(WifiDppQrCodeScannerFragment) mFragmentManager.findFragmentByTag(
WifiDppUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
if (fragment == null) {
fragment = new WifiDppQrCodeScannerFragment();
} else {
if (fragment.isVisible()) {
return;
}
// When the fragment in back stack but not on top of the stack, we can simply pop
// stack because current fragment transactions are arranged in an order
mFragmentManager.popBackStackImmediate();
return;
}
final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment,
WifiDppUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
fragmentTransaction.commit();
}
private void showQrCodeGeneratorFragment() {
WifiDppQrCodeGeneratorFragment fragment =
(WifiDppQrCodeGeneratorFragment) mFragmentManager.findFragmentByTag(
WifiDppUtils.TAG_FRAGMENT_QR_CODE_GENERATOR);
if (fragment == null) {
fragment = new WifiDppQrCodeGeneratorFragment();
} else {
if (fragment.isVisible()) {
return;
}
// When the fragment in back stack but not on top of the stack, we can simply pop
// stack because current fragment transactions are arranged in an order
mFragmentManager.popBackStackImmediate();
return;
}
final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment,
WifiDppUtils.TAG_FRAGMENT_QR_CODE_GENERATOR);
fragmentTransaction.commit();
}
private void showChooseSavedWifiNetworkFragment(boolean addToBackStack) {
WifiDppChooseSavedWifiNetworkFragment fragment =
(WifiDppChooseSavedWifiNetworkFragment) mFragmentManager.findFragmentByTag(
WifiDppUtils.TAG_FRAGMENT_CHOOSE_SAVED_WIFI_NETWORK);
if (fragment == null) {
fragment = new WifiDppChooseSavedWifiNetworkFragment();
} else {
if (fragment.isVisible()) {
return;
}
// When the fragment in back stack but not on top of the stack, we can simply pop
// stack because current fragment transactions are arranged in an order
mFragmentManager.popBackStackImmediate();
return;
}
final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment,
WifiDppUtils.TAG_FRAGMENT_CHOOSE_SAVED_WIFI_NETWORK);
if (addToBackStack) {
fragmentTransaction.addToBackStack(/* name */ null);
}
fragmentTransaction.commit();
}
private void showAddDeviceFragment(boolean addToBackStack) {
WifiDppAddDeviceFragment fragment =
(WifiDppAddDeviceFragment) mFragmentManager.findFragmentByTag(
WifiDppUtils.TAG_FRAGMENT_ADD_DEVICE);
if (fragment == null) {
fragment = new WifiDppAddDeviceFragment();
} else {
if (fragment.isVisible()) {
return;
}
// When the fragment in back stack but not on top of the stack, we can simply pop
// stack because current fragment transactions are arranged in an order
mFragmentManager.popBackStackImmediate();
return;
}
final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment,
WifiDppUtils.TAG_FRAGMENT_ADD_DEVICE);
if (addToBackStack) {
fragmentTransaction.addToBackStack(/* name */ null);
}
fragmentTransaction.commit();
}
@Override
public WifiNetworkConfig getWifiNetworkConfig() {
return mWifiNetworkConfig;
}
WifiQrCode getWifiDppQrCode() {
return mWifiDppQrCode;
}
@VisibleForTesting
boolean setWifiNetworkConfig(WifiNetworkConfig config) {
if(!WifiNetworkConfig.isValidConfig(config)) {
return false;
} else {
mWifiNetworkConfig = new WifiNetworkConfig(config);
return true;
}
}
@VisibleForTesting
boolean setWifiDppQrCode(WifiQrCode wifiQrCode) {
if (wifiQrCode == null) {
return false;
}
if (!WifiQrCode.SCHEME_DPP.equals(wifiQrCode.getScheme())) {
return false;
}
mWifiDppQrCode = new WifiQrCode(wifiQrCode.getQrCode());
return true;
}
@Override
public void onScanWifiDppSuccess(WifiQrCode wifiQrCode) {
mWifiDppQrCode = wifiQrCode;
showAddDeviceFragment(/* addToBackStack */ true);
}
@Override
public void onClickChooseDifferentNetwork() {
showChooseSavedWifiNetworkFragment(/* addToBackStack */ true);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mWifiDppQrCode != null) {
outState.putString(KEY_QR_CODE, mWifiDppQrCode.getQrCode());
}
if (mWifiNetworkConfig != null) {
outState.putString(KEY_WIFI_SECURITY, mWifiNetworkConfig.getSecurity());
outState.putString(KEY_WIFI_SSID, mWifiNetworkConfig.getSsid());
outState.putString(KEY_WIFI_PRESHARED_KEY, mWifiNetworkConfig.getPreSharedKey());
outState.putBoolean(KEY_WIFI_HIDDEN_SSID, mWifiNetworkConfig.getHiddenSsid());
outState.putInt(KEY_WIFI_NETWORK_ID, mWifiNetworkConfig.getNetworkId());
outState.putBoolean(KEY_IS_HOTSPOT, mWifiNetworkConfig.isHotspot());
}
super.onSaveInstanceState(outState);
}
@Override
public void onChooseNetwork(WifiNetworkConfig wifiNetworkConfig) {
mWifiNetworkConfig = new WifiNetworkConfig(wifiNetworkConfig);
showAddDeviceFragment(/* addToBackStack */ true);
}
private WifiNetworkConfig getConnectedWifiNetworkConfigOrNull() {
final WifiManager wifiManager = getSystemService(WifiManager.class);
if (!wifiManager.isWifiEnabled()) {
return null;
}
final WifiInfo connectionInfo = wifiManager.getConnectionInfo();
if (connectionInfo == null) {
return null;
}
final int connectionNetworkId = connectionInfo.getNetworkId();
final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
for (WifiConfiguration wifiConfiguration : configs) {
if (wifiConfiguration.networkId == connectionNetworkId) {
return WifiNetworkConfig.getValidConfigOrNull(
WifiDppUtils.getSecurityString(wifiConfiguration),
wifiConfiguration.getPrintableSsid(),
wifiConfiguration.preSharedKey,
wifiConfiguration.hiddenSSID,
wifiConfiguration.networkId,
/* isHotspot */ false);
}
}
return null;
}
private static boolean isGuestUser(Context context) {
if (context == null) return false;
final UserManager userManager = context.getSystemService(UserManager.class);
if (userManager == null) return false;
return userManager.isGuestUser();
}
@VisibleForTesting
static boolean isAddWifiConfigAllowed(Context context) {
UserManager userManager = context.getSystemService(UserManager.class);
if (userManager != null && userManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)) {
Log.e(TAG, "The user is not allowed to add Wi-Fi configuration.");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2023 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.dpp;
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import android.app.Activity;
import android.app.KeyguardManager;
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.contract.ActivityResultContracts;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.core.InstrumentedActivity;
/**
* Sharing a Wi-Fi network by QR code after unlocking. Used by {@code InternetDialog} in QS.
*/
public class WifiDppConfiguratorAuthActivity extends InstrumentedActivity {
private static final String WIFI_SHARING_KEY_ALIAS = "wifi_sharing_auth_key";
private static final int MAX_UNLOCK_SECONDS = 60;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// This is a transparent activity, disable the dim.
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
Intent authIntent = getSystemService(KeyguardManager.class)
.createConfirmDeviceCredentialIntent(
getText(R.string.wifi_dpp_lockscreen_title), null, getUserId());
if (authIntent == null
|| WifiDppUtils.isUnlockedWithinSeconds(
WIFI_SHARING_KEY_ALIAS, MAX_UNLOCK_SECONDS)) {
startQrCodeActivity();
finish();
} else {
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
this::onAuthResult).launch(authIntent);
}
}
@VisibleForTesting
void onAuthResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
startQrCodeActivity();
}
finish();
}
private void startQrCodeActivity() {
// Close quick settings shade
sendBroadcast(
new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
Intent qrCodeIntent = new Intent();
qrCodeIntent.setAction(
WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
qrCodeIntent.putExtras(getIntent());
startActivity(qrCodeIntent);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2018 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.dpp;
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.util.EventLog;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentTransaction;
import com.android.settings.R;
import com.android.settingslib.wifi.WifiRestrictionsCache;
/**
* To provision "this" device with specified Wi-Fi network.
*
* To use intent action {@code ACTION_ENROLLEE_QR_CODE_SCANNER}, specify the SSID string of the
* Wi-Fi network to be provisioned in {@code WifiDppUtils.EXTRA_WIFI_SSID}.
*/
public class WifiDppEnrolleeActivity extends WifiDppBaseActivity implements
WifiDppQrCodeScannerFragment.OnScanWifiDppSuccessListener {
private static final String TAG = "WifiDppEnrolleeActivity";
static final String ACTION_ENROLLEE_QR_CODE_SCANNER =
"android.settings.WIFI_DPP_ENROLLEE_QR_CODE_SCANNER";
@VisibleForTesting
protected WifiRestrictionsCache mWifiRestrictionsCache;
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE;
}
@Override
protected void handleIntent(Intent intent) {
String action = intent != null ? intent.getAction() : null;
if (action == null) {
finish();
return;
}
if (!isWifiConfigAllowed()) {
Log.e(TAG, "The user is not allowed to configure Wi-Fi.");
finish();
EventLog.writeEvent(0x534e4554, "202017876", getApplicationContext().getUserId(),
"The user is not allowed to configure Wi-Fi.");
return;
}
switch (action) {
case ACTION_ENROLLEE_QR_CODE_SCANNER:
String ssid = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_SSID);
showQrCodeScannerFragment(ssid);
break;
default:
Log.e(TAG, "Launch with an invalid action");
finish();
}
}
private boolean isWifiConfigAllowed() {
if (mWifiRestrictionsCache == null) {
mWifiRestrictionsCache = WifiRestrictionsCache.getInstance(getApplicationContext());
}
return mWifiRestrictionsCache.isConfigWifiAllowed();
}
@VisibleForTesting
protected void showQrCodeScannerFragment(String ssid) {
WifiDppQrCodeScannerFragment fragment =
(WifiDppQrCodeScannerFragment) mFragmentManager.findFragmentByTag(
WifiDppUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
if (fragment == null) {
fragment = new WifiDppQrCodeScannerFragment(ssid);
} else {
if (fragment.isVisible()) {
return;
}
// When the fragment in back stack but not on top of the stack, we can simply pop
// stack because current fragment transactions are arranged in an order
mFragmentManager.popBackStackImmediate();
return;
}
final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment,
WifiDppUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
fragmentTransaction.commit();
}
@Override
public void onScanWifiDppSuccess(WifiQrCode wifiQrCode) {
// Do nothing
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2019 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.dpp;
import android.app.Application;
import android.net.wifi.EasyConnectStatusCallback;
import android.net.wifi.WifiManager;
import android.util.SparseArray;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
public class WifiDppInitiatorViewModel extends AndroidViewModel {
private MutableLiveData<Integer> mEnrolleeSuccessNetworkId;
private MutableLiveData<Integer> mStatusCode;
private boolean mIsWifiDppHandshaking;
private String mTriedSsid;
private SparseArray<int[]> mTriedChannels;
private int[] mBandArray;
public WifiDppInitiatorViewModel(Application application) {
super(application);
}
MutableLiveData<Integer> getEnrolleeSuccessNetworkId() {
if (mEnrolleeSuccessNetworkId == null) {
mEnrolleeSuccessNetworkId = new MutableLiveData<>();
}
return mEnrolleeSuccessNetworkId;
}
MutableLiveData<Integer> getStatusCode() {
if (mStatusCode == null) {
mStatusCode = new MutableLiveData<>();
}
return mStatusCode;
}
String getTriedSsid() {
return mTriedSsid;
}
SparseArray<int[]> getTriedChannels() {
return mTriedChannels;
}
int[] getBandArray() {
return mBandArray;
}
boolean isWifiDppHandshaking() {
return mIsWifiDppHandshaking;
}
void startEasyConnectAsConfiguratorInitiator(String qrCode, int networkId) {
mIsWifiDppHandshaking = true;
final WifiManager wifiManager = getApplication().getSystemService(WifiManager.class);
wifiManager.startEasyConnectAsConfiguratorInitiator(qrCode, networkId,
WifiManager.EASY_CONNECT_NETWORK_ROLE_STA, getApplication().getMainExecutor(),
new EasyConnectDelegateCallback());
}
void startEasyConnectAsEnrolleeInitiator(String qrCode) {
mIsWifiDppHandshaking = true;
final WifiManager wifiManager = getApplication().getSystemService(WifiManager.class);
wifiManager.startEasyConnectAsEnrolleeInitiator(qrCode, getApplication().getMainExecutor(),
new EasyConnectDelegateCallback());
}
private class EasyConnectDelegateCallback extends EasyConnectStatusCallback {
@Override
public void onEnrolleeSuccess(int newNetworkId) {
mIsWifiDppHandshaking = false;
mEnrolleeSuccessNetworkId.setValue(newNetworkId);
}
@Override
public void onConfiguratorSuccess(int code) {
mIsWifiDppHandshaking = false;
mStatusCode.setValue(WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS);
}
@Override
public void onFailure(int code, String ssid, SparseArray<int[]> channelListArray,
int[] operatingClassArray) {
mIsWifiDppHandshaking = false;
mTriedSsid = ssid;
mTriedChannels = channelListArray;
mBandArray = operatingClassArray;
mStatusCode.setValue(code);
}
@Override
public void onProgress(int code) {
// Do nothing
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2018 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.dpp;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import com.android.settings.core.InstrumentedFragment;
import com.google.android.setupcompat.template.FooterBarMixin;
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
/**
* There are below 4 fragments for Wi-Fi DPP UI flow, to reduce redundant code of UI components,
* this parent fragment instantiates common UI components
*
* {@code WifiDppQrCodeScannerFragment}
* {@code WifiDppQrCodeGeneratorFragment}
* {@code WifiDppChooseSavedWifiNetworkFragment}
* {@code WifiDppAddDeviceFragment}
*/
public abstract class WifiDppQrCodeBaseFragment extends InstrumentedFragment {
private static final String TAG = "WifiDppQrCodeBaseFragment";
private GlifLayout mGlifLayout;
protected TextView mSummary;
protected FooterButton mLeftButton;
protected FooterButton mRightButton;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mGlifLayout = (GlifLayout) view;
mSummary = view.findViewById(android.R.id.summary);
if (isFooterAvailable()) {
mLeftButton = new FooterButton.Builder(getContext())
.setButtonType(FooterButton.ButtonType.CANCEL)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
.build();
mGlifLayout.getMixin(FooterBarMixin.class).setSecondaryButton(mLeftButton);
mRightButton = new FooterButton.Builder(getContext())
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build();
mGlifLayout.getMixin(FooterBarMixin.class).setPrimaryButton(mRightButton);
}
mGlifLayout.getHeaderTextView().setAccessibilityLiveRegion(
View.ACCESSIBILITY_LIVE_REGION_POLITE);
}
protected void setHeaderIconImageResource(@DrawableRes int iconResId) {
mGlifLayout.setIcon(getDrawable(iconResId));
}
private Drawable getDrawable(@DrawableRes int iconResId) {
Drawable buttonIcon = null;
try {
buttonIcon = getContext().getDrawable(iconResId);
} catch (Resources.NotFoundException exception) {
Log.e(TAG, "Resource does not exist: " + iconResId);
}
return buttonIcon;
}
protected void setHeaderTitle(String title) {
mGlifLayout.setHeaderText(title);
}
protected void setHeaderTitle(int resId, Object... formatArgs) {
mGlifLayout.setHeaderText(getString(resId, formatArgs));
}
protected void setProgressBarShown(boolean shown) {
mGlifLayout.setProgressBarShown(shown);
}
protected abstract boolean isFooterAvailable();
}

View File

@@ -0,0 +1,289 @@
/*
* Copyright (C) 2018 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.dpp;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.provider.Settings;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
import com.android.settings.R;
import com.android.settingslib.qrcode.QrCodeGenerator;
import com.google.zxing.WriterException;
/**
* After sharing a saved Wi-Fi network, {@code WifiDppConfiguratorActivity} start with this fragment
* to generate a Wi-Fi DPP QR code for other device to initiate as an enrollee.
*/
public class WifiDppQrCodeGeneratorFragment extends WifiDppQrCodeBaseFragment {
private static final String TAG = "WifiDppQrCodeGeneratorFragment";
private ImageView mQrCodeView;
private String mQrCode;
private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
private static final String EXTRA_WIFI_CREDENTIALS_BUNDLE =
"android.intent.extra.WIFI_CREDENTIALS_BUNDLE";
private static final String EXTRA_SSID = "android.intent.extra.SSID";
private static final String EXTRA_PASSWORD = "android.intent.extra.PASSWORD";
private static final String EXTRA_SECURITY_TYPE = "android.intent.extra.SECURITY_TYPE";
private static final String EXTRA_HIDDEN_SSID = "android.intent.extra.HIDDEN_SSID";
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// setTitle for TalkBack
final WifiNetworkConfig wifiNetworkConfig = getWifiNetworkConfigFromHostActivity();
if (getActivity() != null) {
if (wifiNetworkConfig.isHotspot()) {
getActivity().setTitle(R.string.wifi_dpp_share_hotspot);
} else {
getActivity().setTitle(R.string.wifi_dpp_share_wifi);
}
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
final MenuItem menuItem = menu.findItem(Menu.FIRST);
if (menuItem != null) {
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.wifi_dpp_qrcode_generator_fragment, container,
/* attachToRoot */ false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mQrCodeView = view.findViewById(R.id.qrcode_view);
final WifiNetworkConfig wifiNetworkConfig = getWifiNetworkConfigFromHostActivity();
if (wifiNetworkConfig.isHotspot()) {
setHeaderTitle(R.string.wifi_dpp_share_hotspot);
} else {
setHeaderTitle(R.string.wifi_dpp_share_wifi);
}
final String password = wifiNetworkConfig.getPreSharedKey();
TextView passwordView = view.findViewById(R.id.password);
passwordView.setInputType(
InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
if (TextUtils.isEmpty(password)) {
mSummary.setText(getString(
R.string.wifi_dpp_scan_open_network_qr_code_with_another_device,
wifiNetworkConfig.getSsid()));
passwordView.setVisibility(View.GONE);
} else {
mSummary.setText(getString(R.string.wifi_dpp_scan_qr_code_with_another_device,
wifiNetworkConfig.getSsid()));
if (wifiNetworkConfig.isHotspot()) {
passwordView.setText(getString(R.string.wifi_dpp_hotspot_password, password));
} else {
passwordView.setText(getString(R.string.wifi_dpp_wifi_password, password));
}
}
final Intent intent = new Intent().setComponent(getNearbySharingComponent());
addActionButton(view.findViewById(R.id.wifi_dpp_layout), createNearbyButton(intent, v -> {
intent.setAction(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
Bundle wifiCredentialBundle = new Bundle();
String ssid = WifiDppUtils.removeFirstAndLastDoubleQuotes(wifiNetworkConfig.getSsid());
String passwordExtra = wifiNetworkConfig.getPreSharedKey();
String securityType = wifiNetworkConfig.getSecurity();
boolean hiddenSsid = wifiNetworkConfig.getHiddenSsid();
wifiCredentialBundle.putString(EXTRA_SSID, ssid);
wifiCredentialBundle.putString(EXTRA_PASSWORD, passwordExtra);
wifiCredentialBundle.putString(EXTRA_SECURITY_TYPE, securityType);
wifiCredentialBundle.putBoolean(EXTRA_HIDDEN_SSID, hiddenSsid);
intent.putExtra(EXTRA_WIFI_CREDENTIALS_BUNDLE, wifiCredentialBundle);
startActivity(intent);
}));
mQrCode = wifiNetworkConfig.getQrCode();
setQrCode();
}
@VisibleForTesting
ComponentName getNearbySharingComponent() {
String nearbyComponent = Settings.Secure.getString(
getContext().getContentResolver(),
Settings.Secure.NEARBY_SHARING_COMPONENT);
if (TextUtils.isEmpty(nearbyComponent)) {
nearbyComponent = getString(
com.android.internal.R.string.config_defaultNearbySharingComponent);
}
if (TextUtils.isEmpty(nearbyComponent)) {
return null;
}
return ComponentName.unflattenFromString(nearbyComponent);
}
private TargetInfo getNearbySharingTarget(Intent originalIntent) {
final ComponentName cn = getNearbySharingComponent();
if (cn == null) return null;
final Intent resolveIntent = new Intent(originalIntent);
resolveIntent.setComponent(cn);
PackageManager pm = getContext().getPackageManager();
final ResolveInfo resolveInfo = pm.resolveActivity(
resolveIntent, PackageManager.GET_META_DATA);
if (resolveInfo == null || resolveInfo.activityInfo == null) {
Log.e(TAG, "Device-specified nearby sharing component (" + cn
+ ") not available");
return null;
}
// Allow the nearby sharing component to provide a more appropriate icon and label
// for the chip.
CharSequence name = null;
Drawable icon = null;
final Bundle metaData = resolveInfo.activityInfo.metaData;
if (metaData != null) {
try {
final Resources pkgRes = pm.getResourcesForActivity(cn);
final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY);
name = pkgRes.getString(nameResId);
final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY);
icon = pkgRes.getDrawable(resId);
} catch (Resources.NotFoundException ex) {
} catch (PackageManager.NameNotFoundException ex) {
}
}
if (TextUtils.isEmpty(name)) {
name = resolveInfo.loadLabel(pm);
}
if (icon == null) {
icon = resolveInfo.loadIcon(pm);
}
final DisplayResolveInfo dri = new DisplayResolveInfo(
originalIntent, resolveInfo, name, "", resolveIntent, null);
dri.setDisplayIcon(icon);
return dri;
}
private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
final Button b = (Button) LayoutInflater.from(getContext()).inflate(
R.layout.action_button, null);
if (icon != null) {
final int size = getResources().getDimensionPixelSize(R.dimen.action_button_icon_size);
icon.setBounds(0, 0, size, size);
b.setCompoundDrawablesRelative(icon, null, null, null);
}
b.setText(title);
b.setOnClickListener(r);
return b;
}
private void addActionButton(ViewGroup parent, Button b) {
if (b == null) return;
final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
final int gap = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.resolver_icon_margin) / 2;
lp.setMarginsRelative(gap, 0, gap, 0);
parent.addView(b, lp);
}
@VisibleForTesting
@Nullable
Button createNearbyButton(Intent originalIntent, View.OnClickListener r) {
final TargetInfo ti = getNearbySharingTarget(originalIntent);
if (ti == null) return null;
final Button button = createActionButton(ti.getDisplayIcon(getContext()),
ti.getDisplayLabel(), r);
button.setAllCaps(false);
return button;
}
private void setQrCode() {
try {
final int qrcodeSize = getContext().getResources().getDimensionPixelSize(
R.dimen.qrcode_size);
final Bitmap bmp = QrCodeGenerator.encodeQrCode(mQrCode, qrcodeSize);
mQrCodeView.setImageBitmap(bmp);
} catch (WriterException e) {
Log.e(TAG, "Error generating QR code bitmap " + e);
}
}
private WifiNetworkConfig getWifiNetworkConfigFromHostActivity() {
final WifiNetworkConfig wifiNetworkConfig = ((WifiNetworkConfig.Retriever) getActivity())
.getWifiNetworkConfig();
if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) {
throw new IllegalStateException("Invalid Wi-Fi network for configuring");
}
return wifiNetworkConfig;
}
@Override
protected boolean isFooterAvailable() {
return false;
}
}

View File

@@ -0,0 +1,817 @@
/*
* Copyright (C) 2018 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.dpp;
import static android.net.wifi.WifiInfo.sanitizeSsid;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.net.wifi.EasyConnectStatusCallback;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.ViewModelProvider;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.qrcode.QrCamera;
import com.android.settingslib.qrcode.QrDecorateView;
import com.android.settingslib.wifi.WifiPermissionChecker;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.List;
public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment implements
SurfaceTextureListener,
QrCamera.ScannerCallback,
WifiManager.ActionListener {
private static final String TAG = "WifiDppQrCodeScanner";
/** Message sent to hide error message */
private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
/** Message sent to show error message */
private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
/** Message sent to manipulate Wi-Fi DPP QR code */
private static final int MESSAGE_SCAN_WIFI_DPP_SUCCESS = 3;
/** Message sent to manipulate ZXing Wi-Fi QR code */
private static final int MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS = 4;
private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
// Key for Bundle usage
private static final String KEY_IS_CONFIGURATOR_MODE = "key_is_configurator_mode";
private static final String KEY_LATEST_ERROR_CODE = "key_latest_error_code";
public static final String KEY_WIFI_CONFIGURATION = "key_wifi_configuration";
private static final int ARG_RESTART_CAMERA = 1;
// Max age of tracked WifiEntries.
private static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating WifiPickerTracker scans.
private static final long SCAN_INTERVAL_MILLIS = 10_000;
private QrCamera mCamera;
private TextureView mTextureView;
private QrDecorateView mDecorateView;
private TextView mErrorMessage;
/** true if the fragment working for configurator, false enrollee*/
private boolean mIsConfiguratorMode;
/** The SSID of the Wi-Fi network which the user specify to enroll */
private String mSsid;
/** QR code data scanned by camera */
private WifiQrCode mWifiQrCode;
/** The WifiConfiguration connecting for enrollee usage */
private WifiConfiguration mEnrolleeWifiConfiguration;
private int mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE;
private WifiPickerTracker mWifiPickerTracker;
private HandlerThread mWorkerThread;
private WifiPermissionChecker mWifiPermissionChecker;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_HIDE_ERROR_MESSAGE:
mErrorMessage.setVisibility(View.INVISIBLE);
break;
case MESSAGE_SHOW_ERROR_MESSAGE:
final String errorMessage = (String) msg.obj;
mErrorMessage.setVisibility(View.VISIBLE);
mErrorMessage.setText(errorMessage);
mErrorMessage.sendAccessibilityEvent(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
// Cancel any pending messages to hide error view and requeue the message so
// user has time to see error
removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE,
SHOW_ERROR_MESSAGE_INTERVAL);
if (msg.arg1 == ARG_RESTART_CAMERA) {
setProgressBarShown(false);
mDecorateView.setFocused(false);
restartCamera();
}
break;
case MESSAGE_SCAN_WIFI_DPP_SUCCESS:
if (mScanWifiDppSuccessListener == null) {
// mScanWifiDppSuccessListener may be null after onDetach(), do nothing here
return;
}
mScanWifiDppSuccessListener.onScanWifiDppSuccess((WifiQrCode)msg.obj);
if (!mIsConfiguratorMode) {
setProgressBarShown(true);
startWifiDppEnrolleeInitiator((WifiQrCode)msg.obj);
updateEnrolleeSummary();
mSummary.sendAccessibilityEvent(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
notifyUserForQrCodeRecognition();
break;
case MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS:
final Context context = getContext();
if (context == null) {
// Context may be null if the message is received after the Activity has
// been destroyed
Log.d(TAG, "Scan success but context is null");
return;
}
// We may get 2 WifiConfiguration if the QR code has no password in it,
// one for open network and one for enhanced open network.
final WifiManager wifiManager =
context.getSystemService(WifiManager.class);
final WifiNetworkConfig qrCodeWifiNetworkConfig =
(WifiNetworkConfig)msg.obj;
final List<WifiConfiguration> qrCodeWifiConfigurations =
qrCodeWifiNetworkConfig.getWifiConfigurations();
// Adds all Wi-Fi networks in QR code to the set of configured networks and
// connects to it if it's reachable.
boolean hasHiddenOrReachableWifiNetwork = false;
for (WifiConfiguration qrCodeWifiConfiguration : qrCodeWifiConfigurations) {
final int id = wifiManager.addNetwork(qrCodeWifiConfiguration);
if (id == -1) {
continue;
}
if (!canConnectWifi(qrCodeWifiConfiguration.SSID)) return;
wifiManager.enableNetwork(id, /* attemptConnect */ false);
// WifiTracker only contains a hidden SSID Wi-Fi network if it's saved.
// We can't check if a hidden SSID Wi-Fi network is reachable in advance.
if (qrCodeWifiConfiguration.hiddenSSID ||
isReachableWifiNetwork(qrCodeWifiConfiguration)) {
hasHiddenOrReachableWifiNetwork = true;
mEnrolleeWifiConfiguration = qrCodeWifiConfiguration;
wifiManager.connect(id,
/* listener */ WifiDppQrCodeScannerFragment.this);
}
}
if (!hasHiddenOrReachableWifiNetwork) {
showErrorMessageAndRestartCamera(
R.string.wifi_dpp_check_connection_try_again);
return;
}
mMetricsFeatureProvider.action(
mMetricsFeatureProvider.getAttribution(getActivity()),
SettingsEnums.ACTION_SETTINGS_ENROLL_WIFI_QR_CODE,
SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE,
/* key */ null,
/* value */ Integer.MIN_VALUE);
notifyUserForQrCodeRecognition();
break;
default:
}
}
};
@UiThread
private void notifyUserForQrCodeRecognition() {
if (mCamera != null) {
mCamera.stop();
}
mDecorateView.setFocused(true);
mErrorMessage.setVisibility(View.INVISIBLE);
WifiDppUtils.triggerVibrationForQrCodeRecognition(getContext());
}
private boolean isReachableWifiNetwork(WifiConfiguration wifiConfiguration) {
final List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries();
final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry();
if (connectedWifiEntry != null) {
// Add connected WifiEntry to prevent fail toast to users when it's connected.
wifiEntries.add(connectedWifiEntry);
}
for (WifiEntry wifiEntry : wifiEntries) {
if (!TextUtils.equals(wifiEntry.getSsid(), sanitizeSsid(wifiConfiguration.SSID))) {
continue;
}
final int security =
WifiDppUtils.getSecurityTypeFromWifiConfiguration(wifiConfiguration);
if (security == wifiEntry.getSecurity()) {
return true;
}
// Default security type of PSK/SAE transition mode WifiEntry is SECURITY_PSK and
// there is no way to know if a WifiEntry is of transition mode. Give it a chance.
if (security == WifiEntry.SECURITY_SAE
&& wifiEntry.getSecurity() == WifiEntry.SECURITY_PSK) {
return true;
}
}
return false;
}
@VisibleForTesting
boolean canConnectWifi(String ssid) {
final List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries();
for (WifiEntry wifiEntry : wifiEntries) {
if (!TextUtils.equals(wifiEntry.getSsid(), sanitizeSsid(ssid))) continue;
if (!wifiEntry.canConnect()) {
Log.w(TAG, "Wi-Fi is not allowed to connect by your organization. SSID:" + ssid);
showErrorMessageAndRestartCamera(R.string.not_allowed_by_ent);
return false;
}
}
return true;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mIsConfiguratorMode = savedInstanceState.getBoolean(KEY_IS_CONFIGURATOR_MODE);
mLatestStatusCode = savedInstanceState.getInt(KEY_LATEST_ERROR_CODE);
mEnrolleeWifiConfiguration = savedInstanceState.getParcelable(KEY_WIFI_CONFIGURATION);
}
final WifiDppInitiatorViewModel model =
new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class);
model.getEnrolleeSuccessNetworkId().observe(this, networkId -> {
// After configuration change, observe callback will be triggered,
// do nothing for this case if a handshake does not end
if (model.isWifiDppHandshaking()) {
return;
}
new EasyConnectEnrolleeStatusCallback().onEnrolleeSuccess(networkId.intValue());
});
model.getStatusCode().observe(this, statusCode -> {
// After configuration change, observe callback will be triggered,
// do nothing for this case if a handshake does not end
if (model.isWifiDppHandshaking()) {
return;
}
int code = statusCode.intValue();
Log.d(TAG, "Easy connect enrollee callback onFailure " + code);
new EasyConnectEnrolleeStatusCallback().onFailure(code);
});
}
@Override
public void onPause() {
if (mCamera != null) {
mCamera.stop();
}
super.onPause();
}
@Override
public void onResume() {
super.onResume();
if (!isWifiDppHandshaking()) {
restartCamera();
}
}
@Override
public int getMetricsCategory() {
if (mIsConfiguratorMode) {
return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
} else {
return SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE;
}
}
// Container Activity must implement this interface
public interface OnScanWifiDppSuccessListener {
void onScanWifiDppSuccess(WifiQrCode wifiQrCode);
}
private OnScanWifiDppSuccessListener mScanWifiDppSuccessListener;
/**
* Configurator container activity of the fragment should create instance with this constructor.
*/
public WifiDppQrCodeScannerFragment() {
super();
mIsConfiguratorMode = true;
}
public WifiDppQrCodeScannerFragment(WifiPickerTracker wifiPickerTracker,
WifiPermissionChecker wifiPermissionChecker) {
super();
mIsConfiguratorMode = true;
mWifiPickerTracker = wifiPickerTracker;
mWifiPermissionChecker = wifiPermissionChecker;
}
/**
* Enrollee container activity of the fragment should create instance with this constructor and
* specify the SSID string of the WI-Fi network to be provisioned.
*/
WifiDppQrCodeScannerFragment(String ssid) {
super();
mIsConfiguratorMode = false;
mSsid = ssid;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
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();
}
};
final Context context = getContext();
mWifiPickerTracker = FeatureFactory.getFeatureFactory()
.getWifiTrackerLibProvider()
.createWifiPickerTracker(getSettingsLifecycle(), context,
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
null /* listener */);
// setTitle for TalkBack
if (mIsConfiguratorMode) {
getActivity().setTitle(R.string.wifi_dpp_add_device_to_network);
} else {
getActivity().setTitle(R.string.wifi_dpp_scan_qr_code);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mScanWifiDppSuccessListener = (OnScanWifiDppSuccessListener) context;
}
@Override
public void onDetach() {
mScanWifiDppSuccessListener = null;
super.onDetach();
}
@Override
public void onDestroyView() {
mWorkerThread.quit();
super.onDestroyView();
}
@Override
public final View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.wifi_dpp_qrcode_scanner_fragment, container,
/* attachToRoot */ false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mSummary = view.findViewById(R.id.sud_layout_subtitle);
mTextureView = view.findViewById(R.id.preview_view);
mTextureView.setSurfaceTextureListener(this);
mDecorateView = view.findViewById(R.id.decorate_view);
setProgressBarShown(isWifiDppHandshaking());
if (mIsConfiguratorMode) {
setHeaderTitle(R.string.wifi_dpp_add_device_to_network);
WifiNetworkConfig wifiNetworkConfig = ((WifiNetworkConfig.Retriever) getActivity())
.getWifiNetworkConfig();
if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) {
throw new IllegalStateException("Invalid Wi-Fi network for configuring");
}
mSummary.setText(getString(R.string.wifi_dpp_center_qr_code,
wifiNetworkConfig.getSsid()));
} else {
setHeaderTitle(R.string.wifi_dpp_scan_qr_code);
updateEnrolleeSummary();
}
mErrorMessage = view.findViewById(R.id.error_message);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.removeItem(Menu.FIRST);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
initCamera(surface);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
// Do nothing
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
destroyCamera();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// Do nothing
}
@Override
public Size getViewSize() {
return new Size(mTextureView.getWidth(), mTextureView.getHeight());
}
@Override
public Rect getFramePosition(Size previewSize, int cameraOrientation) {
return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
}
@Override
public void setTransform(Matrix transform) {
mTextureView.setTransform(transform);
}
@Override
public boolean isValid(String qrCode) {
try {
mWifiQrCode = new WifiQrCode(qrCode);
} catch (IllegalArgumentException e) {
showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format);
return false;
}
// It's impossible to provision other device with ZXing Wi-Fi Network config format
final String scheme = mWifiQrCode.getScheme();
if (mIsConfiguratorMode && WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG.equals(scheme)) {
showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format);
return false;
}
return true;
}
/**
* This method is only called when QrCamera.ScannerCallback.isValid returns true;
*/
@Override
public void handleSuccessfulResult(String qrCode) {
switch (mWifiQrCode.getScheme()) {
case WifiQrCode.SCHEME_DPP:
handleWifiDpp();
break;
case WifiQrCode.SCHEME_ZXING_WIFI_NETWORK_CONFIG:
handleZxingWifiFormat();
break;
default:
// continue below
}
}
private void handleWifiDpp() {
Message message = mHandler.obtainMessage(MESSAGE_SCAN_WIFI_DPP_SUCCESS);
message.obj = new WifiQrCode(mWifiQrCode.getQrCode());
mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
}
private void handleZxingWifiFormat() {
Message message = mHandler.obtainMessage(MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS);
message.obj = new WifiQrCode(mWifiQrCode.getQrCode()).getWifiNetworkConfig();
mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
}
@Override
public void handleCameraFailure() {
destroyCamera();
}
private void initCamera(SurfaceTexture surface) {
// Check if the camera has already created.
if (mCamera == null) {
mCamera = new QrCamera(getContext(), this);
if (isWifiDppHandshaking()) {
if (mDecorateView != null) {
mDecorateView.setFocused(true);
}
} else {
mCamera.start(surface);
}
}
}
private void destroyCamera() {
if (mCamera != null) {
mCamera.stop();
mCamera = null;
}
}
private void showErrorMessage(@StringRes int messageResId) {
final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
getString(messageResId));
message.sendToTarget();
}
@VisibleForTesting
void showErrorMessageAndRestartCamera(@StringRes int messageResId) {
final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE,
getString(messageResId));
message.arg1 = ARG_RESTART_CAMERA;
message.sendToTarget();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(KEY_IS_CONFIGURATOR_MODE, mIsConfiguratorMode);
outState.putInt(KEY_LATEST_ERROR_CODE, mLatestStatusCode);
outState.putParcelable(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration);
super.onSaveInstanceState(outState);
}
private class EasyConnectEnrolleeStatusCallback extends EasyConnectStatusCallback {
@Override
public void onEnrolleeSuccess(int newNetworkId) {
// Connect to the new network.
final WifiManager wifiManager = getContext().getSystemService(WifiManager.class);
final List<WifiConfiguration> wifiConfigs =
wifiManager.getPrivilegedConfiguredNetworks();
for (WifiConfiguration wifiConfig : wifiConfigs) {
if (wifiConfig.networkId == newNetworkId) {
mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS;
mEnrolleeWifiConfiguration = wifiConfig;
if (!canConnectWifi(wifiConfig.SSID)) return;
wifiManager.connect(wifiConfig, WifiDppQrCodeScannerFragment.this);
return;
}
}
Log.e(TAG, "Invalid networkId " + newNetworkId);
mLatestStatusCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC;
updateEnrolleeSummary();
showErrorMessageAndRestartCamera(R.string.wifi_dpp_check_connection_try_again);
}
@Override
public void onConfiguratorSuccess(int code) {
// Do nothing
}
@Override
public void onFailure(int code) {
Log.d(TAG, "EasyConnectEnrolleeStatusCallback.onFailure " + code);
int errorMessageResId;
switch (code) {
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI:
errorMessageResId = R.string.wifi_dpp_qr_code_is_not_valid_format;
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION:
errorMessageResId = R.string.wifi_dpp_failure_authentication_or_configuration;
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE:
errorMessageResId = R.string.wifi_dpp_failure_not_compatible;
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION:
errorMessageResId = R.string.wifi_dpp_failure_authentication_or_configuration;
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY:
if (code == mLatestStatusCode) {
throw(new IllegalStateException("stopEasyConnectSession and try again for"
+ "EASY_CONNECT_EVENT_FAILURE_BUSY but still failed"));
}
mLatestStatusCode = code;
final WifiManager wifiManager =
getContext().getSystemService(WifiManager.class);
wifiManager.stopEasyConnectSession();
startWifiDppEnrolleeInitiator(mWifiQrCode);
return;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT:
errorMessageResId = R.string.wifi_dpp_failure_timeout;
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC:
errorMessageResId = R.string.wifi_dpp_failure_generic;
break;
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED:
throw(new IllegalStateException("EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED" +
" should be a configurator only error"));
case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK:
throw(new IllegalStateException("EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK" +
" should be a configurator only error"));
default:
throw(new IllegalStateException("Unexpected Wi-Fi DPP error"));
}
mLatestStatusCode = code;
updateEnrolleeSummary();
showErrorMessageAndRestartCamera(errorMessageResId);
}
@Override
public void onProgress(int code) {
// Do nothing
}
}
private void startWifiDppEnrolleeInitiator(WifiQrCode wifiQrCode) {
final WifiDppInitiatorViewModel model =
new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class);
model.startEasyConnectAsEnrolleeInitiator(wifiQrCode.getQrCode());
}
@Override
public void onSuccess() {
final Intent resultIntent = new Intent();
resultIntent.putExtra(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration);
final Activity hostActivity = getActivity();
if (hostActivity == null) return;
if (mWifiPermissionChecker == null) {
mWifiPermissionChecker = new WifiPermissionChecker(hostActivity);
}
if (!mWifiPermissionChecker.canAccessWifiState()) {
Log.w(TAG, "Calling package does not have ACCESS_WIFI_STATE permission for result.");
EventLog.writeEvent(0x534e4554, "187176859",
mWifiPermissionChecker.getLaunchedPackage(), "no ACCESS_WIFI_STATE permission");
hostActivity.finish();
return;
}
if (!mWifiPermissionChecker.canAccessFineLocation()) {
Log.w(TAG, "Calling package does not have ACCESS_FINE_LOCATION permission for result.");
EventLog.writeEvent(0x534e4554, "187176859",
mWifiPermissionChecker.getLaunchedPackage(),
"no ACCESS_FINE_LOCATION permission");
hostActivity.finish();
return;
}
hostActivity.setResult(Activity.RESULT_OK, resultIntent);
hostActivity.finish();
}
@Override
public void onFailure(int reason) {
Log.d(TAG, "Wi-Fi connect onFailure reason - " + reason);
showErrorMessageAndRestartCamera(R.string.wifi_dpp_check_connection_try_again);
}
// Check is Easy Connect handshaking or not
private boolean isWifiDppHandshaking() {
final WifiDppInitiatorViewModel model =
new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class);
return model.isWifiDppHandshaking();
}
/**
* To resume camera decoding task after handshake fail or Wi-Fi connection fail.
*/
private void restartCamera() {
if (mCamera == null) {
Log.d(TAG, "mCamera is not available for restarting camera");
return;
}
if (mCamera.isDecodeTaskAlive()) {
mCamera.stop();
}
final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
if (surfaceTexture == null) {
throw new IllegalStateException("SurfaceTexture is not ready for restarting camera");
}
mCamera.start(surfaceTexture);
}
private void updateEnrolleeSummary() {
if (isWifiDppHandshaking()) {
mSummary.setText(R.string.wifi_dpp_connecting);
} else {
String description;
if (TextUtils.isEmpty(mSsid)) {
description = getString(R.string.wifi_dpp_scan_qr_code_join_unknown_network, mSsid);
} else {
description = getString(R.string.wifi_dpp_scan_qr_code_join_network, mSsid);
}
mSummary.setText(description);
}
}
@VisibleForTesting
protected boolean isDecodeTaskAlive() {
return mCamera != null && mCamera.isDecodeTaskAlive();
}
@Override
protected boolean isFooterAvailable() {
return false;
}
}

View File

@@ -0,0 +1,582 @@
/*
* Copyright (C) 2018 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.dpp;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.hardware.biometrics.BiometricPrompt;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settingslib.wifi.AccessPoint;
import com.android.wifitrackerlib.WifiEntry;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
/**
* Here are the items shared by both WifiDppConfiguratorActivity & WifiDppEnrolleeActivity
*
* @see WifiQrCode
*/
public class WifiDppUtils {
/**
* The fragment tag specified to FragmentManager for container activities to manage fragments.
*/
static final String TAG_FRAGMENT_QR_CODE_SCANNER = "qr_code_scanner_fragment";
/**
* @see #TAG_FRAGMENT_QR_CODE_SCANNER
*/
static final String TAG_FRAGMENT_QR_CODE_GENERATOR = "qr_code_generator_fragment";
/**
* @see #TAG_FRAGMENT_QR_CODE_SCANNER
*/
static final String TAG_FRAGMENT_CHOOSE_SAVED_WIFI_NETWORK =
"choose_saved_wifi_network_fragment";
/**
* @see #TAG_FRAGMENT_QR_CODE_SCANNER
*/
static final String TAG_FRAGMENT_ADD_DEVICE = "add_device_fragment";
/** The data is one of the static String SECURITY_* in {@link WifiQrCode} */
static final String EXTRA_WIFI_SECURITY = "security";
/** The data corresponding to {@code WifiConfiguration} SSID */
static final String EXTRA_WIFI_SSID = "ssid";
/** The data corresponding to {@code WifiConfiguration} preSharedKey */
static final String EXTRA_WIFI_PRE_SHARED_KEY = "preSharedKey";
/** The data corresponding to {@code WifiConfiguration} hiddenSSID */
static final String EXTRA_WIFI_HIDDEN_SSID = "hiddenSsid";
/** The data corresponding to {@code WifiConfiguration} networkId */
static final String EXTRA_WIFI_NETWORK_ID = "networkId";
/** The data to recognize if it's a Wi-Fi hotspot for configuration */
static final String EXTRA_IS_HOTSPOT = "isHotspot";
/**
* Default status code for Easy Connect
*/
static final int EASY_CONNECT_EVENT_FAILURE_NONE = 0;
/**
* Success status code for Easy Connect.
*/
static final int EASY_CONNECT_EVENT_SUCCESS = 1;
private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3);
private static final String AES_CBC_PKCS7_PADDING = "AES/CBC/PKCS7Padding";
/**
* Returns whether the device support WiFi DPP.
*/
static boolean isWifiDppEnabled(Context context) {
final WifiManager manager = context.getSystemService(WifiManager.class);
return manager.isEasyConnectSupported();
}
/**
* Returns an intent to launch QR code scanner for Wi-Fi DPP enrollee.
*
* After enrollee success, the callee activity will return connecting WifiConfiguration by
* putExtra {@code WifiDialogActivity.KEY_WIFI_CONFIGURATION} for
* {@code Activity#setResult(int resultCode, Intent data)}. The calling object should check
* if it's available before using it.
*
* @param ssid The data corresponding to {@code WifiConfiguration} SSID
* @return Intent for launching QR code scanner
*/
public static Intent getEnrolleeQrCodeScannerIntent(Context context, String ssid) {
final Intent intent = new Intent(context, WifiDppEnrolleeActivity.class);
intent.setAction(WifiDppEnrolleeActivity.ACTION_ENROLLEE_QR_CODE_SCANNER);
if (!TextUtils.isEmpty(ssid)) {
intent.putExtra(EXTRA_WIFI_SSID, ssid);
}
return intent;
}
private static String getPresharedKey(WifiManager wifiManager,
WifiConfiguration wifiConfiguration) {
final List<WifiConfiguration> privilegedWifiConfigurations =
wifiManager.getPrivilegedConfiguredNetworks();
for (WifiConfiguration privilegedWifiConfiguration : privilegedWifiConfigurations) {
if (privilegedWifiConfiguration.networkId == wifiConfiguration.networkId) {
// WEP uses a shared key hence the AuthAlgorithm.SHARED is used
// to identify it.
if (wifiConfiguration.allowedKeyManagement.get(KeyMgmt.NONE)
&& wifiConfiguration.allowedAuthAlgorithms.get(
WifiConfiguration.AuthAlgorithm.SHARED)) {
return privilegedWifiConfiguration
.wepKeys[privilegedWifiConfiguration.wepTxKeyIndex];
} else {
return privilegedWifiConfiguration.preSharedKey;
}
}
}
return wifiConfiguration.preSharedKey;
}
static String removeFirstAndLastDoubleQuotes(String str) {
if (TextUtils.isEmpty(str)) {
return str;
}
int begin = 0;
int end = str.length() - 1;
if (str.charAt(begin) == '\"') {
begin++;
}
if (str.charAt(end) == '\"') {
end--;
}
return str.substring(begin, end+1);
}
static String getSecurityString(WifiConfiguration config) {
if (config.allowedKeyManagement.get(KeyMgmt.SAE)) {
return WifiQrCode.SECURITY_SAE;
}
if (config.allowedKeyManagement.get(KeyMgmt.OWE)) {
return WifiQrCode.SECURITY_NO_PASSWORD;
}
if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK) ||
config.allowedKeyManagement.get(KeyMgmt.WPA2_PSK)) {
return WifiQrCode.SECURITY_WPA_PSK;
}
return (config.wepKeys[0] == null) ?
WifiQrCode.SECURITY_NO_PASSWORD : WifiQrCode.SECURITY_WEP;
}
static String getSecurityString(WifiEntry wifiEntry) {
final int security = wifiEntry.getSecurity();
switch (security) {
case WifiEntry.SECURITY_SAE:
return WifiQrCode.SECURITY_SAE;
case WifiEntry.SECURITY_PSK:
return WifiQrCode.SECURITY_WPA_PSK;
case WifiEntry.SECURITY_WEP:
return WifiQrCode.SECURITY_WEP;
case WifiEntry.SECURITY_OWE:
case WifiEntry.SECURITY_NONE:
default:
return WifiQrCode.SECURITY_NO_PASSWORD;
}
}
/**
* Returns an intent to launch QR code generator. It may return null if the security is not
* supported by QR code generator.
*
* Do not use this method for Wi-Fi hotspot network, use
* {@code getHotspotConfiguratorIntentOrNull} instead.
*
* @param context The context to use for the content resolver
* @param wifiManager An instance of {@link WifiManager}
* @param accessPoint An instance of {@link AccessPoint}
* @return Intent for launching QR code generator
*/
public static Intent getConfiguratorQrCodeGeneratorIntentOrNull(Context context,
WifiManager wifiManager, AccessPoint accessPoint) {
final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
if (isSupportConfiguratorQrCodeGenerator(context, accessPoint)) {
intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
} else {
return null;
}
final WifiConfiguration wifiConfiguration = accessPoint.getConfig();
setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);
// For a transition mode Wi-Fi AP, creates a QR code that's compatible with more devices
if (accessPoint.isPskSaeTransitionMode()) {
intent.putExtra(EXTRA_WIFI_SECURITY, WifiQrCode.SECURITY_WPA_PSK);
}
return intent;
}
/**
* Returns an intent to launch QR code generator. It may return null if the security is not
* supported by QR code generator.
*
* Do not use this method for Wi-Fi hotspot network, use
* {@code getHotspotConfiguratorIntentOrNull} instead.
*
* @param context The context to use for the content resolver
* @param wifiManager An instance of {@link WifiManager}
* @param wifiEntry An instance of {@link WifiEntry}
* @return Intent for launching QR code generator
*/
public static Intent getConfiguratorQrCodeGeneratorIntentOrNull(Context context,
WifiManager wifiManager, WifiEntry wifiEntry) {
final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
if (wifiEntry.canShare()) {
intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
} else {
return null;
}
final WifiConfiguration wifiConfiguration = wifiEntry.getWifiConfiguration();
setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);
return intent;
}
/**
* Returns an intent to launch QR code scanner. It may return null if the security is not
* supported by QR code scanner.
*
* @param context The context to use for the content resolver
* @param wifiManager An instance of {@link WifiManager}
* @param wifiEntry An instance of {@link WifiEntry}
* @return Intent for launching QR code scanner
*/
public static Intent getConfiguratorQrCodeScannerIntentOrNull(Context context,
WifiManager wifiManager, WifiEntry wifiEntry) {
final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
if (wifiEntry.canEasyConnect()) {
intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_SCANNER);
} else {
return null;
}
final WifiConfiguration wifiConfiguration = wifiEntry.getWifiConfiguration();
setConfiguratorIntentExtra(intent, wifiManager, wifiConfiguration);
if (wifiConfiguration.networkId == WifiConfiguration.INVALID_NETWORK_ID) {
throw new IllegalArgumentException("Invalid network ID");
} else {
intent.putExtra(EXTRA_WIFI_NETWORK_ID, wifiConfiguration.networkId);
}
return intent;
}
/**
* Returns an intent to launch QR code generator for the Wi-Fi hotspot. It may return null if
* the security is not supported by QR code generator.
*
* @param context The context to use for the content resolver
* @param wifiManager An instance of {@link WifiManager}
* @param softApConfiguration {@link SoftApConfiguration} of the Wi-Fi hotspot
* @return Intent for launching QR code generator
*/
public static Intent getHotspotConfiguratorIntentOrNull(Context context,
WifiManager wifiManager, SoftApConfiguration softApConfiguration) {
final Intent intent = new Intent(context, WifiDppConfiguratorActivity.class);
if (isSupportHotspotConfiguratorQrCodeGenerator(softApConfiguration)) {
intent.setAction(WifiDppConfiguratorActivity.ACTION_CONFIGURATOR_QR_CODE_GENERATOR);
} else {
return null;
}
final String ssid = removeFirstAndLastDoubleQuotes(softApConfiguration.getSsid());
String security;
final int securityType = softApConfiguration.getSecurityType();
if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) {
security = WifiQrCode.SECURITY_SAE;
} else if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
|| securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
security = WifiQrCode.SECURITY_WPA_PSK;
} else {
security = WifiQrCode.SECURITY_NO_PASSWORD;
}
// When the value of this key is read, the actual key is not returned, just a "*".
// Call privileged system API to obtain actual key.
final String preSharedKey = removeFirstAndLastDoubleQuotes(
softApConfiguration.getPassphrase());
if (!TextUtils.isEmpty(ssid)) {
intent.putExtra(EXTRA_WIFI_SSID, ssid);
}
if (!TextUtils.isEmpty(security)) {
intent.putExtra(EXTRA_WIFI_SECURITY, security);
}
if (!TextUtils.isEmpty(preSharedKey)) {
intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey);
}
intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, softApConfiguration.isHiddenSsid());
intent.putExtra(EXTRA_WIFI_NETWORK_ID, WifiConfiguration.INVALID_NETWORK_ID);
intent.putExtra(EXTRA_IS_HOTSPOT, true);
return intent;
}
/**
* Set all extra except {@code EXTRA_WIFI_NETWORK_ID} for the intent to
* launch configurator activity later.
*
* @param intent the target to set extra
* @param wifiManager an instance of {@code WifiManager}
* @param wifiConfiguration the Wi-Fi network for launching configurator activity
*/
private static void setConfiguratorIntentExtra(Intent intent, WifiManager wifiManager,
WifiConfiguration wifiConfiguration) {
final String ssid = removeFirstAndLastDoubleQuotes(wifiConfiguration.SSID);
final String security = getSecurityString(wifiConfiguration);
// When the value of this key is read, the actual key is not returned, just a "*".
// Call privileged system API to obtain actual key.
final String preSharedKey = removeFirstAndLastDoubleQuotes(getPresharedKey(wifiManager,
wifiConfiguration));
if (!TextUtils.isEmpty(ssid)) {
intent.putExtra(EXTRA_WIFI_SSID, ssid);
}
if (!TextUtils.isEmpty(security)) {
intent.putExtra(EXTRA_WIFI_SECURITY, security);
}
if (!TextUtils.isEmpty(preSharedKey)) {
intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey);
}
intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, wifiConfiguration.hiddenSSID);
}
/**
* Checks whether the device is unlocked recently.
*
* @param keyStoreAlias key
* @param seconds how many seconds since the device is unlocked
* @return whether the device is unlocked within the time
*/
public static boolean isUnlockedWithinSeconds(String keyStoreAlias, int seconds) {
try {
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS7_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, generateSecretKey(keyStoreAlias, seconds));
cipher.doFinal();
return true;
} catch (NoSuchPaddingException
| IllegalBlockSizeException
| NoSuchAlgorithmException
| BadPaddingException
| InvalidKeyException e) {
return false;
}
}
private static SecretKey generateSecretKey(String keyStoreAlias, int seconds) {
KeyGenParameterSpec spec = new KeyGenParameterSpec
.Builder(keyStoreAlias, KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setUserAuthenticationRequired(true)
.setUserAuthenticationParameters(
seconds,
KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG)
.build();
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
keyGenerator.init(spec);
return keyGenerator.generateKey();
} catch (NoSuchAlgorithmException
| InvalidAlgorithmParameterException e) {
return null;
}
}
/**
* Shows authentication screen to confirm credentials (pin, pattern or password) for the current
* user of the device.
*
* @param context The {@code Context} used to get {@code KeyguardManager} service
* @param successRunnable The {@code Runnable} which will be executed if the user does not setup
* device security or if lock screen is unlocked
*/
public static void showLockScreen(Context context, Runnable successRunnable) {
final KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(
Context.KEYGUARD_SERVICE);
if (keyguardManager.isKeyguardSecure()) {
final BiometricPrompt.AuthenticationCallback authenticationCallback =
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
BiometricPrompt.AuthenticationResult result) {
successRunnable.run();
}
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
//Do nothing
}
};
final int userId = UserHandle.myUserId();
final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context)
.setTitle(context.getText(R.string.wifi_dpp_lockscreen_title));
if (keyguardManager.isDeviceSecure()) {
builder.setDeviceCredentialAllowed(true);
builder.setTextForDeviceCredential(
null /* title */,
Utils.getConfirmCredentialStringForUser(
context, userId, Utils.getCredentialType(context, userId)),
null /* description */);
}
final BiometricPrompt bp = builder.build();
final Handler handler = new Handler(Looper.getMainLooper());
bp.authenticate(new CancellationSignal(),
runnable -> handler.post(runnable),
authenticationCallback);
} else {
successRunnable.run();
}
}
/**
* Checks if QR code generator supports to config other devices with the Wi-Fi network
*
* @param context The context to use for {@code WifiManager}
* @param accessPoint The {@link AccessPoint} of the Wi-Fi network
*/
public static boolean isSupportConfiguratorQrCodeGenerator(Context context,
AccessPoint accessPoint) {
if (accessPoint.isPasspoint()) {
return false;
}
return isSupportZxing(context, accessPoint.getSecurity());
}
/**
* Checks if this device supports to be configured by the Wi-Fi network of the security
*
* @param context The context to use for {@code WifiManager}
* @param wifiEntrySecurity The security constants defined in {@link WifiEntry}
*/
public static boolean isSupportEnrolleeQrCodeScanner(Context context, int wifiEntrySecurity) {
return isSupportWifiDpp(context, wifiEntrySecurity)
|| isSupportZxing(context, wifiEntrySecurity);
}
private static boolean isSupportHotspotConfiguratorQrCodeGenerator(
SoftApConfiguration softApConfiguration) {
final int securityType = softApConfiguration.getSecurityType();
return securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE
|| securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION
|| securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
|| securityType == SoftApConfiguration.SECURITY_TYPE_OPEN;
}
private static boolean isSupportWifiDpp(Context context, int wifiEntrySecurity) {
if (!isWifiDppEnabled(context)) {
return false;
}
// DPP 1.0 only supports SAE and PSK.
final WifiManager wifiManager = context.getSystemService(WifiManager.class);
switch (wifiEntrySecurity) {
case WifiEntry.SECURITY_SAE:
if (wifiManager.isWpa3SaeSupported()) {
return true;
}
break;
case WifiEntry.SECURITY_PSK:
return true;
default:
}
return false;
}
private static boolean isSupportZxing(Context context, int wifiEntrySecurity) {
final WifiManager wifiManager = context.getSystemService(WifiManager.class);
switch (wifiEntrySecurity) {
case WifiEntry.SECURITY_PSK:
case WifiEntry.SECURITY_WEP:
case WifiEntry.SECURITY_NONE:
return true;
case WifiEntry.SECURITY_SAE:
if (wifiManager.isWpa3SaeSupported()) {
return true;
}
break;
case WifiEntry.SECURITY_OWE:
if (wifiManager.isEnhancedOpenSupported()) {
return true;
}
break;
default:
}
return false;
}
static void triggerVibrationForQrCodeRecognition(Context context) {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator == null) {
return;
}
vibrator.vibrate(VibrationEffect.createOneShot(
VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(),
VibrationEffect.DEFAULT_AMPLITUDE));
}
@WifiEntry.Security
static int getSecurityTypeFromWifiConfiguration(WifiConfiguration config) {
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
return WifiEntry.SECURITY_SAE;
}
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
return WifiEntry.SECURITY_PSK;
}
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
return WifiEntry.SECURITY_EAP_SUITE_B;
}
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
|| config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
return WifiEntry.SECURITY_EAP;
}
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
return WifiEntry.SECURITY_OWE;
}
return (config.wepKeys[0] != null) ? WifiEntry.SECURITY_WEP : WifiEntry.SECURITY_NONE;
}
}

View File

@@ -0,0 +1,316 @@
/*
* Copyright (C) 2018 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.dpp;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_NO_PASSWORD;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_SAE;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WEP;
import static com.android.settings.wifi.dpp.WifiQrCode.SECURITY_WPA_PSK;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
/**
* Wraps the parameters of ZXing reader library's Wi-Fi Network config format.
* Please check {@code WifiQrCode} for detail of the format.
*
* Checks below members of {@code WifiDppUtils} for more information.
* EXTRA_WIFI_SECURITY / EXTRA_WIFI_SSID / EXTRA_WIFI_PRE_SHARED_KEY / EXTRA_WIFI_HIDDEN_SSID /
* EXTRA_QR_CODE
*/
public class WifiNetworkConfig {
static final String FAKE_SSID = "fake network";
static final String FAKE_PASSWORD = "password";
private static final String TAG = "WifiNetworkConfig";
private String mSecurity;
private String mSsid;
private String mPreSharedKey;
private boolean mHiddenSsid;
private int mNetworkId;
private boolean mIsHotspot;
@VisibleForTesting
WifiNetworkConfig(String security, String ssid, String preSharedKey,
boolean hiddenSsid, int networkId, boolean isHotspot) {
mSecurity = security;
mSsid = ssid;
mPreSharedKey = preSharedKey;
mHiddenSsid = hiddenSsid;
mNetworkId = networkId;
mIsHotspot = isHotspot;
}
public WifiNetworkConfig(WifiNetworkConfig config) {
mSecurity = config.mSecurity;
mSsid = config.mSsid;
mPreSharedKey = config.mPreSharedKey;
mHiddenSsid = config.mHiddenSsid;
mNetworkId = config.mNetworkId;
mIsHotspot = config.mIsHotspot;
}
/**
* Wi-Fi DPP activities should implement this interface for fragments to retrieve the
* WifiNetworkConfig for configuration
*/
public interface Retriever {
WifiNetworkConfig getWifiNetworkConfig();
}
/**
* Retrieve WifiNetworkConfig from below 2 intents
*
* android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR
* android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_SCANNER
*/
static WifiNetworkConfig getValidConfigOrNull(Intent intent) {
final String security = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_SECURITY);
final String ssid = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_SSID);
final String preSharedKey = intent.getStringExtra(WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY);
final boolean hiddenSsid = intent.getBooleanExtra(WifiDppUtils.EXTRA_WIFI_HIDDEN_SSID,
false);
final int networkId = intent.getIntExtra(WifiDppUtils.EXTRA_WIFI_NETWORK_ID,
WifiConfiguration.INVALID_NETWORK_ID);
final boolean isHotspot = intent.getBooleanExtra(WifiDppUtils.EXTRA_IS_HOTSPOT, false);
return getValidConfigOrNull(security, ssid, preSharedKey, hiddenSsid, networkId, isHotspot);
}
static WifiNetworkConfig getValidConfigOrNull(String security, String ssid,
String preSharedKey, boolean hiddenSsid, int networkId, boolean isHotspot) {
if (!isValidConfig(security, ssid, preSharedKey, hiddenSsid)) {
return null;
}
return new WifiNetworkConfig(security, ssid, preSharedKey, hiddenSsid, networkId,
isHotspot);
}
static boolean isValidConfig(WifiNetworkConfig config) {
if (config == null) {
return false;
} else {
return isValidConfig(config.mSecurity, config.mSsid, config.mPreSharedKey,
config.mHiddenSsid);
}
}
static boolean isValidConfig(String security, String ssid, String preSharedKey,
boolean hiddenSsid) {
if (!TextUtils.isEmpty(security) && !SECURITY_NO_PASSWORD.equals(security)) {
if (TextUtils.isEmpty(preSharedKey)) {
return false;
}
}
if (!hiddenSsid && TextUtils.isEmpty(ssid)) {
return false;
}
return true;
}
/**
* Escaped special characters "\", ";", ":", "," with a backslash
* See https://github.com/zxing/zxing/wiki/Barcode-Contents
*/
private String escapeSpecialCharacters(String str) {
if (TextUtils.isEmpty(str)) {
return str;
}
StringBuilder buf = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (ch =='\\' || ch == ',' || ch == ';' || ch == ':') {
buf.append('\\');
}
buf.append(ch);
}
return buf.toString();
}
/**
* Construct a barcode string for WiFi network login.
* See https://en.wikipedia.org/wiki/QR_code#WiFi_network_login
*/
String getQrCode() {
final String empty = "";
return new StringBuilder("WIFI:")
.append("S:")
.append(escapeSpecialCharacters(mSsid))
.append(";")
.append("T:")
.append(TextUtils.isEmpty(mSecurity) ? empty : mSecurity)
.append(";")
.append("P:")
.append(TextUtils.isEmpty(mPreSharedKey) ? empty
: escapeSpecialCharacters(mPreSharedKey))
.append(";")
.append("H:")
.append(mHiddenSsid)
.append(";;")
.toString();
}
public String getSecurity() {
return mSecurity;
}
public String getSsid() {
return mSsid;
}
public String getPreSharedKey() {
return mPreSharedKey;
}
public boolean getHiddenSsid() {
return mHiddenSsid;
}
public int getNetworkId() {
return mNetworkId;
}
public boolean isHotspot() {
return mIsHotspot;
}
public boolean isSupportWifiDpp(Context context) {
if (!WifiDppUtils.isWifiDppEnabled(context)) {
return false;
}
if (TextUtils.isEmpty(mSecurity)) {
return false;
}
// DPP 1.0 only supports SAE and PSK.
final WifiManager wifiManager = context.getSystemService(WifiManager.class);
switch (mSecurity) {
case SECURITY_SAE:
if (wifiManager.isWpa3SaeSupported()) {
return true;
}
break;
case SECURITY_WPA_PSK:
return true;
default:
}
return false;
}
/**
* This is a simplified method from {@code WifiConfigController.getConfig()}
*
* @return When it's a open network, returns 2 WifiConfiguration in the List, the 1st is
* open network and the 2nd is enhanced open network. Returns 1 WifiConfiguration in the
* List for all other supported Wi-Fi securities.
*/
List<WifiConfiguration> getWifiConfigurations() {
final List<WifiConfiguration> wifiConfigurations = new ArrayList<>();
if (!isValidConfig(this)) {
return wifiConfigurations;
}
if (TextUtils.isEmpty(mSecurity) || SECURITY_NO_PASSWORD.equals(mSecurity)) {
// TODO (b/129835824): we add both open network and enhanced open network to WifiManager
// for android Q, should improve it in the future.
final WifiConfiguration openNetworkWifiConfiguration = getBasicWifiConfiguration();
openNetworkWifiConfiguration.allowedKeyManagement.set(KeyMgmt.NONE);
wifiConfigurations.add(openNetworkWifiConfiguration);
final WifiConfiguration enhancedOpenNetworkWifiConfiguration =
getBasicWifiConfiguration();
enhancedOpenNetworkWifiConfiguration
.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
wifiConfigurations.add(enhancedOpenNetworkWifiConfiguration);
return wifiConfigurations;
}
final WifiConfiguration wifiConfiguration = getBasicWifiConfiguration();
if (mSecurity.startsWith(SECURITY_WEP)) {
wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
// WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
final int length = mPreSharedKey.length();
if ((length == 10 || length == 26 || length == 58)
&& mPreSharedKey.matches("[0-9A-Fa-f]*")) {
wifiConfiguration.wepKeys[0] = mPreSharedKey;
} else {
wifiConfiguration.wepKeys[0] = addQuotationIfNeeded(mPreSharedKey);
}
} else if (mSecurity.startsWith(SECURITY_WPA_PSK)) {
wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
if (mPreSharedKey.matches("[0-9A-Fa-f]{64}")) {
wifiConfiguration.preSharedKey = mPreSharedKey;
} else {
wifiConfiguration.preSharedKey = addQuotationIfNeeded(mPreSharedKey);
}
} else if (mSecurity.startsWith(SECURITY_SAE)) {
wifiConfiguration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
if (mPreSharedKey.length() != 0) {
wifiConfiguration.preSharedKey = addQuotationIfNeeded(mPreSharedKey);
}
} else {
Log.w(TAG, "Unsupported security");
return wifiConfigurations;
}
wifiConfigurations.add(wifiConfiguration);
return wifiConfigurations;
}
private WifiConfiguration getBasicWifiConfiguration() {
final WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifiConfiguration.SSID = addQuotationIfNeeded(mSsid);
wifiConfiguration.hiddenSSID = mHiddenSsid;
wifiConfiguration.networkId = mNetworkId;
return wifiConfiguration;
}
private String addQuotationIfNeeded(String input) {
if (TextUtils.isEmpty(input)) {
return "";
}
if (input.length() >= 2 && input.startsWith("\"") && input.endsWith("\"")) {
return input;
}
StringBuilder sb = new StringBuilder();
sb.append("\"").append(input).append("\"");
return sb.toString();
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright (C) 2019 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.dpp;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
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.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.wifi.AddNetworkFragment;
import com.android.settings.wifi.WifiEntryPreference;
import com.android.wifitrackerlib.SavedNetworkTracker;
import com.android.wifitrackerlib.WifiEntry;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.List;
import java.util.stream.Collectors;
public class WifiNetworkListFragment extends SettingsPreferenceFragment implements
SavedNetworkTracker.SavedNetworkTrackerCallback, Preference.OnPreferenceClickListener {
private static final String TAG = "WifiNetworkListFragment";
@VisibleForTesting static final String WIFI_CONFIG_KEY = "wifi_config_key";
private static final String PREF_KEY_ACCESS_POINTS = "access_points";
@VisibleForTesting
static final int ADD_NETWORK_REQUEST = 1;
@VisibleForTesting PreferenceCategory mPreferenceGroup;
@VisibleForTesting Preference mAddPreference;
@VisibleForTesting WifiManager mWifiManager;
private WifiManager.ActionListener mSaveListener;
// 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 SavedNetworkTracker mSavedNetworkTracker;
@VisibleForTesting HandlerThread mWorkerThread;
// Container Activity must implement this interface
public interface OnChooseNetworkListener {
void onChooseNetwork(WifiNetworkConfig wifiNetworkConfig);
}
@VisibleForTesting OnChooseNetworkListener mOnChooseNetworkListener;
@Override
public int getMetricsCategory() {
return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
}
private static class DisableUnreachableWifiEntryPreference extends WifiEntryPreference {
DisableUnreachableWifiEntryPreference(Context context, WifiEntry entry) {
super(context, entry);
}
@Override
public void onUpdated() {
super.onUpdated();
this.setEnabled(getWifiEntry().getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE);
}
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(context instanceof OnChooseNetworkListener)) {
throw new IllegalArgumentException("Invalid context type");
}
mOnChooseNetworkListener = (OnChooseNetworkListener) context;
}
@Override
public void onDetach() {
mOnChooseNetworkListener = null;
super.onDetach();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final Context context = getContext();
mWifiManager = context.getSystemService(WifiManager.class);
mSaveListener = new WifiManager.ActionListener() {
@Override
public void onSuccess() {
// Do nothing.
}
@Override
public void onFailure(int reason) {
final Activity activity = getActivity();
if (activity != null && !activity.isFinishing()) {
Toast.makeText(activity, R.string.wifi_failed_save_message,
Toast.LENGTH_SHORT).show();
}
}
};
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();
}
};
mSavedNetworkTracker = new SavedNetworkTracker(getSettingsLifecycle(), context,
context.getSystemService(WifiManager.class),
context.getSystemService(ConnectivityManager.class),
new Handler(Looper.getMainLooper()),
mWorkerThread.getThreadHandler(),
elapsedRealtimeClock,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
this);
}
@Override
public void onDestroyView() {
mWorkerThread.quit();
super.onDestroyView();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ADD_NETWORK_REQUEST && resultCode == Activity.RESULT_OK) {
final WifiConfiguration wifiConfiguration = data.getParcelableExtra(WIFI_CONFIG_KEY);
if (wifiConfiguration != null) {
mWifiManager.save(wifiConfiguration, mSaveListener);
}
}
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.wifi_dpp_network_list);
mPreferenceGroup = findPreference(PREF_KEY_ACCESS_POINTS);
mAddPreference = new Preference(getPrefContext());
mAddPreference.setIcon(R.drawable.ic_add_24dp);
mAddPreference.setTitle(R.string.wifi_add_network);
mAddPreference.setOnPreferenceClickListener(this);
}
/** Called when the state of Wifi has changed. */
@Override
public void onSavedWifiEntriesChanged() {
final List<WifiEntry> savedWifiEntries = mSavedNetworkTracker.getSavedWifiEntries().stream()
.filter(entry -> isValidForDppConfiguration(entry))
.collect(Collectors.toList());
int index = 0;
mPreferenceGroup.removeAll();
for (WifiEntry savedEntry : savedWifiEntries) {
final DisableUnreachableWifiEntryPreference preference =
new DisableUnreachableWifiEntryPreference(getContext(), savedEntry);
preference.setOnPreferenceClickListener(this);
preference.setEnabled(savedEntry.getLevel() != WifiEntry.WIFI_LEVEL_UNREACHABLE);
preference.setOrder(index++);
mPreferenceGroup.addPreference(preference);
}
mAddPreference.setOrder(index);
mPreferenceGroup.addPreference(mAddPreference);
}
@Override
public void onSubscriptionWifiEntriesChanged() {
// Do nothing.
}
@Override
public void onWifiStateChanged() {
// Do nothing.
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference instanceof WifiEntryPreference) {
final WifiEntry selectedWifiEntry = ((WifiEntryPreference) preference).getWifiEntry();
// Launch WifiDppAddDeviceFragment to start DPP in Configurator-Initiator role.
final WifiConfiguration wifiConfig = selectedWifiEntry.getWifiConfiguration();
if (wifiConfig == null) {
throw new IllegalArgumentException("Invalid access point");
}
final WifiNetworkConfig networkConfig = WifiNetworkConfig.getValidConfigOrNull(
WifiDppUtils.getSecurityString(selectedWifiEntry),
wifiConfig.getPrintableSsid(), wifiConfig.preSharedKey, wifiConfig.hiddenSSID,
wifiConfig.networkId, /* isHotspot */ false);
if (mOnChooseNetworkListener != null) {
mOnChooseNetworkListener.onChooseNetwork(networkConfig);
}
} else if (preference == mAddPreference) {
new SubSettingLauncher(getContext())
.setTitleRes(R.string.wifi_add_network)
.setDestination(AddNetworkFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.setResultListener(this, ADD_NETWORK_REQUEST)
.launch();
} else {
return super.onPreferenceTreeClick(preference);
}
return true;
}
private boolean isValidForDppConfiguration(WifiEntry wifiEntry) {
final int security = wifiEntry.getSecurity();
// DPP 1.0 only support PSK and SAE.
return security == WifiEntry.SECURITY_PSK || security == WifiEntry.SECURITY_SAE;
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2018 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.dpp;
import android.net.wifi.WifiConfiguration;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* Supports to parse 2 types of QR code
*
* 1. Standard Wi-Fi DPP bootstrapping information or
* 2. ZXing reader library's Wi-Fi Network config format described in
* https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11
*
* ZXing reader library's Wi-Fi Network config format example:
*
* WIFI:T:WPA;S:mynetwork;P:mypass;;
*
* parameter Example Description
* T WPA Authentication type; can be WEP or WPA, or 'nopass' for no password. Or,
* omit for no password.
* S mynetwork Network SSID. Required. Enclose in double quotes if it is an ASCII name,
* but could be interpreted as hex (i.e. "ABCD")
* P mypass Password, ignored if T is "nopass" (in which case it may be omitted).
* Enclose in double quotes if it is an ASCII name, but could be interpreted as
* hex (i.e. "ABCD")
* H true Optional. True if the network SSID is hidden.
*
*/
public class WifiQrCode {
static final String SCHEME_DPP = "DPP";
static final String SCHEME_ZXING_WIFI_NETWORK_CONFIG = "WIFI";
static final String PREFIX_DPP = "DPP:";
static final String PREFIX_ZXING_WIFI_NETWORK_CONFIG = "WIFI:";
static final String PREFIX_DPP_PUBLIC_KEY = "K:";
static final String PREFIX_DPP_INFORMATION = "I:";
static final String PREFIX_ZXING_SECURITY = "T:";
static final String PREFIX_ZXING_SSID = "S:";
static final String PREFIX_ZXING_PASSWORD = "P:";
static final String PREFIX_ZXING_HIDDEN_SSID = "H:";
static final String DELIMITER_QR_CODE = ";";
// Ignores password if security is SECURITY_NO_PASSWORD or absent
static final String SECURITY_NO_PASSWORD = "nopass"; //open network or OWE
static final String SECURITY_WEP = "WEP";
static final String SECURITY_WPA_PSK = "WPA";
static final String SECURITY_SAE = "SAE";
private String mQrCode;
/**
* SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG
* for ZXing reader library' Wi-Fi Network config format
*/
private String mScheme;
// Data from parsed Wi-Fi DPP QR code
private String mPublicKey;
private String mInformation;
// Data from parsed ZXing reader library's Wi-Fi Network config format
private WifiNetworkConfig mWifiNetworkConfig;
public WifiQrCode(String qrCode) throws IllegalArgumentException {
if (TextUtils.isEmpty(qrCode)) {
throw new IllegalArgumentException("Empty QR code");
}
mQrCode = qrCode;
if (qrCode.startsWith(PREFIX_DPP)) {
mScheme = SCHEME_DPP;
parseWifiDppQrCode(qrCode);
} else if (qrCode.startsWith(PREFIX_ZXING_WIFI_NETWORK_CONFIG)) {
mScheme = SCHEME_ZXING_WIFI_NETWORK_CONFIG;
parseZxingWifiQrCode(qrCode);
} else {
throw new IllegalArgumentException("Invalid scheme");
}
}
/** Parses Wi-Fi DPP QR code string */
private void parseWifiDppQrCode(String qrCode) throws IllegalArgumentException {
List<String> keyValueList = getKeyValueList(qrCode, PREFIX_DPP, DELIMITER_QR_CODE);
String publicKey = getValueOrNull(keyValueList, PREFIX_DPP_PUBLIC_KEY);
if (TextUtils.isEmpty(publicKey)) {
throw new IllegalArgumentException("Invalid format");
}
mPublicKey = publicKey;
mInformation = getValueOrNull(keyValueList, PREFIX_DPP_INFORMATION);
}
/** Parses ZXing reader library's Wi-Fi Network config format */
private void parseZxingWifiQrCode(String qrCode) throws IllegalArgumentException {
List<String> keyValueList = getKeyValueList(qrCode, PREFIX_ZXING_WIFI_NETWORK_CONFIG,
DELIMITER_QR_CODE);
String security = getValueOrNull(keyValueList, PREFIX_ZXING_SECURITY);
String ssid = getValueOrNull(keyValueList, PREFIX_ZXING_SSID);
String password = getValueOrNull(keyValueList, PREFIX_ZXING_PASSWORD);
String hiddenSsidString = getValueOrNull(keyValueList, PREFIX_ZXING_HIDDEN_SSID);
boolean hiddenSsid = "true".equalsIgnoreCase(hiddenSsidString);
//"\", ";", "," and ":" are escaped with a backslash "\", should remove at first
security = removeBackSlash(security);
ssid = removeBackSlash(ssid);
password = removeBackSlash(password);
mWifiNetworkConfig = WifiNetworkConfig.getValidConfigOrNull(security, ssid, password,
hiddenSsid, WifiConfiguration.INVALID_NETWORK_ID, /* isHotspot */ false);
if (mWifiNetworkConfig == null) {
throw new IllegalArgumentException("Invalid format");
}
}
/**
* Splits key/value pairs from qrCode
*
* @param qrCode the QR code raw string
* @param prefixQrCode the string before all key/value pairs in qrCode
* @param delimiter the string to split key/value pairs, can't contain a backslash
* @return a list contains string of key/value (e.g. K:key1)
*/
private List<String> getKeyValueList(String qrCode, String prefixQrCode,
String delimiter) {
String keyValueString = qrCode.substring(prefixQrCode.length());
// Should not treat \delimiter as a delimiter
String regex = "(?<!\\\\)" + Pattern.quote(delimiter);
return Arrays.asList(keyValueString.split(regex));
}
private String getValueOrNull(List<String> keyValueList, String prefix) {
for (String keyValue : keyValueList) {
String strippedKeyValue = keyValue.stripLeading();
if (strippedKeyValue.startsWith(prefix)) {
return strippedKeyValue.substring(prefix.length());
}
}
return null;
}
@VisibleForTesting
String removeBackSlash(String input) {
if (input == null) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean backSlash = false;
for (char ch : input.toCharArray()) {
if (ch != '\\') {
sb.append(ch);
backSlash = false;
} else {
if (backSlash) {
sb.append(ch);
backSlash = false;
continue;
}
backSlash = true;
}
}
return sb.toString();
}
String getQrCode() {
return mQrCode;
}
/**
* Uses to check type of QR code
*
* SCHEME_DPP for standard Wi-Fi device provision protocol; SCHEME_ZXING_WIFI_NETWORK_CONFIG
* for ZXing reader library' Wi-Fi Network config format
*/
public String getScheme() {
return mScheme;
}
/** Available when {@code getScheme()} returns SCHEME_DPP */
@VisibleForTesting
String getPublicKey() {
return mPublicKey;
}
/** May be available when {@code getScheme()} returns SCHEME_DPP */
public String getInformation() {
return mInformation;
}
/** Available when {@code getScheme()} returns SCHEME_ZXING_WIFI_NETWORK_CONFIG */
WifiNetworkConfig getWifiNetworkConfig() {
if (mWifiNetworkConfig == null) {
return null;
}
return new WifiNetworkConfig(mWifiNetworkConfig);
}
static WifiQrCode getValidWifiDppQrCodeOrNull(String qrCode) {
WifiQrCode wifiQrCode;
try {
wifiQrCode = new WifiQrCode(qrCode);
} catch(IllegalArgumentException e) {
return null;
}
if (SCHEME_DPP.equals(wifiQrCode.getScheme())) {
return wifiQrCode;
}
return null;
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2023 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.factory;
import android.content.Context;
import android.net.TetheringManager;
import android.net.wifi.WifiManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import com.android.settings.wifi.details.WifiNetworkDetailsViewModel;
import com.android.settings.wifi.repository.SharedConnectivityRepository;
import com.android.settings.wifi.repository.WifiHotspotRepository;
import com.android.settings.wifi.tether.WifiHotspotSecurityViewModel;
import com.android.settings.wifi.tether.WifiHotspotSpeedViewModel;
import com.android.settings.wifi.tether.WifiTetherViewModel;
import org.jetbrains.annotations.NotNull;
/**
* Wi-Fi Feature Provider
*/
public class WifiFeatureProvider {
private static final String TAG = "WifiFeatureProvider";
private final Context mAppContext;
private WifiManager mWifiManager;
private TetheringManager mTetheringManager;
private WifiVerboseLogging mWifiVerboseLogging;
private WifiHotspotRepository mWifiHotspotRepository;
private SharedConnectivityRepository mSharedConnectivityRepository;
public WifiFeatureProvider(@NonNull Context appContext) {
mAppContext = appContext;
}
/**
* Gets WifiManager
*/
public WifiManager getWifiManager() {
if (mWifiManager == null) {
mWifiManager = mAppContext.getSystemService(WifiManager.class);
}
return mWifiManager;
}
/**
* Gets TetheringManager
*/
public TetheringManager getTetheringManager() {
if (mTetheringManager == null) {
mTetheringManager = mAppContext.getSystemService(TetheringManager.class);
verboseLog(TAG, "getTetheringManager():" + mTetheringManager);
}
return mTetheringManager;
}
/**
* Gets WifiVerboseLogging
*/
public WifiVerboseLogging getWifiVerboseLogging() {
if (mWifiVerboseLogging == null) {
mWifiVerboseLogging = new WifiVerboseLogging(mAppContext, getWifiManager());
}
return mWifiVerboseLogging;
}
/**
* Gets WifiHotspotRepository
*/
public WifiHotspotRepository getWifiHotspotRepository() {
if (mWifiHotspotRepository == null) {
mWifiHotspotRepository = new WifiHotspotRepository(mAppContext, getWifiManager(),
getTetheringManager());
verboseLog(TAG, "getWifiHotspotRepository():" + mWifiHotspotRepository);
}
return mWifiHotspotRepository;
}
/**
* Gets SharedConnectivityRepository
*/
public SharedConnectivityRepository getSharedConnectivityRepository() {
if (mSharedConnectivityRepository == null) {
mSharedConnectivityRepository = new SharedConnectivityRepository(mAppContext);
verboseLog(TAG, "getSharedConnectivityRepository():" + mSharedConnectivityRepository);
}
return mSharedConnectivityRepository;
}
/**
* Gets WifiTetherViewModel
*/
public WifiTetherViewModel getWifiTetherViewModel(@NotNull ViewModelStoreOwner owner) {
return new ViewModelProvider(owner).get(WifiTetherViewModel.class);
}
/**
* Gets WifiHotspotSecurityViewModel
*/
public WifiHotspotSecurityViewModel getWifiHotspotSecurityViewModel(
@NotNull ViewModelStoreOwner owner) {
WifiHotspotSecurityViewModel viewModel =
new ViewModelProvider(owner).get(WifiHotspotSecurityViewModel.class);
verboseLog(TAG, "getWifiHotspotSecurityViewModel():" + viewModel);
return viewModel;
}
/**
* Gets WifiHotspotSpeedViewModel
*/
public WifiHotspotSpeedViewModel getWifiHotspotSpeedViewModel(
@NotNull ViewModelStoreOwner owner) {
WifiHotspotSpeedViewModel viewModel =
new ViewModelProvider(owner).get(WifiHotspotSpeedViewModel.class);
verboseLog(TAG, "getWifiHotspotSpeedViewModel():" + viewModel);
return viewModel;
}
/**
* Gets WifiNetworkDetailsViewModel
*/
public WifiNetworkDetailsViewModel getWifiNetworkDetailsViewModel(
@NotNull ViewModelStoreOwner owner) {
WifiNetworkDetailsViewModel viewModel =
new ViewModelProvider(owner).get(WifiNetworkDetailsViewModel.class);
verboseLog(TAG, "getWifiNetworkDetailsViewModel():" + viewModel);
return viewModel;
}
/**
* Send a {@link Log#VERBOSE} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public void verboseLog(@Nullable String tag, @NonNull String msg) {
getWifiVerboseLogging().log(tag, msg);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 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.factory;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Wi-Fi Verbose Logging
*/
public class WifiVerboseLogging {
private static final String TAG = "WifiVerboseLogging";
protected final Context mAppContext;
protected final WifiManager mWifiManager;
protected final boolean mIsVerboseLoggingEnabled;
public WifiVerboseLogging(@NonNull Context appContext, @NonNull WifiManager wifiManager) {
mAppContext = appContext;
mWifiManager = wifiManager;
mIsVerboseLoggingEnabled = wifiManager.isVerboseLoggingEnabled();
Log.v(TAG, "isVerboseLoggingEnabled:" + mIsVerboseLoggingEnabled);
}
/**
* Send a {@link Log#VERBOSE} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
public void log(@Nullable String tag, @NonNull String msg) {
if (mIsVerboseLoggingEnabled) {
Log.v(tag, msg);
}
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright (C) 2022 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.helper;
import android.annotation.TestApi;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import com.android.wifitrackerlib.SavedNetworkTracker;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Helper for saved Wi-Fi networks tracker from WifiTrackerLib.
*/
public class SavedWifiHelper extends WifiTrackerBase {
private static final String TAG = "SavedWifiHelper";
private static final Object sInstanceLock = new Object();
@TestApi
@GuardedBy("sInstanceLock")
private static Map<Context, SavedWifiHelper> sTestInstances;
protected SavedNetworkTracker mSavedNetworkTracker;
/**
* Static method to create a SavedWifiHelper class.
*
* @param context The Context this is associated with.
* @param lifecycle The lifecycle this is associated with.
* @return an instance of {@link SavedWifiHelper} object.
*/
public static SavedWifiHelper getInstance(@NonNull Context context,
@NonNull Lifecycle lifecycle) {
synchronized (sInstanceLock) {
if (sTestInstances != null && sTestInstances.containsKey(context)) {
SavedWifiHelper testInstance = sTestInstances.get(context);
Log.w(TAG, "The context owner use a test instance:" + testInstance);
return testInstance;
}
return new SavedWifiHelper(context, lifecycle);
}
}
/**
* A convenience method to set pre-prepared instance or mock(SavedWifiHelper.class) for
* testing.
*
* @param context The Context this is associated with.
* @param instance of {@link SavedWifiHelper} object.
* @hide
*/
@TestApi
@VisibleForTesting
public static void setTestInstance(@NonNull Context context, SavedWifiHelper instance) {
synchronized (sInstanceLock) {
if (sTestInstances == null) sTestInstances = new ConcurrentHashMap<>();
Log.w(TAG, "Set a test instance by context:" + context);
sTestInstances.put(context, instance);
}
}
public SavedWifiHelper(@NonNull Context context, @NonNull Lifecycle lifecycle) {
this(context, lifecycle, null);
}
@VisibleForTesting
protected SavedWifiHelper(@NonNull Context context, @NonNull Lifecycle lifecycle,
SavedNetworkTracker saveNetworkTracker) {
super(lifecycle);
mSavedNetworkTracker = (saveNetworkTracker != null) ? saveNetworkTracker
: createSavedNetworkTracker(context, lifecycle);
}
@VisibleForTesting
protected SavedNetworkTracker createSavedNetworkTracker(@NonNull Context context,
@NonNull Lifecycle lifecycle) {
return new SavedNetworkTracker(lifecycle, context.getApplicationContext(),
context.getApplicationContext().getSystemService(WifiManager.class),
context.getApplicationContext().getSystemService(ConnectivityManager.class),
new Handler(Looper.getMainLooper()),
getWorkerThreadHandler(),
ELAPSED_REALTIME_CLOCK,
MAX_SCAN_AGE_MILLIS,
SCAN_INTERVAL_MILLIS,
null /* SavedNetworkTrackerCallback */);
}
@Override
protected String getTag() {
return TAG;
}
public SavedNetworkTracker getSavedNetworkTracker() {
return mSavedNetworkTracker;
}
/**
* Returns true when the certificate is being used by a saved network or network suggestion.
*/
public boolean isCertificateInUse(String certAlias) {
return mSavedNetworkTracker.isCertificateRequired(certAlias);
}
/**
* Returns a list of network names which is using the certificate alias.
*
* @return a list of network names.
*/
public List<String> getCertificateNetworkNames(String certAlias) {
return mSavedNetworkTracker.getCertificateRequesterNames(certAlias);
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2022 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.helper;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SimpleClock;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import java.time.Clock;
import java.time.ZoneOffset;
/**
* Base class for the WifiTrackerLib related classes.
*/
public class WifiTrackerBase implements DefaultLifecycleObserver {
private static final String TAG = "WifiTrackerBase";
// Max age of tracked WifiEntries
protected static final long MAX_SCAN_AGE_MILLIS = 15_000;
// Interval between initiating Wi-Fi Tracker scans
protected static final long SCAN_INTERVAL_MILLIS = 10_000;
// Clock used for evaluating the age of scans
protected static final Clock ELAPSED_REALTIME_CLOCK = new SimpleClock(ZoneOffset.UTC) {
@Override
public long millis() {
return SystemClock.elapsedRealtime();
}
};
@VisibleForTesting
protected HandlerThread mWorkerThread;
public WifiTrackerBase(@NonNull Lifecycle lifecycle) {
this(lifecycle, null /* handlerThread */);
}
@VisibleForTesting
protected WifiTrackerBase(@NonNull Lifecycle lifecycle, HandlerThread handlerThread) {
lifecycle.addObserver(this);
mWorkerThread = (handlerThread != null) ? handlerThread :
new HandlerThread(getTag()
+ "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
Process.THREAD_PRIORITY_BACKGROUND);
mWorkerThread.start();
}
protected String getTag() {
return TAG;
}
@Override
public void onDestroy(@NonNull LifecycleOwner owner) {
mWorkerThread.quit();
}
/** Returns the worker thread handler. */
public Handler getWorkerThreadHandler() {
return mWorkerThread.getThreadHandler();
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 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.p2p;
import android.content.Context;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
public abstract class P2pCategoryPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
protected PreferenceGroup mCategory;
public P2pCategoryPreferenceController(Context context) {
super(context);
}
@Override
public boolean isAvailable() {
return mCategory.getPreferenceCount() > 0;
}
@Override
public void displayPreference(PreferenceScreen screen) {
mCategory = screen.findPreference(getPreferenceKey());
super.displayPreference(screen);
}
public void removeAllChildren() {
if (mCategory != null) {
mCategory.removeAll();
mCategory.setVisible(false);
}
}
public void addChild(Preference child) {
if (mCategory != null) {
mCategory.addPreference(child);
mCategory.setVisible(true);
}
}
public void setEnabled(boolean enabled) {
if (mCategory != null) {
mCategory.setEnabled(enabled);
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2017 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.p2p;
import android.content.Context;
public class P2pPeerCategoryPreferenceController extends P2pCategoryPreferenceController {
public P2pPeerCategoryPreferenceController(Context context) {
super(context);
}
@Override
public String getPreferenceKey() {
return "p2p_peer_devices";
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2017 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.p2p;
import android.content.Context;
public class P2pPersistentCategoryPreferenceController extends P2pCategoryPreferenceController {
public P2pPersistentCategoryPreferenceController(Context context) {
super(context);
}
@Override
public String getPreferenceKey() {
return "p2p_persistent_group";
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2017 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.p2p;
import android.content.Context;
import android.net.wifi.p2p.WifiP2pDevice;
import android.text.TextUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
public class P2pThisDevicePreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
private Preference mPreference;
public P2pThisDevicePreferenceController(Context context) {
super(context);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return "p2p_this_device";
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
public void setEnabled(boolean enabled) {
if (mPreference != null) {
mPreference.setEnabled(enabled);
}
}
public void updateDeviceName(WifiP2pDevice thisDevice) {
if (mPreference != null && thisDevice != null) {
if (TextUtils.isEmpty(thisDevice.deviceName)) {
mPreference.setTitle(thisDevice.deviceAddress);
} else {
mPreference.setTitle(thisDevice.deviceName);
}
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C) 2011 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.p2p;
import android.content.Context;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pDevice;
import android.text.TextUtils;
import android.widget.ImageView;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
public class WifiP2pPeer extends Preference {
private static final int FIXED_RSSI = 60;
private static final int[] STATE_SECURED = {com.android.settingslib.R.attr.state_encrypted};
public WifiP2pDevice device;
@VisibleForTesting final int mRssi;
private ImageView mSignal;
@VisibleForTesting
static final int SIGNAL_LEVELS = 4;
public WifiP2pPeer(Context context, WifiP2pDevice dev) {
super(context);
device = dev;
setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
mRssi = FIXED_RSSI; //TODO: fix
if (TextUtils.isEmpty(device.deviceName)) {
setTitle(device.deviceAddress);
} else {
setTitle(device.deviceName);
}
String[] statusArray = context.getResources().getStringArray(R.array.wifi_p2p_status);
setSummary(statusArray[device.status]);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
mSignal = (ImageView) view.findViewById(R.id.signal);
if (mRssi == Integer.MAX_VALUE) {
mSignal.setImageDrawable(null);
} else {
mSignal.setImageResource(R.drawable.wifi_signal);
mSignal.setImageState(STATE_SECURED, true);
}
mSignal.setImageLevel(getLevel());
}
@Override
public int compareTo(Preference preference) {
if (!(preference instanceof WifiP2pPeer)) {
return 1;
}
WifiP2pPeer other = (WifiP2pPeer) preference;
// devices go in the order of the status
if (device.status != other.device.status) {
return device.status < other.device.status ? -1 : 1;
}
// Sort by name/address
if (device.deviceName != null) {
return device.deviceName.compareToIgnoreCase(other.device.deviceName);
}
return device.deviceAddress.compareToIgnoreCase(other.device.deviceAddress);
}
int getLevel() {
if (mRssi == Integer.MAX_VALUE) {
return -1;
}
return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2012 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.p2p;
import android.content.Context;
import android.net.wifi.p2p.WifiP2pGroup;
import androidx.preference.Preference;
public class WifiP2pPersistentGroup extends Preference {
public WifiP2pGroup mGroup;
public WifiP2pPersistentGroup(Context context, WifiP2pGroup group) {
super(context);
mGroup = group;
setTitle(mGroup.getNetworkName());
}
int getNetworkId() {
return mGroup.getNetworkId();
}
String getGroupName() {
return mGroup.getNetworkName();
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2017 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.p2p;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.WifiManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
/**
* {@link PreferenceControllerMixin} to toggle Wifi Direct preference on Wi-Fi state.
*/
public class WifiP2pPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, LifecycleObserver, OnPause, OnResume {
private static final String KEY_WIFI_DIRECT = "wifi_direct";
private final WifiManager mWifiManager;
@VisibleForTesting
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
togglePreferences();
}
};
private final IntentFilter mFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
private Preference mWifiDirectPref;
@VisibleForTesting
boolean mIsWifiDirectAllow;
public WifiP2pPreferenceController(
Context context, Lifecycle lifecycle, WifiManager wifiManager) {
super(context);
mWifiManager = wifiManager;
mIsWifiDirectAllow = WifiEnterpriseRestrictionUtils.isWifiDirectAllowed(context);
lifecycle.addObserver(this);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mWifiDirectPref = screen.findPreference(KEY_WIFI_DIRECT);
togglePreferences();
if (!mIsWifiDirectAllow) {
mWifiDirectPref.setSummary(R.string.not_allowed_by_ent);
}
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
preference.setEnabled(isWifiP2pAvailable());
}
@Override
public void onResume() {
mContext.registerReceiver(mReceiver, mFilter);
}
@Override
public void onPause() {
mContext.unregisterReceiver(mReceiver);
}
@Override
public boolean isAvailable() {
// Always show preference.
return true;
}
@Override
public String getPreferenceKey() {
return KEY_WIFI_DIRECT;
}
private void togglePreferences() {
if (mWifiDirectPref != null) {
mWifiDirectPref.setEnabled(isWifiP2pAvailable());
}
}
private boolean isWifiP2pAvailable() {
return mWifiManager.isWifiEnabled() && mIsWifiDirectAllow;
}
}

View File

@@ -0,0 +1,683 @@
/*
* Copyright (C) 2011 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.p2p;
import android.app.Activity;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkInfo;
import android.net.wifi.WpsInfo;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pDeviceList;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pGroupList;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.DeviceInfoListener;
import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
import android.net.wifi.p2p.WifiP2pManager.PersistentGroupInfoListener;
import android.os.Bundle;
import android.sysprop.TelephonyProperties;
import android.text.InputFilter;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.ArrayList;
import java.util.List;
/*
* Displays Wi-fi p2p settings UI
*/
public class WifiP2pSettings extends DashboardFragment
implements PersistentGroupInfoListener, PeerListListener, DeviceInfoListener {
private static final String TAG = "WifiP2pSettings";
private static final boolean DBG = false;
@VisibleForTesting static final int MENU_ID_SEARCH = Menu.FIRST;
@VisibleForTesting static final int MENU_ID_RENAME = Menu.FIRST + 1;
private final IntentFilter mIntentFilter = new IntentFilter();
@VisibleForTesting WifiP2pManager mWifiP2pManager;
@VisibleForTesting static WifiP2pManager.Channel sChannel;
@VisibleForTesting OnClickListener mRenameListener;
@VisibleForTesting OnClickListener mDisconnectListener;
@VisibleForTesting OnClickListener mCancelConnectListener;
@VisibleForTesting OnClickListener mDeleteGroupListener;
@VisibleForTesting WifiP2pPeer mSelectedWifiPeer;
@VisibleForTesting WifiP2pPersistentGroup mSelectedGroup;
@VisibleForTesting String mSelectedGroupName;
private EditText mDeviceNameText;
private boolean mWifiP2pEnabled;
@VisibleForTesting boolean mWifiP2pSearching;
@VisibleForTesting int mConnectedDevices;
@VisibleForTesting boolean mLastGroupFormed = false;
private boolean mIsIgnoreInitConnectionInfoCallback = false;
@VisibleForTesting P2pPeerCategoryPreferenceController mPeerCategoryController;
@VisibleForTesting P2pPersistentCategoryPreferenceController mPersistentCategoryController;
@VisibleForTesting P2pThisDevicePreferenceController mThisDevicePreferenceController;
@VisibleForTesting static final int DIALOG_DISCONNECT = 1;
@VisibleForTesting static final int DIALOG_CANCEL_CONNECT = 2;
@VisibleForTesting static final int DIALOG_RENAME = 3;
@VisibleForTesting static final int DIALOG_DELETE_GROUP = 4;
@VisibleForTesting static final String SAVE_DIALOG_PEER = "PEER_STATE";
@VisibleForTesting static final String SAVE_DEVICE_NAME = "DEV_NAME";
@VisibleForTesting static final String SAVE_SELECTED_GROUP = "GROUP_NAME";
private WifiP2pDevice mThisDevice;
private WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
@VisibleForTesting String mSavedDeviceName;
@VisibleForTesting
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
mWifiP2pEnabled = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
WifiP2pManager.WIFI_P2P_STATE_DISABLED) == WifiP2pManager.WIFI_P2P_STATE_ENABLED;
handleP2pStateChanged();
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
mPeers = (WifiP2pDeviceList) intent.getParcelableExtra(
WifiP2pManager.EXTRA_P2P_DEVICE_LIST);
handlePeersChanged();
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
if (mWifiP2pManager == null) return;
NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(
WifiP2pManager.EXTRA_NETWORK_INFO);
WifiP2pInfo wifip2pinfo = (WifiP2pInfo) intent.getParcelableExtra(
WifiP2pManager.EXTRA_WIFI_P2P_INFO);
if (networkInfo.isConnected()) {
if (DBG) Log.d(TAG, "Connected");
} else if (mLastGroupFormed != true) {
//start a search when we are disconnected
//but not on group removed broadcast event
startSearch();
}
mLastGroupFormed = wifip2pinfo.groupFormed;
mIsIgnoreInitConnectionInfoCallback = true;
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
WifiP2pDevice device =
(WifiP2pDevice) intent.getExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
if (device != null && device.status == WifiP2pDevice.UNAVAILABLE) {
return;
}
// Do not use WifiP2pManager.EXTRA_WIFI_P2P_DEVICE from the extras, as the system
// broadcast does not contain the device's MAC.
// Requesting our own device info as an app holding the NETWORK_SETTINGS permission
// ensures that the MAC address will be available in the result.
if (DBG) Log.d(TAG, "This device changed. Requesting device info.");
if (mWifiP2pManager != null && sChannel != null) {
mWifiP2pManager.requestDeviceInfo(sChannel, WifiP2pSettings.this);
}
} else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
int discoveryState = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE,
WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
if (DBG) Log.d(TAG, "Discovery state changed: " + discoveryState);
if (discoveryState == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) {
updateSearchMenu(true);
} else {
updateSearchMenu(false);
}
} else if (WifiP2pManager.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED.equals(action)) {
if (mWifiP2pManager != null && sChannel != null) {
mWifiP2pManager.requestPersistentGroupInfo(sChannel, WifiP2pSettings.this);
}
}
}
};
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.wifi_p2p_settings;
}
@Override
public int getMetricsCategory() {
return SettingsEnums.WIFI_P2P;
}
@Override
public int getHelpResource() {
return R.string.help_url_wifi_p2p;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
mPersistentCategoryController =
new P2pPersistentCategoryPreferenceController(context);
mPeerCategoryController =
new P2pPeerCategoryPreferenceController(context);
mThisDevicePreferenceController = new P2pThisDevicePreferenceController(context);
controllers.add(mPersistentCategoryController);
controllers.add(mPeerCategoryController);
controllers.add(mThisDevicePreferenceController);
return controllers;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View root = super.onCreateView(inflater, container, savedInstanceState);
final Activity activity = getActivity();
if (mWifiP2pManager == null) {
mWifiP2pManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
}
if (mWifiP2pManager != null) {
if (!initChannel()) {
//Failure to set up connection
Log.e(TAG, "Failed to set up connection with wifi p2p service");
mWifiP2pManager = null;
}
} else {
Log.e(TAG, "mWifiP2pManager is null !");
}
if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DIALOG_PEER)) {
WifiP2pDevice device = savedInstanceState.getParcelable(SAVE_DIALOG_PEER);
mSelectedWifiPeer = new WifiP2pPeer(getPrefContext(), device);
}
if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_DEVICE_NAME)) {
mSavedDeviceName = savedInstanceState.getString(SAVE_DEVICE_NAME);
}
if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_SELECTED_GROUP)) {
mSelectedGroupName = savedInstanceState.getString(SAVE_SELECTED_GROUP);
}
mRenameListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
if (mWifiP2pManager != null && sChannel != null) {
String name = mDeviceNameText.getText().toString();
if (name != null) {
for (int i = 0; i < name.length(); i++) {
char cur = name.charAt(i);
if(!Character.isDigit(cur) && !Character.isLetter(cur)
&& cur != '-' && cur != '_' && cur != ' ') {
Toast.makeText(getActivity(),
R.string.wifi_p2p_failed_rename_message,
Toast.LENGTH_LONG).show();
return;
}
}
}
mWifiP2pManager.setDeviceName(sChannel,
mDeviceNameText.getText().toString(),
new WifiP2pManager.ActionListener() {
public void onSuccess() {
if (DBG) Log.d(TAG, " device rename success");
}
public void onFailure(int reason) {
Toast.makeText(getActivity(),
R.string.wifi_p2p_failed_rename_message,
Toast.LENGTH_LONG).show();
}
});
}
}
}
};
//disconnect dialog listener
mDisconnectListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
if (mWifiP2pManager != null && sChannel != null) {
mWifiP2pManager.removeGroup(sChannel, new WifiP2pManager.ActionListener() {
public void onSuccess() {
if (DBG) Log.d(TAG, " remove group success");
}
public void onFailure(int reason) {
if (DBG) Log.d(TAG, " remove group fail " + reason);
}
});
}
}
}
};
//cancel connect dialog listener
mCancelConnectListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
if (mWifiP2pManager != null && sChannel != null) {
mWifiP2pManager.cancelConnect(sChannel,
new WifiP2pManager.ActionListener() {
public void onSuccess() {
if (DBG) Log.d(TAG, " cancel connect success");
}
public void onFailure(int reason) {
if (DBG) Log.d(TAG, " cancel connect fail " + reason);
}
});
}
}
}
};
//delete persistent group dialog listener
mDeleteGroupListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
if (mWifiP2pManager != null && sChannel != null) {
if (mSelectedGroup != null) {
if (DBG) Log.d(TAG, " deleting group " + mSelectedGroup.getGroupName());
mWifiP2pManager.deletePersistentGroup(sChannel,
mSelectedGroup.getNetworkId(),
new WifiP2pManager.ActionListener() {
public void onSuccess() {
if (DBG) Log.d(TAG, " delete group success");
}
public void onFailure(int reason) {
if (DBG) Log.d(TAG, " delete group fail " + reason);
}
});
mSelectedGroup = null;
} else {
if (DBG) Log.w(TAG, " No selected group to delete!");
}
}
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
if (DBG) {
Log.d(TAG, " forgetting selected group " + mSelectedGroup.getGroupName());
}
mSelectedGroup = null;
}
}
};
return root;
}
@Override
public void onStart() {
super.onStart();
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED);
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (mWifiP2pManager != null && initChannel()) {
// Register receiver after make sure channel exist
getActivity().registerReceiver(mReceiver, mIntentFilter);
mWifiP2pManager.requestPeers(sChannel, WifiP2pSettings.this);
mWifiP2pManager.requestDeviceInfo(sChannel, WifiP2pSettings.this);
mIsIgnoreInitConnectionInfoCallback = false;
}
}
@Override
public void onStop() {
super.onStop();
if (mWifiP2pManager != null && sChannel != null) {
mWifiP2pManager.stopPeerDiscovery(sChannel, null);
if (!mLastGroupFormed) {
// Close the channel when p2p doesn't connected.
sChannel.close();
sChannel = null;
}
}
getActivity().unregisterReceiver(mReceiver);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
int textId = mWifiP2pSearching ? R.string.wifi_p2p_menu_searching :
R.string.wifi_p2p_menu_search;
menu.add(Menu.NONE, MENU_ID_SEARCH, 0, textId)
.setEnabled(mWifiP2pEnabled);
menu.add(Menu.NONE, MENU_ID_RENAME, 0, R.string.wifi_p2p_menu_rename)
.setEnabled(mWifiP2pEnabled);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
MenuItem searchMenu = menu.findItem(MENU_ID_SEARCH);
MenuItem renameMenu = menu.findItem(MENU_ID_RENAME);
if (mWifiP2pEnabled) {
searchMenu.setEnabled(true);
renameMenu.setEnabled(true);
} else {
searchMenu.setEnabled(false);
renameMenu.setEnabled(false);
}
if (mWifiP2pSearching) {
searchMenu.setTitle(R.string.wifi_p2p_menu_searching);
} else {
searchMenu.setTitle(R.string.wifi_p2p_menu_search);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ID_SEARCH:
startSearch();
return true;
case MENU_ID_RENAME:
showDialog(DIALOG_RENAME);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference instanceof WifiP2pPeer) {
mSelectedWifiPeer = (WifiP2pPeer) preference;
if (mSelectedWifiPeer.device.status == WifiP2pDevice.CONNECTED) {
showDialog(DIALOG_DISCONNECT);
} else if (mSelectedWifiPeer.device.status == WifiP2pDevice.INVITED) {
showDialog(DIALOG_CANCEL_CONNECT);
} else {
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = mSelectedWifiPeer.device.deviceAddress;
int forceWps = TelephonyProperties.wps_info().orElse(-1);
if (forceWps != -1) {
config.wps.setup = forceWps;
} else {
if (mSelectedWifiPeer.device.wpsPbcSupported()) {
config.wps.setup = WpsInfo.PBC;
} else if (mSelectedWifiPeer.device.wpsKeypadSupported()) {
config.wps.setup = WpsInfo.KEYPAD;
} else {
config.wps.setup = WpsInfo.DISPLAY;
}
}
if (mWifiP2pManager != null && sChannel != null) {
mWifiP2pManager.connect(sChannel, config,
new WifiP2pManager.ActionListener() {
public void onSuccess() {
if (DBG) Log.d(TAG, " connect success");
}
public void onFailure(int reason) {
Log.e(TAG, " connect fail " + reason);
Toast.makeText(getActivity(),
R.string.wifi_p2p_failed_connect_message,
Toast.LENGTH_SHORT).show();
}
});
}
}
} else if (preference instanceof WifiP2pPersistentGroup) {
mSelectedGroup = (WifiP2pPersistentGroup) preference;
showDialog(DIALOG_DELETE_GROUP);
}
return super.onPreferenceTreeClick(preference);
}
@Override
public Dialog onCreateDialog(int id) {
if (id == DIALOG_DISCONNECT) {
String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ?
mSelectedWifiPeer.device.deviceAddress :
mSelectedWifiPeer.device.deviceName;
String msg;
if (mConnectedDevices > 1) {
msg = getActivity().getString(R.string.wifi_p2p_disconnect_multiple_message,
deviceName, mConnectedDevices - 1);
} else {
msg = getActivity().getString(R.string.wifi_p2p_disconnect_message, deviceName);
}
AlertDialog dialog = new AlertDialog.Builder(getActivity())
.setTitle(R.string.wifi_p2p_disconnect_title)
.setMessage(msg)
.setPositiveButton(getActivity().getString(R.string.dlg_ok), mDisconnectListener)
.setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
.create();
return dialog;
} else if (id == DIALOG_CANCEL_CONNECT) {
int stringId = R.string.wifi_p2p_cancel_connect_message;
String deviceName = TextUtils.isEmpty(mSelectedWifiPeer.device.deviceName) ?
mSelectedWifiPeer.device.deviceAddress :
mSelectedWifiPeer.device.deviceName;
AlertDialog dialog = new AlertDialog.Builder(getActivity())
.setTitle(R.string.wifi_p2p_cancel_connect_title)
.setMessage(getActivity().getString(stringId, deviceName))
.setPositiveButton(getActivity().getString(R.string.dlg_ok), mCancelConnectListener)
.setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
.create();
return dialog;
} else if (id == DIALOG_RENAME) {
final LayoutInflater layoutInflater = LayoutInflater.from(getPrefContext());
final View root = layoutInflater.inflate(R.layout.dialog_edittext, null /* root */);
mDeviceNameText = root.findViewById(R.id.edittext);
mDeviceNameText.setFilters(new InputFilter[] {new InputFilter.LengthFilter(22)});
if (mSavedDeviceName != null) {
mDeviceNameText.setText(mSavedDeviceName);
mDeviceNameText.setSelection(mSavedDeviceName.length());
} else if (mThisDevice != null && !TextUtils.isEmpty(mThisDevice.deviceName)) {
mDeviceNameText.setText(mThisDevice.deviceName);
mDeviceNameText.setSelection(0, mThisDevice.deviceName.length());
}
mSavedDeviceName = null;
AlertDialog dialog = new AlertDialog.Builder(getActivity())
.setTitle(R.string.wifi_p2p_menu_rename)
.setView(root)
.setPositiveButton(getActivity().getString(R.string.dlg_ok), mRenameListener)
.setNegativeButton(getActivity().getString(R.string.dlg_cancel), null)
.create();
return dialog;
} else if (id == DIALOG_DELETE_GROUP) {
int stringId = R.string.wifi_p2p_delete_group_message;
AlertDialog dialog = new AlertDialog.Builder(getActivity())
.setMessage(getActivity().getString(stringId))
.setPositiveButton(getActivity().getString(R.string.dlg_ok), mDeleteGroupListener)
.setNegativeButton(getActivity().getString(R.string.dlg_cancel),
mDeleteGroupListener).create();
return dialog;
}
return null;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DIALOG_DISCONNECT:
return SettingsEnums.DIALOG_WIFI_P2P_DISCONNECT;
case DIALOG_CANCEL_CONNECT:
return SettingsEnums.DIALOG_WIFI_P2P_CANCEL_CONNECT;
case DIALOG_RENAME:
return SettingsEnums.DIALOG_WIFI_P2P_RENAME;
case DIALOG_DELETE_GROUP:
return SettingsEnums.DIALOG_WIFI_P2P_DELETE_GROUP;
}
return 0;
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (mSelectedWifiPeer != null) {
outState.putParcelable(SAVE_DIALOG_PEER, mSelectedWifiPeer.device);
}
if (mDeviceNameText != null) {
outState.putString(SAVE_DEVICE_NAME, mDeviceNameText.getText().toString());
}
if (mSelectedGroup != null) {
outState.putString(SAVE_SELECTED_GROUP, mSelectedGroup.getGroupName());
}
}
private void handlePeersChanged() {
mPeerCategoryController.removeAllChildren();
mConnectedDevices = 0;
if (DBG) Log.d(TAG, "List of available peers");
for (WifiP2pDevice peer: mPeers.getDeviceList()) {
if (DBG) Log.d(TAG, "-> " + peer);
mPeerCategoryController.addChild(new WifiP2pPeer(getPrefContext(), peer));
if (peer.status == WifiP2pDevice.CONNECTED) mConnectedDevices++;
}
if (DBG) Log.d(TAG, " mConnectedDevices " + mConnectedDevices);
}
@Override
public void onPersistentGroupInfoAvailable(WifiP2pGroupList groups) {
mPersistentCategoryController.removeAllChildren();
for (WifiP2pGroup group: groups.getGroupList()) {
if (DBG) Log.d(TAG, " group " + group);
WifiP2pPersistentGroup wppg = new WifiP2pPersistentGroup(getPrefContext(), group);
mPersistentCategoryController.addChild(wppg);
if (wppg.getGroupName().equals(mSelectedGroupName)) {
if (DBG) Log.d(TAG, "Selecting group " + wppg.getGroupName());
mSelectedGroup = wppg;
mSelectedGroupName = null;
}
}
if (mSelectedGroupName != null) {
// Looks like there's a dialog pending getting user confirmation to delete the
// selected group. When user hits OK on that dialog, we won't do anything; but we
// shouldn't be in this situation in first place, because these groups are persistent
// groups and they shouldn't just get deleted!
Log.w(TAG, " Selected group " + mSelectedGroupName + " disappered on next query ");
}
}
@Override
public void onPeersAvailable(WifiP2pDeviceList peers) {
if (DBG) Log.d(TAG, "Requested peers are available");
mPeers = peers;
handlePeersChanged();
}
@Override
public void onDeviceInfoAvailable(WifiP2pDevice wifiP2pDevice) {
mThisDevice = wifiP2pDevice;
if (DBG) Log.d(TAG, "Update device info: " + mThisDevice);
mThisDevicePreferenceController.updateDeviceName(mThisDevice);
if (wifiP2pDevice.status == WifiP2pDevice.UNAVAILABLE
|| wifiP2pDevice.status == WifiP2pDevice.FAILED) {
return;
}
onDeviceAvailable();
}
private void onDeviceAvailable() {
if (mWifiP2pManager == null || sChannel == null) {
return;
}
mWifiP2pManager.requestNetworkInfo(sChannel, networkInfo -> {
if (sChannel == null) return;
mWifiP2pManager.requestConnectionInfo(sChannel, wifip2pinfo -> {
if (!mIsIgnoreInitConnectionInfoCallback) {
if (networkInfo.isConnected()) {
if (DBG) {
Log.d(TAG, "Connected");
}
} else if (!mLastGroupFormed) {
// Find peers when p2p doesn't connected.
startSearch();
}
mLastGroupFormed = wifip2pinfo.groupFormed;
}
});
});
}
private void handleP2pStateChanged() {
mThisDevicePreferenceController.setEnabled(mWifiP2pEnabled);
mPersistentCategoryController.setEnabled(mWifiP2pEnabled);
mPeerCategoryController.setEnabled(mWifiP2pEnabled);
if (mWifiP2pEnabled) {
startSearch();
}
updateSearchMenu(mWifiP2pEnabled);
}
private void updateSearchMenu(boolean searching) {
mWifiP2pSearching = searching;
Activity activity = getActivity();
if (activity != null) activity.invalidateOptionsMenu();
}
private void startSearch() {
if (mWifiP2pManager != null && sChannel != null && !mWifiP2pSearching) {
mWifiP2pManager.discoverPeers(sChannel, new WifiP2pManager.ActionListener() {
public void onSuccess() {
}
public void onFailure(int reason) {
if (DBG) Log.d(TAG, " discover fail " + reason);
}
});
}
}
private boolean initChannel() {
if (sChannel != null) {
return true;
}
if (mWifiP2pManager != null) {
sChannel = mWifiP2pManager.initialize(getActivity().getApplicationContext(),
getActivity().getMainLooper(), null);
}
if (sChannel == null) {
Log.e(TAG, "Failed to set up connection with wifi p2p service");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright (C) 2023 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.repository;
import android.app.PendingIntent;
import android.content.Context;
import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
import android.net.wifi.sharedconnectivity.app.KnownNetwork;
import android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus;
import android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback;
import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
import android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState;
import android.os.HandlerThread;
import android.provider.DeviceConfig;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.settings.overlay.FeatureFactory;
import java.util.List;
import java.util.concurrent.Executor;
/**
* Shared Connectivity Repository for {@link SharedConnectivityManager}
*/
public class SharedConnectivityRepository {
private static final String TAG = "SharedConnectivityRepository";
private static final String DEVICE_CONFIG_NAMESPACE = "wifi";
private static final String DEVICE_CONFIG_KEY = "shared_connectivity_enabled";
private Context mAppContext;
private SharedConnectivityManager mManager;
private ClientCallback mClientCallback = new ClientCallback();
private HandlerThread mWorkerThread = new HandlerThread(TAG);
private Executor mWorkerExecutor = cmd -> mWorkerThread.getThreadHandler().post(cmd);
private Runnable mLaunchSettingsRunnable = () -> handleLaunchSettings();
@VisibleForTesting
MutableLiveData<SharedConnectivitySettingsState> mSettingsState = new MutableLiveData<>();
public SharedConnectivityRepository(@NonNull Context appContext) {
this(appContext, isDeviceConfigEnabled());
}
@VisibleForTesting
SharedConnectivityRepository(@NonNull Context appContext, boolean isConfigEnabled) {
mAppContext = appContext;
if (!isConfigEnabled) {
return;
}
mManager = mAppContext.getSystemService(SharedConnectivityManager.class);
if (mManager == null) {
Log.w(TAG, "Failed to get SharedConnectivityManager");
return;
}
mWorkerThread.start();
mManager.registerCallback(mWorkerExecutor, mClientCallback);
}
/**
* Return whether Wi-Fi Shared Connectivity service is available or not.
*
* @return {@code true} if Wi-Fi Shared Connectivity service is available
*/
public boolean isServiceAvailable() {
return mManager != null;
}
/**
* Gets SharedConnectivitySettingsState LiveData
*/
public LiveData<SharedConnectivitySettingsState> getSettingsState() {
return mSettingsState;
}
/**
* Launch Instant Hotspot Settings
*/
public void launchSettings() {
mWorkerExecutor.execute(mLaunchSettingsRunnable);
}
@WorkerThread
@VisibleForTesting
void handleLaunchSettings() {
if (mManager == null) {
return;
}
SharedConnectivitySettingsState state = mManager.getSettingsState();
log("handleLaunchSettings(), state:" + state);
if (state == null) {
Log.e(TAG, "No SettingsState to launch Instant Hotspot settings");
return;
}
PendingIntent intent = state.getInstantTetherSettingsPendingIntent();
if (intent == null) {
Log.e(TAG, "No PendingIntent to launch Instant Hotspot settings");
return;
}
sendSettingsIntent(intent);
}
@WorkerThread
@VisibleForTesting
void sendSettingsIntent(@NonNull PendingIntent intent) {
try {
log("sendSettingsIntent(), sent intent:" + intent);
intent.send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Failed to launch Instant Hotspot settings", e);
}
}
@WorkerThread
class ClientCallback implements SharedConnectivityClientCallback {
@Override
public void onHotspotNetworkConnectionStatusChanged(HotspotNetworkConnectionStatus status) {
log("onHotspotNetworkConnectionStatusChanged(), status:" + status);
}
@Override
public void onHotspotNetworksUpdated(List<HotspotNetwork> networks) {
log("onHotspotNetworksUpdated(), networks:" + networks);
}
@Override
public void onKnownNetworkConnectionStatusChanged(KnownNetworkConnectionStatus status) {
log("onKnownNetworkConnectionStatusChanged(), status:" + status);
}
@Override
public void onKnownNetworksUpdated(List<KnownNetwork> networks) {
log("onKnownNetworksUpdated(), networks:" + networks);
}
@Override
public void onRegisterCallbackFailed(Exception e) {
Log.e(TAG, "onRegisterCallbackFailed(), e:" + e);
}
@Override
public void onServiceConnected() {
SharedConnectivitySettingsState state = mManager.getSettingsState();
Log.d(TAG, "onServiceConnected(), Manager#getSettingsState:" + state);
mSettingsState.postValue(state);
}
@Override
public void onServiceDisconnected() {
log("onServiceDisconnected()");
}
@Override
public void onSharedConnectivitySettingsChanged(SharedConnectivitySettingsState state) {
Log.d(TAG, "onSharedConnectivitySettingsChanged(), state:" + state);
mSettingsState.postValue(state);
}
}
private void log(String msg) {
FeatureFactory.getFeatureFactory().getWifiFeatureProvider().verboseLog(TAG, msg);
}
/**
* Returns true if Shared Connectivity feature is enabled.
*/
public static boolean isDeviceConfigEnabled() {
return DeviceConfig.getBoolean(DEVICE_CONFIG_NAMESPACE, DEVICE_CONFIG_KEY, false);
}
}

View File

@@ -0,0 +1,662 @@
/*
* Copyright (C) 2023 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.repository;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.wifi.SoftApConfiguration.BAND_2GHZ;
import static android.net.wifi.SoftApConfiguration.BAND_5GHZ;
import static android.net.wifi.SoftApConfiguration.BAND_6GHZ;
import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_OPEN;
import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE;
import static android.net.wifi.WifiAvailableChannel.OP_MODE_SAP;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
import android.content.Context;
import android.net.TetheringManager;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiAvailableChannel;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiScanner;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
/**
* Wi-Fi Hotspot Repository
*/
public class WifiHotspotRepository {
private static final String TAG = "WifiHotspotRepository";
private static final int RESTART_INTERVAL_MS = 100;
/** Wi-Fi hotspot band unknown. */
public static final int BAND_UNKNOWN = 0;
/** Wi-Fi hotspot band 2.4GHz and 5GHz. */
public static final int BAND_2GHZ_5GHZ = BAND_2GHZ | BAND_5GHZ;
/** Wi-Fi hotspot band 2.4GHz and 5GHz and 6GHz. */
public static final int BAND_2GHZ_5GHZ_6GHZ = BAND_2GHZ | BAND_5GHZ | BAND_6GHZ;
/** Wi-Fi hotspot speed unknown. */
public static final int SPEED_UNKNOWN = 0;
/** Wi-Fi hotspot speed 2.4GHz. */
public static final int SPEED_2GHZ = 1;
/** Wi-Fi hotspot speed 5GHz. */
public static final int SPEED_5GHZ = 2;
/** Wi-Fi hotspot speed 2.4GHz and 5GHz. */
public static final int SPEED_2GHZ_5GHZ = 3;
/** Wi-Fi hotspot speed 6GHz. */
public static final int SPEED_6GHZ = 4;
protected static Map<Integer, Integer> sSpeedMap = new HashMap<>();
static {
sSpeedMap.put(BAND_UNKNOWN, SPEED_UNKNOWN);
sSpeedMap.put(BAND_2GHZ, SPEED_2GHZ);
sSpeedMap.put(BAND_5GHZ, SPEED_5GHZ);
sSpeedMap.put(BAND_6GHZ, SPEED_6GHZ);
sSpeedMap.put(BAND_2GHZ_5GHZ, SPEED_2GHZ_5GHZ);
}
private final Context mAppContext;
private final WifiManager mWifiManager;
private final TetheringManager mTetheringManager;
protected String mLastPassword;
protected LastPasswordListener mLastPasswordListener = new LastPasswordListener();
protected MutableLiveData<Integer> mSecurityType;
protected MutableLiveData<Integer> mSpeedType;
protected Boolean mIsDualBand;
protected Boolean mIs5gBandSupported;
protected Boolean mIs5gAvailable;
protected MutableLiveData<Boolean> m5gAvailable;
protected Boolean mIs6gBandSupported;
protected Boolean mIs6gAvailable;
protected MutableLiveData<Boolean> m6gAvailable;
protected ActiveCountryCodeChangedCallback mActiveCountryCodeChangedCallback;
@VisibleForTesting
Boolean mIsConfigShowSpeed;
private Boolean mIsSpeedFeatureAvailable;
@VisibleForTesting
SoftApCallback mSoftApCallback = new SoftApCallback();
@VisibleForTesting
StartTetheringCallback mStartTetheringCallback;
@VisibleForTesting
int mWifiApState = WIFI_AP_STATE_DISABLED;
@VisibleForTesting
boolean mIsRestarting;
@VisibleForTesting
MutableLiveData<Boolean> mRestarting;
public WifiHotspotRepository(@NonNull Context appContext, @NonNull WifiManager wifiManager,
@NonNull TetheringManager tetheringManager) {
mAppContext = appContext;
mWifiManager = wifiManager;
mTetheringManager = tetheringManager;
mWifiManager.registerSoftApCallback(mAppContext.getMainExecutor(), mSoftApCallback);
}
/**
* Query the last configured Tethered Ap Passphrase since boot.
*/
public void queryLastPasswordIfNeeded() {
SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
if (config.getSecurityType() != SoftApConfiguration.SECURITY_TYPE_OPEN) {
return;
}
mWifiManager.queryLastConfiguredTetheredApPassphraseSinceBoot(mAppContext.getMainExecutor(),
mLastPasswordListener);
}
/**
* Generate password.
*/
public String generatePassword() {
return !TextUtils.isEmpty(mLastPassword) ? mLastPassword : generateRandomPassword();
}
@VisibleForTesting
String generatePassword(SoftApConfiguration config) {
String password = config.getPassphrase();
if (TextUtils.isEmpty(password)) {
password = generatePassword();
}
return password;
}
private class LastPasswordListener implements Consumer<String> {
@Override
public void accept(String password) {
mLastPassword = password;
}
}
private static String generateRandomPassword() {
String randomUUID = UUID.randomUUID().toString();
//first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
}
/**
* Gets the Wi-Fi tethered AP Configuration.
*
* @return AP details in {@link SoftApConfiguration}
*/
public SoftApConfiguration getSoftApConfiguration() {
return mWifiManager.getSoftApConfiguration();
}
/**
* Sets the tethered Wi-Fi AP Configuration.
*
* @param config A valid SoftApConfiguration specifying the configuration of the SAP.
*/
public void setSoftApConfiguration(@NonNull SoftApConfiguration config) {
if (mIsRestarting) {
Log.e(TAG, "Skip setSoftApConfiguration because hotspot is restarting.");
return;
}
mWifiManager.setSoftApConfiguration(config);
refresh();
restartTetheringIfNeeded();
}
/**
* Refresh data from the SoftApConfiguration.
*/
public void refresh() {
updateSecurityType();
update6gAvailable();
update5gAvailable();
updateSpeedType();
}
/**
* Set to auto refresh data.
*
* @param enabled whether the auto refresh should be enabled or not.
*/
public void setAutoRefresh(boolean enabled) {
if (enabled) {
startAutoRefresh();
} else {
stopAutoRefresh();
}
}
/**
* Gets SecurityType LiveData
*/
public LiveData<Integer> getSecurityType() {
if (mSecurityType == null) {
startAutoRefresh();
mSecurityType = new MutableLiveData<>();
updateSecurityType();
log("getSecurityType():" + mSecurityType.getValue());
}
return mSecurityType;
}
protected void updateSecurityType() {
if (mSecurityType == null) {
return;
}
SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
int securityType = (config != null) ? config.getSecurityType() : SECURITY_TYPE_OPEN;
log("updateSecurityType(), securityType:" + securityType);
mSecurityType.setValue(securityType);
}
/**
* Sets SecurityType
*
* @param securityType the Wi-Fi hotspot security type.
*/
public void setSecurityType(int securityType) {
log("setSecurityType():" + securityType);
if (mSecurityType == null) {
getSecurityType();
}
if (securityType == mSecurityType.getValue()) {
Log.w(TAG, "setSecurityType() is no changed! mSecurityType:"
+ mSecurityType.getValue());
return;
}
SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
if (config == null) {
mSecurityType.setValue(SECURITY_TYPE_OPEN);
Log.e(TAG, "setSecurityType(), WifiManager#getSoftApConfiguration() return null!");
return;
}
SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
String passphrase = (securityType == SECURITY_TYPE_OPEN) ? null : generatePassword(config);
configBuilder.setPassphrase(passphrase, securityType);
setSoftApConfiguration(configBuilder.build());
mWifiManager.queryLastConfiguredTetheredApPassphraseSinceBoot(
mAppContext.getMainExecutor(), mLastPasswordListener);
}
/**
* Gets SpeedType LiveData
*/
public LiveData<Integer> getSpeedType() {
if (mSpeedType == null) {
startAutoRefresh();
mSpeedType = new MutableLiveData<>();
updateSpeedType();
log("getSpeedType():" + mSpeedType.getValue());
}
return mSpeedType;
}
protected void updateSpeedType() {
if (mSpeedType == null) {
return;
}
SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
if (config == null) {
mSpeedType.setValue(SPEED_UNKNOWN);
return;
}
int keyBand = config.getBand();
log("updateSpeedType(), getBand():" + keyBand);
if (!is5gAvailable()) {
keyBand &= ~BAND_5GHZ;
}
if (!is6gAvailable()) {
keyBand &= ~BAND_6GHZ;
}
if ((keyBand & BAND_6GHZ) != 0) {
keyBand = BAND_6GHZ;
} else if (isDualBand() && is5gAvailable()) {
keyBand = BAND_2GHZ_5GHZ;
} else if ((keyBand & BAND_5GHZ) != 0) {
keyBand = BAND_5GHZ;
} else if ((keyBand & BAND_2GHZ) != 0) {
keyBand = BAND_2GHZ;
} else {
keyBand = 0;
}
log("updateSpeedType(), keyBand:" + keyBand);
mSpeedType.setValue(sSpeedMap.get(keyBand));
}
/**
* Sets SpeedType
*
* @param speedType the Wi-Fi hotspot speed type.
*/
public void setSpeedType(int speedType) {
log("setSpeedType():" + speedType);
if (mSpeedType == null) {
getSpeedType();
}
if (speedType == mSpeedType.getValue()) {
Log.w(TAG, "setSpeedType() is no changed! mSpeedType:" + mSpeedType.getValue());
return;
}
SoftApConfiguration config = mWifiManager.getSoftApConfiguration();
if (config == null) {
mSpeedType.setValue(SPEED_UNKNOWN);
Log.e(TAG, "setSpeedType(), WifiManager#getSoftApConfiguration() return null!");
return;
}
SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
if (speedType == SPEED_6GHZ) {
log("setSpeedType(), setBand(BAND_2GHZ_5GHZ_6GHZ)");
configBuilder.setBand(BAND_2GHZ_5GHZ_6GHZ);
if (config.getSecurityType() != SECURITY_TYPE_WPA3_SAE) {
log("setSpeedType(), setPassphrase(SECURITY_TYPE_WPA3_SAE)");
configBuilder.setPassphrase(generatePassword(config), SECURITY_TYPE_WPA3_SAE);
}
} else if (speedType == SPEED_5GHZ) {
log("setSpeedType(), setBand(BAND_2GHZ_5GHZ)");
configBuilder.setBand(BAND_2GHZ_5GHZ);
} else if (mIsDualBand) {
log("setSpeedType(), setBands(BAND_2GHZ + BAND_2GHZ_5GHZ)");
int[] bands = {BAND_2GHZ, BAND_2GHZ_5GHZ};
configBuilder.setBands(bands);
} else {
log("setSpeedType(), setBand(BAND_2GHZ)");
configBuilder.setBand(BAND_2GHZ);
}
setSoftApConfiguration(configBuilder.build());
}
/**
* Return whether Wi-Fi Dual Band is supported or not.
*
* @return {@code true} if Wi-Fi Dual Band is supported
*/
public boolean isDualBand() {
if (mIsDualBand == null) {
mIsDualBand = mWifiManager.isBridgedApConcurrencySupported();
log("isDualBand():" + mIsDualBand);
}
return mIsDualBand;
}
/**
* Return whether Wi-Fi 5 GHz band is supported or not.
*
* @return {@code true} if Wi-Fi 5 GHz Band is supported
*/
public boolean is5GHzBandSupported() {
if (mIs5gBandSupported == null) {
mIs5gBandSupported = mWifiManager.is5GHzBandSupported();
log("is5GHzBandSupported():" + mIs5gBandSupported);
}
return mIs5gBandSupported;
}
/**
* Return whether Wi-Fi Hotspot 5 GHz band is available or not.
*
* @return {@code true} if Wi-Fi Hotspot 5 GHz Band is available
*/
public boolean is5gAvailable() {
if (mIs5gAvailable == null) {
// If Settings is unable to get available 5GHz SAP information, Wi-Fi Framework's
// proposal is to assume that 5GHz is available. (See b/272450463#comment16)
mIs5gAvailable = is5GHzBandSupported()
&& isChannelAvailable(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS,
true /* defaultValue */);
log("is5gAvailable():" + mIs5gAvailable);
}
return mIs5gAvailable;
}
/**
* Gets is5gAvailable LiveData
*/
public LiveData<Boolean> get5gAvailable() {
if (m5gAvailable == null) {
m5gAvailable = new MutableLiveData<>();
m5gAvailable.setValue(is5gAvailable());
}
return m5gAvailable;
}
protected void update5gAvailable() {
if (m5gAvailable != null) {
m5gAvailable.setValue(is5gAvailable());
}
}
/**
* Return whether Wi-Fi 6 GHz band is supported or not.
*
* @return {@code true} if Wi-Fi 6 GHz Band is supported
*/
public boolean is6GHzBandSupported() {
if (mIs6gBandSupported == null) {
mIs6gBandSupported = mWifiManager.is6GHzBandSupported();
log("is6GHzBandSupported():" + mIs6gBandSupported);
}
return mIs6gBandSupported;
}
/**
* Return whether Wi-Fi Hotspot 6 GHz band is available or not.
*
* @return {@code true} if Wi-Fi Hotspot 6 GHz Band is available
*/
public boolean is6gAvailable() {
if (mIs6gAvailable == null) {
mIs6gAvailable = is6GHzBandSupported()
&& isChannelAvailable(WifiScanner.WIFI_BAND_6_GHZ, false /* defaultValue */);
log("is6gAvailable():" + mIs6gAvailable);
}
return mIs6gAvailable;
}
/**
* Gets is6gAvailable LiveData
*/
public LiveData<Boolean> get6gAvailable() {
if (m6gAvailable == null) {
m6gAvailable = new MutableLiveData<>();
m6gAvailable.setValue(is6gAvailable());
}
return m6gAvailable;
}
protected void update6gAvailable() {
if (m6gAvailable != null) {
m6gAvailable.setValue(is6gAvailable());
}
}
/**
* Return whether the Hotspot channel is available or not.
*
* @param band one of the following band constants defined in
* {@code WifiScanner#WIFI_BAND_*} constants.
* 1. {@code WifiScanner#WIFI_BAND_5_GHZ_WITH_DFS}
* 2. {@code WifiScanner#WIFI_BAND_6_GHZ}
* @param defaultValue returns the default value if WifiManager#getUsableChannels is
* unavailable to get the SAP information.
*/
protected boolean isChannelAvailable(int band, boolean defaultValue) {
try {
List<WifiAvailableChannel> channels = mWifiManager.getUsableChannels(band, OP_MODE_SAP);
log("isChannelAvailable(), band:" + band + ", channels:" + channels);
return (channels != null && channels.size() > 0);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Querying usable SAP channels failed, band:" + band);
} catch (UnsupportedOperationException e) {
// This is expected on some hardware.
Log.e(TAG, "Querying usable SAP channels is unsupported, band:" + band);
}
// Disable Wi-Fi hotspot speed feature if an error occurs while getting usable channels.
mIsSpeedFeatureAvailable = false;
Log.w(TAG, "isChannelAvailable(): Wi-Fi hotspot speed feature disabled");
return defaultValue;
}
private boolean isConfigShowSpeed() {
if (mIsConfigShowSpeed == null) {
mIsConfigShowSpeed = mAppContext.getResources()
.getBoolean(R.bool.config_show_wifi_hotspot_speed);
log("isConfigShowSpeed():" + mIsConfigShowSpeed);
}
return mIsConfigShowSpeed;
}
/**
* Return whether Wi-Fi Hotspot Speed Feature is available or not.
*
* @return {@code true} if Wi-Fi Hotspot Speed Feature is available
*/
public boolean isSpeedFeatureAvailable() {
if (mIsSpeedFeatureAvailable != null) {
return mIsSpeedFeatureAvailable;
}
// Check config to show Wi-Fi hotspot speed feature
if (!isConfigShowSpeed()) {
mIsSpeedFeatureAvailable = false;
log("isSpeedFeatureAvailable():false, isConfigShowSpeed():false");
return false;
}
// Check if 5 GHz band is not supported
if (!is5GHzBandSupported()) {
mIsSpeedFeatureAvailable = false;
log("isSpeedFeatureAvailable():false, 5 GHz band is not supported on this device");
return false;
}
// Check if 5 GHz band SAP channel is not ready
isChannelAvailable(WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS, true /* defaultValue */);
if (mIsSpeedFeatureAvailable != null && !mIsSpeedFeatureAvailable) {
log("isSpeedFeatureAvailable():false, error occurred while getting 5 GHz SAP channel");
return false;
}
// Check if 6 GHz band SAP channel is not ready
isChannelAvailable(WifiScanner.WIFI_BAND_6_GHZ, false /* defaultValue */);
if (mIsSpeedFeatureAvailable != null && !mIsSpeedFeatureAvailable) {
log("isSpeedFeatureAvailable():false, error occurred while getting 6 GHz SAP channel");
return false;
}
mIsSpeedFeatureAvailable = true;
log("isSpeedFeatureAvailable():true");
return true;
}
protected void purgeRefreshData() {
mIs5gAvailable = null;
mIs6gAvailable = null;
}
protected void startAutoRefresh() {
if (mActiveCountryCodeChangedCallback != null) {
return;
}
log("startMonitorSoftApConfiguration()");
mActiveCountryCodeChangedCallback = new ActiveCountryCodeChangedCallback();
mWifiManager.registerActiveCountryCodeChangedCallback(mAppContext.getMainExecutor(),
mActiveCountryCodeChangedCallback);
}
protected void stopAutoRefresh() {
if (mActiveCountryCodeChangedCallback == null) {
return;
}
log("stopMonitorSoftApConfiguration()");
mWifiManager.unregisterActiveCountryCodeChangedCallback(mActiveCountryCodeChangedCallback);
mActiveCountryCodeChangedCallback = null;
}
protected class ActiveCountryCodeChangedCallback implements
WifiManager.ActiveCountryCodeChangedCallback {
@Override
public void onActiveCountryCodeChanged(String country) {
log("onActiveCountryCodeChanged(), country:" + country);
purgeRefreshData();
refresh();
}
@Override
public void onCountryCodeInactive() {
}
}
/**
* Gets Restarting LiveData
*/
public LiveData<Boolean> getRestarting() {
if (mRestarting == null) {
mRestarting = new MutableLiveData<>();
mRestarting.setValue(mIsRestarting);
}
return mRestarting;
}
private void setRestarting(boolean isRestarting) {
log("setRestarting(), isRestarting:" + isRestarting);
mIsRestarting = isRestarting;
if (mRestarting != null) {
mRestarting.setValue(mIsRestarting);
}
}
@VisibleForTesting
void restartTetheringIfNeeded() {
if (mWifiApState != WIFI_AP_STATE_ENABLED) {
return;
}
log("restartTetheringIfNeeded()");
mAppContext.getMainThreadHandler().postDelayed(() -> {
setRestarting(true);
stopTethering();
}, RESTART_INTERVAL_MS);
}
private void startTethering() {
if (mStartTetheringCallback == null) {
mStartTetheringCallback = new StartTetheringCallback();
}
log("startTethering()");
mTetheringManager.startTethering(TETHERING_WIFI, mAppContext.getMainExecutor(),
mStartTetheringCallback);
}
private void stopTethering() {
log("startTethering()");
mTetheringManager.stopTethering(TETHERING_WIFI);
}
@VisibleForTesting
class SoftApCallback implements WifiManager.SoftApCallback {
private static final String TAG = "SoftApCallback";
@Override
public void onStateChanged(int state, int failureReason) {
Log.d(TAG, "onStateChanged(), state:" + state + ", failureReason:" + failureReason);
mWifiApState = state;
if (!mIsRestarting) {
return;
}
if (state == WIFI_AP_STATE_DISABLED) {
mAppContext.getMainThreadHandler().postDelayed(() -> startTethering(),
RESTART_INTERVAL_MS);
return;
}
if (state == WIFI_AP_STATE_ENABLED) {
refresh();
setRestarting(false);
}
}
}
private class StartTetheringCallback implements TetheringManager.StartTetheringCallback {
@Override
public void onTetheringStarted() {
log("onTetheringStarted()");
}
@Override
public void onTetheringFailed(int error) {
log("onTetheringFailed(), error:" + error);
}
}
private void log(String msg) {
FeatureFactory.getFeatureFactory().getWifiFeatureProvider().verboseLog(TAG, msg);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2019 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.savedaccesspoints2;
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.wifi.WifiEntryPreference;
import com.android.wifitrackerlib.WifiEntry;
import java.util.ArrayList;
import java.util.List;
/**
* Controller that manages a PreferenceGroup, which contains a list of saved access points.
*/
public class SavedAccessPointsPreferenceController2 extends BasePreferenceController implements
Preference.OnPreferenceClickListener {
private PreferenceGroup mPreferenceGroup;
private SavedAccessPointsWifiSettings2 mHost;
@VisibleForTesting
List<WifiEntry> mWifiEntries = new ArrayList<>();
public SavedAccessPointsPreferenceController2(Context context, String preferenceKey) {
super(context, preferenceKey);
}
/**
* Set {@link SavedAccessPointsWifiSettings2} for click callback action.
*/
public SavedAccessPointsPreferenceController2 setHost(SavedAccessPointsWifiSettings2 host) {
mHost = host;
return this;
}
@Override
public int getAvailabilityStatus() {
return mWifiEntries.size() > 0 ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreferenceGroup = screen.findPreference(getPreferenceKey());
updatePreference();
super.displayPreference(screen);
}
@VisibleForTesting
void displayPreference(PreferenceScreen screen, List<WifiEntry> wifiEntries) {
if (wifiEntries == null || wifiEntries.isEmpty()) {
mWifiEntries.clear();
} else {
mWifiEntries = wifiEntries;
}
displayPreference(screen);
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (mHost != null) {
mHost.showWifiPage(preference.getKey(), preference.getTitle());
}
return false;
}
/**
* mPreferenceGroup is not in a RecyclerView. To keep TalkBack focus, this method should not
* mPreferenceGroup.removeAll() then mPreferenceGroup.addPreference for mWifiEntries.
*/
private void updatePreference() {
// Update WifiEntry to existing preference and find out which WifiEntry was removed by key.
List<String> removedKeys = new ArrayList<>();
int preferenceCount = mPreferenceGroup.getPreferenceCount();
for (int i = 0; i < preferenceCount; i++) {
WifiEntryPreference pref = (WifiEntryPreference) mPreferenceGroup.getPreference(i);
WifiEntry wifiEntry = mWifiEntries.stream()
.filter(entry -> TextUtils.equals(pref.getKey(), entry.getKey()))
.findFirst()
.orElse(null);
if (wifiEntry != null) {
pref.setWifiEntry(wifiEntry);
} else {
removedKeys.add(pref.getKey());
}
}
// Remove preference by WifiEntry's key.
for (String removedKey : removedKeys) {
mPreferenceGroup.removePreference(mPreferenceGroup.findPreference(removedKey));
}
// Add the Preference of new added WifiEntry.
for (WifiEntry wifiEntry : mWifiEntries) {
if (mPreferenceGroup.findPreference(wifiEntry.getKey()) == null) {
WifiEntryPreference preference = new WifiEntryPreference(mContext, wifiEntry);
preference.setKey(wifiEntry.getKey());
preference.setOnPreferenceClickListener(this);
mPreferenceGroup.addPreference(preference);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More