1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.wifi; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.net.wifi.WifiManager; 24 import android.nfc.FormatException; 25 import android.nfc.NdefMessage; 26 import android.nfc.NdefRecord; 27 import android.nfc.NfcAdapter; 28 import android.nfc.Tag; 29 import android.nfc.tech.Ndef; 30 import android.os.Bundle; 31 import android.os.PowerManager; 32 import android.text.Editable; 33 import android.text.InputType; 34 import android.text.TextWatcher; 35 import android.util.Log; 36 import android.view.View; 37 import android.view.inputmethod.InputMethodManager; 38 import android.widget.Button; 39 import android.widget.CheckBox; 40 import android.widget.CompoundButton; 41 import android.widget.ProgressBar; 42 import android.widget.TextView; 43 44 import com.android.settings.R; 45 import com.android.settingslib.wifi.AccessPoint; 46 47 import java.io.IOException; 48 49 class WriteWifiConfigToNfcDialog extends AlertDialog 50 implements TextWatcher, View.OnClickListener, CompoundButton.OnCheckedChangeListener { 51 52 private static final String NFC_TOKEN_MIME_TYPE = "application/vnd.wfa.wsc"; 53 54 private static final String TAG = WriteWifiConfigToNfcDialog.class.getName().toString(); 55 private static final String PASSWORD_FORMAT = "102700%s%s"; 56 private static final int HEX_RADIX = 16; 57 private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); 58 private static final String SECURITY = "security"; 59 60 private final PowerManager.WakeLock mWakeLock; 61 62 private View mView; 63 private Button mSubmitButton; 64 private Button mCancelButton; 65 private TextView mPasswordView; 66 private TextView mLabelView; 67 private CheckBox mPasswordCheckBox; 68 private ProgressBar mProgressBar; 69 private WifiManager mWifiManager; 70 private String mWpsNfcConfigurationToken; 71 private Context mContext; 72 private int mSecurity; 73 WriteWifiConfigToNfcDialog(Context context, int security)74 WriteWifiConfigToNfcDialog(Context context, int security) { 75 super(context); 76 77 mContext = context; 78 mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)) 79 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WriteWifiConfigToNfcDialog:wakeLock"); 80 mSecurity = security; 81 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 82 } 83 WriteWifiConfigToNfcDialog(Context context, Bundle savedState)84 WriteWifiConfigToNfcDialog(Context context, Bundle savedState) { 85 super(context); 86 87 mContext = context; 88 mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)) 89 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WriteWifiConfigToNfcDialog:wakeLock"); 90 mSecurity = savedState.getInt(SECURITY); 91 mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 92 } 93 94 @Override onCreate(Bundle savedInstanceState)95 public void onCreate(Bundle savedInstanceState) { 96 mView = getLayoutInflater().inflate(R.layout.write_wifi_config_to_nfc, null); 97 98 setView(mView); 99 setInverseBackgroundForced(true); 100 setTitle(R.string.setup_wifi_nfc_tag); 101 setCancelable(true); 102 setButton(DialogInterface.BUTTON_NEUTRAL, 103 mContext.getResources().getString(R.string.write_tag), (OnClickListener) null); 104 setButton(DialogInterface.BUTTON_NEGATIVE, 105 mContext.getResources().getString(com.android.internal.R.string.cancel), 106 (OnClickListener) null); 107 108 mPasswordView = mView.findViewById(R.id.password); 109 mLabelView = mView.findViewById(R.id.password_label); 110 mPasswordView.addTextChangedListener(this); 111 mPasswordCheckBox = mView.findViewById(R.id.show_password); 112 mPasswordCheckBox.setOnCheckedChangeListener(this); 113 mProgressBar = mView.findViewById(R.id.progress_bar); 114 115 super.onCreate(savedInstanceState); 116 117 mSubmitButton = getButton(DialogInterface.BUTTON_NEUTRAL); 118 mSubmitButton.setOnClickListener(this); 119 mSubmitButton.setEnabled(false); 120 121 mCancelButton = getButton(DialogInterface.BUTTON_NEGATIVE); 122 } 123 124 @Override onClick(View v)125 public void onClick(View v) { 126 mWakeLock.acquire(); 127 128 String password = mPasswordView.getText().toString(); 129 String wpsNfcConfigurationToken = mWifiManager.getCurrentNetworkWpsNfcConfigurationToken(); 130 String passwordHex = byteArrayToHexString(password.getBytes()); 131 132 String passwordLength = password.length() >= HEX_RADIX 133 ? Integer.toString(password.length(), HEX_RADIX) 134 : "0" + Character.forDigit(password.length(), HEX_RADIX); 135 136 passwordHex = String.format(PASSWORD_FORMAT, passwordLength, passwordHex).toLowerCase(); 137 138 if (wpsNfcConfigurationToken != null && wpsNfcConfigurationToken.contains(passwordHex)) { 139 mWpsNfcConfigurationToken = wpsNfcConfigurationToken; 140 141 Activity activity = getOwnerActivity(); 142 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity); 143 144 nfcAdapter.enableReaderMode(activity, new NfcAdapter.ReaderCallback() { 145 @Override 146 public void onTagDiscovered(Tag tag) { 147 handleWriteNfcEvent(tag); 148 } 149 }, NfcAdapter.FLAG_READER_NFC_A | 150 NfcAdapter.FLAG_READER_NFC_B | 151 NfcAdapter.FLAG_READER_NFC_BARCODE | 152 NfcAdapter.FLAG_READER_NFC_F | 153 NfcAdapter.FLAG_READER_NFC_V, 154 null); 155 156 mPasswordView.setVisibility(View.GONE); 157 mPasswordCheckBox.setVisibility(View.GONE); 158 mSubmitButton.setVisibility(View.GONE); 159 InputMethodManager imm = (InputMethodManager) 160 getOwnerActivity().getSystemService(Context.INPUT_METHOD_SERVICE); 161 imm.hideSoftInputFromWindow(mPasswordView.getWindowToken(), 0); 162 163 mLabelView.setText(R.string.status_awaiting_tap); 164 165 mView.findViewById(R.id.password_layout).setTextAlignment(View.TEXT_ALIGNMENT_CENTER); 166 mProgressBar.setVisibility(View.VISIBLE); 167 } else { 168 mLabelView.setText(R.string.status_invalid_password); 169 } 170 } 171 saveState(Bundle state)172 public void saveState(Bundle state) { 173 state.putInt(SECURITY, mSecurity); 174 } 175 handleWriteNfcEvent(Tag tag)176 private void handleWriteNfcEvent(Tag tag) { 177 Ndef ndef = Ndef.get(tag); 178 179 if (ndef != null) { 180 if (ndef.isWritable()) { 181 NdefRecord record = NdefRecord.createMime( 182 NFC_TOKEN_MIME_TYPE, 183 hexStringToByteArray(mWpsNfcConfigurationToken)); 184 try { 185 ndef.connect(); 186 ndef.writeNdefMessage(new NdefMessage(record)); 187 getOwnerActivity().runOnUiThread(new Runnable() { 188 @Override 189 public void run() { 190 mProgressBar.setVisibility(View.GONE); 191 } 192 }); 193 setViewText(mLabelView, R.string.status_write_success); 194 setViewText(mCancelButton, com.android.internal.R.string.done_label); 195 } catch (IOException e) { 196 setViewText(mLabelView, R.string.status_failed_to_write); 197 Log.e(TAG, "Unable to write Wi-Fi config to NFC tag.", e); 198 return; 199 } catch (FormatException e) { 200 setViewText(mLabelView, R.string.status_failed_to_write); 201 Log.e(TAG, "Unable to write Wi-Fi config to NFC tag.", e); 202 return; 203 } 204 } else { 205 setViewText(mLabelView, R.string.status_tag_not_writable); 206 Log.e(TAG, "Tag is not writable"); 207 } 208 } else { 209 setViewText(mLabelView, R.string.status_tag_not_writable); 210 Log.e(TAG, "Tag does not support NDEF"); 211 } 212 } 213 214 @Override dismiss()215 public void dismiss() { 216 if (mWakeLock.isHeld()) { 217 mWakeLock.release(); 218 } 219 220 super.dismiss(); 221 } 222 223 @Override onTextChanged(CharSequence s, int start, int before, int count)224 public void onTextChanged(CharSequence s, int start, int before, int count) { 225 enableSubmitIfAppropriate(); 226 } 227 enableSubmitIfAppropriate()228 private void enableSubmitIfAppropriate() { 229 230 if (mPasswordView != null) { 231 if (mSecurity == AccessPoint.SECURITY_WEP) { 232 mSubmitButton.setEnabled(mPasswordView.length() > 0); 233 } else if (mSecurity == AccessPoint.SECURITY_PSK) { 234 mSubmitButton.setEnabled(mPasswordView.length() >= 8); 235 } 236 } else { 237 mSubmitButton.setEnabled(false); 238 } 239 240 } 241 setViewText(final TextView view, final int resid)242 private void setViewText(final TextView view, final int resid) { 243 getOwnerActivity().runOnUiThread(new Runnable() { 244 @Override 245 public void run() { 246 view.setText(resid); 247 } 248 }); 249 } 250 251 @Override onCheckedChanged(CompoundButton buttonView, boolean isChecked)252 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 253 mPasswordView.setInputType( 254 InputType.TYPE_CLASS_TEXT | 255 (isChecked 256 ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD 257 : InputType.TYPE_TEXT_VARIATION_PASSWORD)); 258 } 259 hexStringToByteArray(String s)260 private static byte[] hexStringToByteArray(String s) { 261 int len = s.length(); 262 byte[] data = new byte[len / 2]; 263 264 for (int i = 0; i < len; i += 2) { 265 data[i / 2] = (byte) ((Character.digit(s.charAt(i), HEX_RADIX) << 4) 266 + Character.digit(s.charAt(i + 1), HEX_RADIX)); 267 } 268 269 return data; 270 } 271 byteArrayToHexString(byte[] bytes)272 private static String byteArrayToHexString(byte[] bytes) { 273 char[] hexChars = new char[bytes.length * 2]; 274 for ( int j = 0; j < bytes.length; j++ ) { 275 int v = bytes[j] & 0xFF; 276 hexChars[j * 2] = hexArray[v >>> 4]; 277 hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 278 } 279 return new String(hexChars); 280 } 281 282 @Override beforeTextChanged(CharSequence s, int start, int count, int after)283 public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 284 285 @Override afterTextChanged(Editable s)286 public void afterTextChanged(Editable s) {} 287 } 288