Home | History | Annotate | Download | only in nfcprovisioning
      1 /*
      2  * Copyright (C) 2015 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.example.android.nfcprovisioning;
     18 
     19 import android.app.Activity;
     20 import android.app.admin.DevicePolicyManager;
     21 import android.content.ComponentName;
     22 import android.nfc.NdefMessage;
     23 import android.nfc.NdefRecord;
     24 import android.nfc.NfcAdapter;
     25 import android.nfc.NfcEvent;
     26 import android.os.Build;
     27 import android.os.Bundle;
     28 import android.support.annotation.Nullable;
     29 import android.support.v4.app.Fragment;
     30 import android.support.v4.app.LoaderManager;
     31 import android.support.v4.content.Loader;
     32 import android.text.TextUtils;
     33 import android.view.LayoutInflater;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 import android.widget.EditText;
     37 
     38 import java.io.ByteArrayOutputStream;
     39 import java.io.IOException;
     40 import java.util.Map;
     41 import java.util.Properties;
     42 
     43 /**
     44  * Provides UI and logic for NFC provisioning.
     45  * <p>
     46  * This fragment creates an intent, which sends parameters to a second device via an Nfc bump. If
     47  * the second device is factory reset, this will start provisioning the second device to set it up
     48  * as an owned device.
     49  * </p>
     50  */
     51 public class NfcProvisioningFragment extends Fragment implements
     52         NfcAdapter.CreateNdefMessageCallback,
     53         TextWatcherWrapper.OnTextChangedListener,
     54         LoaderManager.LoaderCallbacks<Map<String, String>> {
     55 
     56     private static final int LOADER_PROVISIONING_VALUES = 1;
     57 
     58     // View references
     59     private EditText mEditPackageName;
     60     private EditText mEditClassName;
     61     private EditText mEditLocale;
     62     private EditText mEditTimezone;
     63     private EditText mEditWifiSsid;
     64     private EditText mEditWifiSecurityType;
     65     private EditText mEditWifiPassword;
     66 
     67     // Values to be set via NFC bump
     68     private Map<String, String> mProvisioningValues;
     69 
     70     @Override
     71     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
     72                              @Nullable Bundle savedInstanceState) {
     73         return inflater.inflate(R.layout.fragment_nfc_provisioning, container, false);
     74     }
     75 
     76     @Override
     77     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
     78         // Retrieve view references
     79         mEditPackageName = (EditText) view.findViewById(R.id.package_name);
     80         mEditClassName = (EditText) view.findViewById(R.id.class_name);
     81         mEditLocale = (EditText) view.findViewById(R.id.locale);
     82         mEditTimezone = (EditText) view.findViewById(R.id.timezone);
     83         mEditWifiSsid = (EditText) view.findViewById(R.id.wifi_ssid);
     84         mEditWifiSecurityType = (EditText) view.findViewById(R.id.wifi_security_type);
     85         mEditWifiPassword = (EditText) view.findViewById(R.id.wifi_password);
     86         // Bind event handlers
     87         mEditPackageName.addTextChangedListener(new TextWatcherWrapper(R.id.package_name, this));
     88         mEditClassName.addTextChangedListener(new TextWatcherWrapper(R.id.class_name, this));
     89         mEditLocale.addTextChangedListener(new TextWatcherWrapper(R.id.locale, this));
     90         mEditTimezone.addTextChangedListener(new TextWatcherWrapper(R.id.timezone, this));
     91         mEditWifiSsid.addTextChangedListener(new TextWatcherWrapper(R.id.wifi_ssid, this));
     92         mEditWifiSecurityType.addTextChangedListener(
     93                 new TextWatcherWrapper(R.id.wifi_security_type, this));
     94         mEditWifiPassword.addTextChangedListener(new TextWatcherWrapper(R.id.wifi_password, this));
     95         // Prior to API 23, the class name is not needed
     96         mEditClassName.setVisibility(Build.VERSION.SDK_INT >= 23 ? View.VISIBLE : View.GONE);
     97     }
     98 
     99     @Override
    100     public void onStart() {
    101         super.onStart();
    102         Activity activity = getActivity();
    103         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
    104         if (adapter != null) {
    105             adapter.setNdefPushMessageCallback(this, activity);
    106         }
    107         getLoaderManager().initLoader(LOADER_PROVISIONING_VALUES, null, this);
    108     }
    109 
    110     @Override
    111     public NdefMessage createNdefMessage(NfcEvent event) {
    112         if (mProvisioningValues == null) {
    113             return null;
    114         }
    115         ByteArrayOutputStream stream = new ByteArrayOutputStream();
    116         Properties properties = new Properties();
    117         // Store all the values into the Properties object
    118         for (Map.Entry<String, String> e : mProvisioningValues.entrySet()) {
    119             if (!TextUtils.isEmpty(e.getValue())) {
    120                 String value;
    121                 if (e.getKey().equals(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID)) {
    122                     // Make sure to surround SSID with double quotes
    123                     value = e.getValue();
    124                     if (!value.startsWith("\"") || !value.endsWith("\"")) {
    125                         value = "\"" + value + "\"";
    126                     }
    127                 } else //noinspection deprecation
    128                     if (e.getKey().equals(
    129                             DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME)
    130                             && Build.VERSION.SDK_INT >= 23) {
    131                         continue;
    132                     } else {
    133                         value = e.getValue();
    134                     }
    135                 properties.put(e.getKey(), value);
    136             }
    137         }
    138         // Make sure to put local time in the properties. This is necessary on some devices to
    139         // reliably download the device owner APK from an HTTPS connection.
    140         if (!properties.contains(DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME)) {
    141             properties.put(DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME,
    142                     String.valueOf(System.currentTimeMillis()));
    143         }
    144         try {
    145             properties.store(stream, getString(R.string.nfc_comment));
    146             NdefRecord record = NdefRecord.createMime(
    147                     DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC, stream.toByteArray());
    148             return new NdefMessage(new NdefRecord[]{record});
    149         } catch (IOException e) {
    150             e.printStackTrace();
    151         }
    152         return null;
    153     }
    154 
    155     @Override
    156     public void onTextChanged(int id, String s) {
    157         if (mProvisioningValues == null) {
    158             return;
    159         }
    160         switch (id) {
    161             case R.id.package_name:
    162                 //noinspection deprecation
    163                 mProvisioningValues.put(
    164                         DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, s);
    165                 break;
    166             case R.id.class_name:
    167                 if (Build.VERSION.SDK_INT >= 23) {
    168                     if (TextUtils.isEmpty(s)) {
    169                         mProvisioningValues.remove(
    170                                 DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME);
    171                     } else {
    172                         // On API 23 and above, we can use
    173                         // EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME to specify the receiver
    174                         // in the device owner app. If the provisioning values contain this key,
    175                         // EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME is not read.
    176                         String packageName = mEditPackageName.getText().toString();
    177                         ComponentName name = new ComponentName(packageName, s);
    178                         mProvisioningValues.put(
    179                                 DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
    180                                 name.flattenToShortString());
    181                     }
    182                 }
    183                 break;
    184             case R.id.locale:
    185                 mProvisioningValues.put(DevicePolicyManager.EXTRA_PROVISIONING_LOCALE, s);
    186                 break;
    187             case R.id.timezone:
    188                 mProvisioningValues.put(DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE, s);
    189                 break;
    190             case R.id.wifi_ssid:
    191                 mProvisioningValues.put(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID, s);
    192                 break;
    193             case R.id.wifi_security_type:
    194                 mProvisioningValues.put(
    195                         DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, s);
    196                 break;
    197             case R.id.wifi_password:
    198                 mProvisioningValues.put(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD, s);
    199                 break;
    200         }
    201     }
    202 
    203     @Override
    204     public Loader<Map<String, String>> onCreateLoader(int id, Bundle args) {
    205         if (id == LOADER_PROVISIONING_VALUES) {
    206             return new ProvisioningValuesLoader(getActivity());
    207         }
    208         return null;
    209     }
    210 
    211     @Override
    212     public void onLoadFinished(Loader<Map<String, String>> loader, Map<String, String> values) {
    213         if (loader.getId() == LOADER_PROVISIONING_VALUES) {
    214             mProvisioningValues = values;
    215             //noinspection deprecation
    216             mEditPackageName.setText(values.get(
    217                     DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME));
    218             if (Build.VERSION.SDK_INT >= 23) {
    219                 ComponentName name = ComponentName.unflattenFromString(values.get(
    220                         DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME));
    221                 mEditClassName.setText(name.getClassName());
    222             }
    223             mEditLocale.setText(values.get(DevicePolicyManager.EXTRA_PROVISIONING_LOCALE));
    224             mEditTimezone.setText(values.get(DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE));
    225             mEditWifiSsid.setText(values.get(DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID));
    226             mEditWifiSecurityType.setText(values.get(
    227                     DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE));
    228             mEditWifiPassword.setText(values.get(
    229                     DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD));
    230         }
    231     }
    232 
    233     @Override
    234     public void onLoaderReset(Loader<Map<String, String>> loader) {
    235         // Do nothing
    236     }
    237 
    238 }
    239