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.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 
     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 
     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
    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
    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 
    182     public void saveState(Bundle state) {
    183         state.putInt(NETWORK_ID, mNetworkId);
    184         state.putInt(SECURITY, mSecurity);
    185     }
    186 
    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
    226     public void dismiss() {
    227         if (mWakeLock.isHeld()) {
    228             mWakeLock.release();
    229         }
    230 
    231         super.dismiss();
    232     }
    233 
    234     @Override
    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 
    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 
    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
    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 
    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 
    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
    299     public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
    300 
    301     @Override
    302     public void afterTextChanged(Editable s) {}
    303 }
    304