124b50803fa603b04e74752beb19f85038515919aroger xue/* 224b50803fa603b04e74752beb19f85038515919aroger xue * Copyright (C) 2017 The Android Open Source Project 324b50803fa603b04e74752beb19f85038515919aroger xue * 424b50803fa603b04e74752beb19f85038515919aroger xue * Licensed under the Apache License, Version 2.0 (the "License"); 524b50803fa603b04e74752beb19f85038515919aroger xue * you may not use this file except in compliance with the License. 624b50803fa603b04e74752beb19f85038515919aroger xue * You may obtain a copy of the License at 724b50803fa603b04e74752beb19f85038515919aroger xue * 824b50803fa603b04e74752beb19f85038515919aroger xue * http://www.apache.org/licenses/LICENSE-2.0 924b50803fa603b04e74752beb19f85038515919aroger xue * 1024b50803fa603b04e74752beb19f85038515919aroger xue * Unless required by applicable law or agreed to in writing, software 1124b50803fa603b04e74752beb19f85038515919aroger xue * distributed under the License is distributed on an "AS IS" BASIS, 1224b50803fa603b04e74752beb19f85038515919aroger xue * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1324b50803fa603b04e74752beb19f85038515919aroger xue * See the License for the specific language governing permissions and 1424b50803fa603b04e74752beb19f85038515919aroger xue * limitations under the License. 1524b50803fa603b04e74752beb19f85038515919aroger xue */ 1624b50803fa603b04e74752beb19f85038515919aroger xuepackage com.android.car.settings.wifi; 1724b50803fa603b04e74752beb19f85038515919aroger xue 1824b50803fa603b04e74752beb19f85038515919aroger xueimport android.net.wifi.WifiConfiguration; 1924b50803fa603b04e74752beb19f85038515919aroger xueimport android.net.wifi.WifiManager; 2024b50803fa603b04e74752beb19f85038515919aroger xueimport android.os.Bundle; 2124b50803fa603b04e74752beb19f85038515919aroger xueimport android.support.annotation.Nullable; 22fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xueimport android.util.Log; 23664ed2dc2488c50194a80004af759a765f0ce18froger xueimport android.view.View; 24664ed2dc2488c50194a80004af759a765f0ce18froger xueimport android.widget.AdapterView; 2524b50803fa603b04e74752beb19f85038515919aroger xueimport android.widget.TextView; 2624b50803fa603b04e74752beb19f85038515919aroger xueimport android.widget.Toast; 2724b50803fa603b04e74752beb19f85038515919aroger xue 2824b50803fa603b04e74752beb19f85038515919aroger xueimport com.android.car.settings.R; 29664ed2dc2488c50194a80004af759a765f0ce18froger xueimport com.android.car.settings.common.EditTextLineItem; 30664ed2dc2488c50194a80004af759a765f0ce18froger xueimport com.android.car.settings.common.ListSettingsFragment; 31664ed2dc2488c50194a80004af759a765f0ce18froger xueimport com.android.car.settings.common.PasswordLineItem; 32664ed2dc2488c50194a80004af759a765f0ce18froger xueimport com.android.car.settings.common.SpinnerLineItem; 33664ed2dc2488c50194a80004af759a765f0ce18froger xueimport com.android.car.settings.common.TypedPagedListAdapter; 3424b50803fa603b04e74752beb19f85038515919aroger xueimport com.android.settingslib.wifi.AccessPoint; 3524b50803fa603b04e74752beb19f85038515919aroger xue 36664ed2dc2488c50194a80004af759a765f0ce18froger xueimport java.util.ArrayList; 37664ed2dc2488c50194a80004af759a765f0ce18froger xueimport java.util.List; 3824b50803fa603b04e74752beb19f85038515919aroger xueimport java.util.regex.Pattern; 3924b50803fa603b04e74752beb19f85038515919aroger xue 4024b50803fa603b04e74752beb19f85038515919aroger xue/** 4124b50803fa603b04e74752beb19f85038515919aroger xue * Adds a wifi network, the network can be public or private. If ADD_NETWORK_MODE is not specified 4224b50803fa603b04e74752beb19f85038515919aroger xue * in the intent, then it needs to contain AccessPoint information, which is be use that to 4324b50803fa603b04e74752beb19f85038515919aroger xue * render UI, e.g. show SSID etc. 4424b50803fa603b04e74752beb19f85038515919aroger xue */ 45664ed2dc2488c50194a80004af759a765f0ce18froger xuepublic class AddWifiFragment extends ListSettingsFragment implements 46664ed2dc2488c50194a80004af759a765f0ce18froger xue AdapterView.OnItemSelectedListener{ 4724b50803fa603b04e74752beb19f85038515919aroger xue public static final String EXTRA_AP_STATE = "extra_ap_state"; 4824b50803fa603b04e74752beb19f85038515919aroger xue 4924b50803fa603b04e74752beb19f85038515919aroger xue private static final String TAG = "AddWifiFragment"; 5024b50803fa603b04e74752beb19f85038515919aroger xue private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$"); 51b79e035a90ea30c59586fc3272ce60fa19cba616Lujiang Xue private static final Pattern VALID_SSID_PATTERN = 52b79e035a90ea30c59586fc3272ce60fa19cba616Lujiang Xue Pattern.compile("^[A-Za-z]+[\\w\\-\\:\\.]*$"); 5324b50803fa603b04e74752beb19f85038515919aroger xue @Nullable private AccessPoint mAccessPoint; 54fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue @Nullable private SpinnerLineItem<AccessPointSecurity> mSpinnerLineItem; 5524b50803fa603b04e74752beb19f85038515919aroger xue private WifiManager mWifiManager; 5640b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue private TextView mAddWifiButton; 5724b50803fa603b04e74752beb19f85038515919aroger xue private final WifiManager.ActionListener mConnectionListener = 5824b50803fa603b04e74752beb19f85038515919aroger xue new WifiManager.ActionListener() { 5924b50803fa603b04e74752beb19f85038515919aroger xue @Override 6024b50803fa603b04e74752beb19f85038515919aroger xue public void onSuccess() { 6124b50803fa603b04e74752beb19f85038515919aroger xue } 6224b50803fa603b04e74752beb19f85038515919aroger xue 6324b50803fa603b04e74752beb19f85038515919aroger xue @Override 6424b50803fa603b04e74752beb19f85038515919aroger xue public void onFailure(int reason) { 6524b50803fa603b04e74752beb19f85038515919aroger xue Toast.makeText(getContext(), 6624b50803fa603b04e74752beb19f85038515919aroger xue R.string.wifi_failed_connect_message, 6724b50803fa603b04e74752beb19f85038515919aroger xue Toast.LENGTH_SHORT).show(); 6824b50803fa603b04e74752beb19f85038515919aroger xue } 6924b50803fa603b04e74752beb19f85038515919aroger xue }; 70664ed2dc2488c50194a80004af759a765f0ce18froger xue private EditTextLineItem mWifiNameInput; 71664ed2dc2488c50194a80004af759a765f0ce18froger xue private EditTextLineItem mWifiPasswordInput; 72664ed2dc2488c50194a80004af759a765f0ce18froger xue 73664ed2dc2488c50194a80004af759a765f0ce18froger xue private int mSelectedPosition = AccessPointSecurity.SECURITY_NONE_POSITION; 7424b50803fa603b04e74752beb19f85038515919aroger xue 7524b50803fa603b04e74752beb19f85038515919aroger xue public static AddWifiFragment getInstance(AccessPoint accessPoint) { 7624b50803fa603b04e74752beb19f85038515919aroger xue AddWifiFragment addWifiFragment = new AddWifiFragment(); 77664ed2dc2488c50194a80004af759a765f0ce18froger xue Bundle bundle = ListSettingsFragment.getBundle(); 7824b50803fa603b04e74752beb19f85038515919aroger xue bundle.putInt(EXTRA_TITLE_ID, R.string.wifi_setup_add_network); 79664ed2dc2488c50194a80004af759a765f0ce18froger xue bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_with_button); 8024b50803fa603b04e74752beb19f85038515919aroger xue Bundle accessPointState = new Bundle(); 8124b50803fa603b04e74752beb19f85038515919aroger xue if (accessPoint != null) { 8224b50803fa603b04e74752beb19f85038515919aroger xue accessPoint.saveWifiState(accessPointState); 8324b50803fa603b04e74752beb19f85038515919aroger xue bundle.putBundle(EXTRA_AP_STATE, accessPointState); 8424b50803fa603b04e74752beb19f85038515919aroger xue } 8524b50803fa603b04e74752beb19f85038515919aroger xue addWifiFragment.setArguments(bundle); 8624b50803fa603b04e74752beb19f85038515919aroger xue return addWifiFragment; 8724b50803fa603b04e74752beb19f85038515919aroger xue } 8824b50803fa603b04e74752beb19f85038515919aroger xue 8924b50803fa603b04e74752beb19f85038515919aroger xue @Override 9024b50803fa603b04e74752beb19f85038515919aroger xue public void onCreate(Bundle savedInstanceState) { 9124b50803fa603b04e74752beb19f85038515919aroger xue super.onCreate(savedInstanceState); 9224b50803fa603b04e74752beb19f85038515919aroger xue if (getArguments().keySet().contains(EXTRA_AP_STATE)) { 9324b50803fa603b04e74752beb19f85038515919aroger xue mAccessPoint = new AccessPoint(getContext(), getArguments().getBundle(EXTRA_AP_STATE)); 9424b50803fa603b04e74752beb19f85038515919aroger xue } 95664ed2dc2488c50194a80004af759a765f0ce18froger xue mWifiManager = getContext().getSystemService(WifiManager.class); 9624b50803fa603b04e74752beb19f85038515919aroger xue } 9724b50803fa603b04e74752beb19f85038515919aroger xue 9824b50803fa603b04e74752beb19f85038515919aroger xue @Override 9924b50803fa603b04e74752beb19f85038515919aroger xue public void onActivityCreated(Bundle savedInstanceState) { 10024b50803fa603b04e74752beb19f85038515919aroger xue super.onActivityCreated(savedInstanceState); 10124b50803fa603b04e74752beb19f85038515919aroger xue 10240b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue mAddWifiButton = getActivity().findViewById(R.id.action_button1); 10340b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue mAddWifiButton.setText(R.string.wifi_setup_connect); 10440b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue mAddWifiButton.setOnClickListener(v -> { 10540b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue connectToAccessPoint(); 10640b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue mFragmentController.goBack(); 10740b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue }); 10840b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue mAddWifiButton.setEnabled(mAccessPoint != null) ; 10924b50803fa603b04e74752beb19f85038515919aroger xue } 11024b50803fa603b04e74752beb19f85038515919aroger xue 111664ed2dc2488c50194a80004af759a765f0ce18froger xue @Override 112664ed2dc2488c50194a80004af759a765f0ce18froger xue public ArrayList<TypedPagedListAdapter.LineItem> getLineItems() { 113664ed2dc2488c50194a80004af759a765f0ce18froger xue ArrayList<TypedPagedListAdapter.LineItem> lineItems = new ArrayList<>(); 114664ed2dc2488c50194a80004af759a765f0ce18froger xue if (mAccessPoint != null) { 115664ed2dc2488c50194a80004af759a765f0ce18froger xue mWifiNameInput = new EditTextLineItem( 116664ed2dc2488c50194a80004af759a765f0ce18froger xue getContext().getText(R.string.wifi_ssid), mAccessPoint.getSsid()); 117664ed2dc2488c50194a80004af759a765f0ce18froger xue mWifiNameInput.setTextType(EditTextLineItem.TextType.NONE); 118664ed2dc2488c50194a80004af759a765f0ce18froger xue } else { 119664ed2dc2488c50194a80004af759a765f0ce18froger xue mWifiNameInput = new EditTextLineItem( 120664ed2dc2488c50194a80004af759a765f0ce18froger xue getContext().getText(R.string.wifi_ssid)); 121664ed2dc2488c50194a80004af759a765f0ce18froger xue mWifiNameInput.setTextType(EditTextLineItem.TextType.TEXT); 12240b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue mWifiNameInput.setTextChangeListener(s -> 123b79e035a90ea30c59586fc3272ce60fa19cba616Lujiang Xue mAddWifiButton.setEnabled(VALID_SSID_PATTERN.matcher(s).matches())); 124664ed2dc2488c50194a80004af759a765f0ce18froger xue } 125664ed2dc2488c50194a80004af759a765f0ce18froger xue lineItems.add(mWifiNameInput); 126664ed2dc2488c50194a80004af759a765f0ce18froger xue 127664ed2dc2488c50194a80004af759a765f0ce18froger xue if (mAccessPoint == null) { 128664ed2dc2488c50194a80004af759a765f0ce18froger xue List<AccessPointSecurity> securities = 129fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue AccessPointSecurity.getSecurityTypes(getContext()); 130fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue mSpinnerLineItem = new SpinnerLineItem<>( 131664ed2dc2488c50194a80004af759a765f0ce18froger xue getContext(), 132664ed2dc2488c50194a80004af759a765f0ce18froger xue this, 133664ed2dc2488c50194a80004af759a765f0ce18froger xue securities, 134664ed2dc2488c50194a80004af759a765f0ce18froger xue getContext().getText(R.string.wifi_security), 135fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue mSelectedPosition); 136fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue lineItems.add(mSpinnerLineItem); 137664ed2dc2488c50194a80004af759a765f0ce18froger xue } 138664ed2dc2488c50194a80004af759a765f0ce18froger xue 139664ed2dc2488c50194a80004af759a765f0ce18froger xue if (mAccessPoint!= null 140664ed2dc2488c50194a80004af759a765f0ce18froger xue || mSelectedPosition != AccessPointSecurity.SECURITY_NONE_POSITION) { 141664ed2dc2488c50194a80004af759a765f0ce18froger xue mWifiPasswordInput = new PasswordLineItem(getContext().getText(R.string.wifi_password)); 142664ed2dc2488c50194a80004af759a765f0ce18froger xue lineItems.add(mWifiPasswordInput); 143664ed2dc2488c50194a80004af759a765f0ce18froger xue } 144664ed2dc2488c50194a80004af759a765f0ce18froger xue return lineItems; 145664ed2dc2488c50194a80004af759a765f0ce18froger xue } 146664ed2dc2488c50194a80004af759a765f0ce18froger xue 147664ed2dc2488c50194a80004af759a765f0ce18froger xue @Override 148664ed2dc2488c50194a80004af759a765f0ce18froger xue public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 149664ed2dc2488c50194a80004af759a765f0ce18froger xue if (position == mSelectedPosition) { 150664ed2dc2488c50194a80004af759a765f0ce18froger xue return; 151664ed2dc2488c50194a80004af759a765f0ce18froger xue } 152664ed2dc2488c50194a80004af759a765f0ce18froger xue mSelectedPosition = position; 153664ed2dc2488c50194a80004af759a765f0ce18froger xue mPagedListAdapter.updateList(getLineItems()); 154664ed2dc2488c50194a80004af759a765f0ce18froger xue } 155664ed2dc2488c50194a80004af759a765f0ce18froger xue 156664ed2dc2488c50194a80004af759a765f0ce18froger xue @Override 157664ed2dc2488c50194a80004af759a765f0ce18froger xue public void onNothingSelected(AdapterView<?> parent) { 158664ed2dc2488c50194a80004af759a765f0ce18froger xue } 159664ed2dc2488c50194a80004af759a765f0ce18froger xue 16024b50803fa603b04e74752beb19f85038515919aroger xue private void connectToAccessPoint() { 16124b50803fa603b04e74752beb19f85038515919aroger xue WifiConfiguration wifiConfig = new WifiConfiguration(); 16224b50803fa603b04e74752beb19f85038515919aroger xue wifiConfig.SSID = String.format("\"%s\"", getSsId()); 16324b50803fa603b04e74752beb19f85038515919aroger xue wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); 16424b50803fa603b04e74752beb19f85038515919aroger xue wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); 16524b50803fa603b04e74752beb19f85038515919aroger xue wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN); 16624b50803fa603b04e74752beb19f85038515919aroger xue wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); 16724b50803fa603b04e74752beb19f85038515919aroger xue wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); 16824b50803fa603b04e74752beb19f85038515919aroger xue wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104); 169fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue int security; 17024b50803fa603b04e74752beb19f85038515919aroger xue if (mAccessPoint == null) { 171fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue security = mSpinnerLineItem.getItem(mSelectedPosition).getSecurityType(); 172fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.hiddenSSID = true; 17324b50803fa603b04e74752beb19f85038515919aroger xue } else { 174fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue security = mAccessPoint.getSecurity(); 175fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue } 176fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue switch (security) { 177fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue case AccessPoint.SECURITY_NONE: 178fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 179fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedAuthAlgorithms.clear(); 180fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); 181fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); 182fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue break; 183fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue case AccessPoint.SECURITY_WEP: 184fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 185fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); 186fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED); 187fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue String password = mWifiPasswordInput.getInput(); 188fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.wepKeys[0] = isHexString(password) ? password 189fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue : "\"" + password + "\""; 190fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.wepTxKeyIndex = 0; 191fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue break; 192fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue case AccessPoint.SECURITY_PSK: 193fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue case AccessPoint.SECURITY_EAP: 194fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); 195fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); 196fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); 197fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue wifiConfig.preSharedKey = String.format( 198fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue "\"%s\"", mWifiPasswordInput.getInput()); 199fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue break; 200fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue default: 201fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue Log.w(TAG, "invalid security type: " + security); 202fe73ade9d187b097b60d7e9a027fac3eeedb4b4dLujiang Xue break; 20324b50803fa603b04e74752beb19f85038515919aroger xue } 20424b50803fa603b04e74752beb19f85038515919aroger xue int netId = mWifiManager.addNetwork(wifiConfig); 20540b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue if (netId == -1) { 20640b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue Toast.makeText(getContext(), 20740b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue R.string.wifi_failed_connect_message, 20840b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue Toast.LENGTH_SHORT).show(); 20940b2b90f1b7ca418cb60aab20b9832870538559fLujiang Xue } else { 21024b50803fa603b04e74752beb19f85038515919aroger xue mWifiManager.enableNetwork(netId, true); 21124b50803fa603b04e74752beb19f85038515919aroger xue } 21224b50803fa603b04e74752beb19f85038515919aroger xue } 21324b50803fa603b04e74752beb19f85038515919aroger xue 21424b50803fa603b04e74752beb19f85038515919aroger xue private boolean isHexString(String password) { 21524b50803fa603b04e74752beb19f85038515919aroger xue return HEX_PATTERN.matcher(password).matches(); 21624b50803fa603b04e74752beb19f85038515919aroger xue } 21724b50803fa603b04e74752beb19f85038515919aroger xue 21824b50803fa603b04e74752beb19f85038515919aroger xue // TODO: handle null case, show warning message etc. 21924b50803fa603b04e74752beb19f85038515919aroger xue private String getSsId() { 22024b50803fa603b04e74752beb19f85038515919aroger xue if (mAccessPoint == null) { 221664ed2dc2488c50194a80004af759a765f0ce18froger xue return mWifiNameInput.getInput(); 22224b50803fa603b04e74752beb19f85038515919aroger xue } else { 22324b50803fa603b04e74752beb19f85038515919aroger xue return mAccessPoint.getSsid().toString(); 22424b50803fa603b04e74752beb19f85038515919aroger xue } 22524b50803fa603b04e74752beb19f85038515919aroger xue } 22624b50803fa603b04e74752beb19f85038515919aroger xue} 227