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