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