platform-packages-apps-Settings / src / com / android / settings / wifi / WriteWifiConfigToNfcDialog.java
WriteWifiConfigToNfcDialog.java
Raw
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.wifi;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.wifi.WifiManager;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.os.PowerManager;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.android.settings.R;
import com.android.settingslib.wifi.AccessPoint;

import java.io.IOException;

class WriteWifiConfigToNfcDialog extends AlertDialog
        implements TextWatcher, View.OnClickListener, CompoundButton.OnCheckedChangeListener {

    private static final String NFC_TOKEN_MIME_TYPE = "application/vnd.wfa.wsc";

    private static final String TAG = WriteWifiConfigToNfcDialog.class.getName().toString();
    private static final String PASSWORD_FORMAT = "102700%s%s";
    private static final int HEX_RADIX = 16;
    private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
    private static final String SECURITY = "security";

    private final PowerManager.WakeLock mWakeLock;

    private View mView;
    private Button mSubmitButton;
    private Button mCancelButton;
    private TextView mPasswordView;
    private TextView mLabelView;
    private CheckBox mPasswordCheckBox;
    private ProgressBar mProgressBar;
    private WifiManager mWifiManager;
    private String mWpsNfcConfigurationToken;
    private Context mContext;
    private int mSecurity;

    WriteWifiConfigToNfcDialog(Context context, int security) {
        super(context);

        mContext = context;
        mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE))
                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WriteWifiConfigToNfcDialog:wakeLock");
        mSecurity = security;
        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    }

    WriteWifiConfigToNfcDialog(Context context, Bundle savedState) {
        super(context);

        mContext = context;
        mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE))
                .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WriteWifiConfigToNfcDialog:wakeLock");
        mSecurity = savedState.getInt(SECURITY);
        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        mView = getLayoutInflater().inflate(R.layout.write_wifi_config_to_nfc, null);

        setView(mView);
        setInverseBackgroundForced(true);
        setTitle(R.string.setup_wifi_nfc_tag);
        setCancelable(true);
        setButton(DialogInterface.BUTTON_NEUTRAL,
                mContext.getResources().getString(R.string.write_tag), (OnClickListener) null);
        setButton(DialogInterface.BUTTON_NEGATIVE,
                mContext.getResources().getString(com.android.internal.R.string.cancel),
                (OnClickListener) null);

        mPasswordView = mView.findViewById(R.id.password);
        mLabelView = mView.findViewById(R.id.password_label);
        mPasswordView.addTextChangedListener(this);
        mPasswordCheckBox = mView.findViewById(R.id.show_password);
        mPasswordCheckBox.setOnCheckedChangeListener(this);
        mProgressBar = mView.findViewById(R.id.progress_bar);

        super.onCreate(savedInstanceState);

        mSubmitButton = getButton(DialogInterface.BUTTON_NEUTRAL);
        mSubmitButton.setOnClickListener(this);
        mSubmitButton.setEnabled(false);

        mCancelButton = getButton(DialogInterface.BUTTON_NEGATIVE);
    }

    @Override
    public void onClick(View v) {
        mWakeLock.acquire();

        String password = mPasswordView.getText().toString();
        String wpsNfcConfigurationToken = mWifiManager.getCurrentNetworkWpsNfcConfigurationToken();
        String passwordHex = byteArrayToHexString(password.getBytes());

        String passwordLength = password.length() >= HEX_RADIX
                ? Integer.toString(password.length(), HEX_RADIX)
                : "0" + Character.forDigit(password.length(), HEX_RADIX);

        passwordHex = String.format(PASSWORD_FORMAT, passwordLength, passwordHex).toLowerCase();

        if (wpsNfcConfigurationToken != null && wpsNfcConfigurationToken.contains(passwordHex)) {
            mWpsNfcConfigurationToken = wpsNfcConfigurationToken;

            Activity activity = getOwnerActivity();
            NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);

            nfcAdapter.enableReaderMode(activity, new NfcAdapter.ReaderCallback() {
                @Override
                public void onTagDiscovered(Tag tag) {
                    handleWriteNfcEvent(tag);
                }
            }, NfcAdapter.FLAG_READER_NFC_A |
                    NfcAdapter.FLAG_READER_NFC_B |
                    NfcAdapter.FLAG_READER_NFC_BARCODE |
                    NfcAdapter.FLAG_READER_NFC_F |
                    NfcAdapter.FLAG_READER_NFC_V,
                    null);

            mPasswordView.setVisibility(View.GONE);
            mPasswordCheckBox.setVisibility(View.GONE);
            mSubmitButton.setVisibility(View.GONE);
            InputMethodManager imm = (InputMethodManager)
                    getOwnerActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(mPasswordView.getWindowToken(), 0);

            mLabelView.setText(R.string.status_awaiting_tap);

            mView.findViewById(R.id.password_layout).setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
            mProgressBar.setVisibility(View.VISIBLE);
        } else {
            mLabelView.setText(R.string.status_invalid_password);
        }
    }

    public void saveState(Bundle state) {
        state.putInt(SECURITY, mSecurity);
    }

    private void handleWriteNfcEvent(Tag tag) {
        Ndef ndef = Ndef.get(tag);

        if (ndef != null) {
            if (ndef.isWritable()) {
                NdefRecord record = NdefRecord.createMime(
                        NFC_TOKEN_MIME_TYPE,
                        hexStringToByteArray(mWpsNfcConfigurationToken));
                try {
                    ndef.connect();
                    ndef.writeNdefMessage(new NdefMessage(record));
                    getOwnerActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mProgressBar.setVisibility(View.GONE);
                        }
                    });
                    setViewText(mLabelView, R.string.status_write_success);
                    setViewText(mCancelButton, com.android.internal.R.string.done_label);
                } catch (IOException e) {
                    setViewText(mLabelView, R.string.status_failed_to_write);
                    Log.e(TAG, "Unable to write Wi-Fi config to NFC tag.", e);
                    return;
                } catch (FormatException e) {
                    setViewText(mLabelView, R.string.status_failed_to_write);
                    Log.e(TAG, "Unable to write Wi-Fi config to NFC tag.", e);
                    return;
                }
            } else {
                setViewText(mLabelView, R.string.status_tag_not_writable);
                Log.e(TAG, "Tag is not writable");
            }
        } else {
            setViewText(mLabelView, R.string.status_tag_not_writable);
            Log.e(TAG, "Tag does not support NDEF");
        }
    }

    @Override
    public void dismiss() {
        if (mWakeLock.isHeld()) {
            mWakeLock.release();
        }

        super.dismiss();
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        enableSubmitIfAppropriate();
    }

    private void enableSubmitIfAppropriate() {

        if (mPasswordView != null) {
            if (mSecurity == AccessPoint.SECURITY_WEP) {
                mSubmitButton.setEnabled(mPasswordView.length() > 0);
            } else if (mSecurity == AccessPoint.SECURITY_PSK) {
                mSubmitButton.setEnabled(mPasswordView.length() >= 8);
            }
        } else {
            mSubmitButton.setEnabled(false);
        }

    }

    private void setViewText(final TextView view, final int resid) {
        getOwnerActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                view.setText(resid);
            }
        });
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        mPasswordView.setInputType(
                InputType.TYPE_CLASS_TEXT |
                (isChecked
                        ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
                        : InputType.TYPE_TEXT_VARIATION_PASSWORD));
    }

    private static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];

        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), HEX_RADIX) << 4)
                    + Character.digit(s.charAt(i + 1), HEX_RADIX));
        }

        return data;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for ( int j = 0; j < bytes.length; j++ ) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

    @Override
    public void afterTextChanged(Editable s) {}
}