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