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.View;
38import android.view.inputmethod.InputMethodManager;
39import android.widget.Button;
40import android.widget.CheckBox;
41import android.widget.CompoundButton;
42import android.widget.ProgressBar;
43import android.widget.TextView;
44
45import com.android.settings.R;
46import com.android.settingslib.wifi.AccessPoint;
47
48import java.io.IOException;
49
50class 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