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