fix: 引入Settings的Module
This commit is contained in:
260
Settings/src/com/android/settings/wifi/AddNetworkFragment.java
Normal file
260
Settings/src/com/android/settings/wifi/AddNetworkFragment.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
130
Settings/src/com/android/settings/wifi/LinkablePreference.java
Normal file
130
Settings/src/com/android/settings/wifi/LinkablePreference.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Settings/src/com/android/settings/wifi/OWNERS
Normal file
6
Settings/src/com/android/settings/wifi/OWNERS
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { }
|
||||
})
|
||||
}
|
||||
157
Settings/src/com/android/settings/wifi/WifiAPITest.java
Normal file
157
Settings/src/com/android/settings/wifi/WifiAPITest.java
Normal 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;
|
||||
}
|
||||
}
|
||||
1878
Settings/src/com/android/settings/wifi/WifiConfigController.java
Normal file
1878
Settings/src/com/android/settings/wifi/WifiConfigController.java
Normal file
File diff suppressed because it is too large
Load Diff
1990
Settings/src/com/android/settings/wifi/WifiConfigController2.java
Normal file
1990
Settings/src/com/android/settings/wifi/WifiConfigController2.java
Normal file
File diff suppressed because it is too large
Load Diff
61
Settings/src/com/android/settings/wifi/WifiConfigInfo.java
Normal file
61
Settings/src/com/android/settings/wifi/WifiConfigInfo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
62
Settings/src/com/android/settings/wifi/WifiConfigUiBase.java
Normal file
62
Settings/src/com/android/settings/wifi/WifiConfigUiBase.java
Normal 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();
|
||||
}
|
||||
107
Settings/src/com/android/settings/wifi/WifiConfigUiBase2.java
Normal file
107
Settings/src/com/android/settings/wifi/WifiConfigUiBase2.java
Normal 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();
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
207
Settings/src/com/android/settings/wifi/WifiDialog.java
Normal file
207
Settings/src/com/android/settings/wifi/WifiDialog.java
Normal 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);
|
||||
}
|
||||
}
|
||||
163
Settings/src/com/android/settings/wifi/WifiDialog2.kt
Normal file
163
Settings/src/com/android/settings/wifi/WifiDialog2.kt
Normal 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
|
||||
}
|
||||
}
|
||||
479
Settings/src/com/android/settings/wifi/WifiDialogActivity.java
Normal file
479
Settings/src/com/android/settings/wifi/WifiDialogActivity.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
210
Settings/src/com/android/settings/wifi/WifiEnabler.java
Normal file
210
Settings/src/com/android/settings/wifi/WifiEnabler.java
Normal 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;
|
||||
}
|
||||
}
|
||||
335
Settings/src/com/android/settings/wifi/WifiEntryPreference.java
Normal file
335
Settings/src/com/android/settings/wifi/WifiEntryPreference.java
Normal 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;
|
||||
}
|
||||
}
|
||||
40
Settings/src/com/android/settings/wifi/WifiInfo.java
Normal file
40
Settings/src/com/android/settings/wifi/WifiInfo.java
Normal 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;
|
||||
}
|
||||
}
|
||||
222
Settings/src/com/android/settings/wifi/WifiNoInternetDialog.java
Normal file
222
Settings/src/com/android/settings/wifi/WifiNoInternetDialog.java
Normal 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" : ""));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
204
Settings/src/com/android/settings/wifi/WifiScanModeActivity.java
Normal file
204
Settings/src/com/android/settings/wifi/WifiScanModeActivity.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
383
Settings/src/com/android/settings/wifi/WifiStatusTest.java
Normal file
383
Settings/src/com/android/settings/wifi/WifiStatusTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
110
Settings/src/com/android/settings/wifi/WifiSummaryUpdater.java
Normal file
110
Settings/src/com/android/settings/wifi/WifiSummaryUpdater.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
311
Settings/src/com/android/settings/wifi/WifiUtils.java
Normal file
311
Settings/src/com/android/settings/wifi/WifiUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
9
Settings/src/com/android/settings/wifi/calling/OWNERS
Normal file
9
Settings/src/com/android/settings/wifi/calling/OWNERS
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
66
Settings/src/com/android/settings/wifi/dpp/AdbQrCode.java
Normal file
66
Settings/src/com/android/settings/wifi/dpp/AdbQrCode.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
582
Settings/src/com/android/settings/wifi/dpp/WifiDppUtils.java
Normal file
582
Settings/src/com/android/settings/wifi/dpp/WifiDppUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
246
Settings/src/com/android/settings/wifi/dpp/WifiQrCode.java
Normal file
246
Settings/src/com/android/settings/wifi/dpp/WifiQrCode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Settings/src/com/android/settings/wifi/p2p/WifiP2pPeer.java
Normal file
96
Settings/src/com/android/settings/wifi/p2p/WifiP2pPeer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
683
Settings/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
Normal file
683
Settings/src/com/android/settings/wifi/p2p/WifiP2pSettings.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user