Home | History | Annotate | Download | only in wifi
      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.Gravity;
     38 import android.view.View;
     39 import android.view.inputmethod.InputMethodManager;
     40 import android.widget.Button;
     41 import android.widget.CheckBox;
     42 import android.widget.CompoundButton;
     43 import android.widget.LinearLayout;
     44 import android.widget.ProgressBar;
     45 import android.widget.TextView;
     46 
     47 import com.android.settings.R;
     48 
     49 import java.io.IOException;
     50 
     51 class 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