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