1 /* 2 * Copyright (C) 2011 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.nfc_extras; 18 19 import java.util.HashMap; 20 21 import android.content.Context; 22 import android.nfc.INfcAdapterExtras; 23 import android.nfc.NfcAdapter; 24 import android.os.RemoteException; 25 import android.util.Log; 26 27 /** 28 * Provides additional methods on an {@link NfcAdapter} for Card Emulation 29 * and management of {@link NfcExecutionEnvironment}'s. 30 * 31 * There is a 1-1 relationship between an {@link NfcAdapterExtras} object and 32 * a {@link NfcAdapter} object. 33 */ 34 public final class NfcAdapterExtras { 35 private static final String TAG = "NfcAdapterExtras"; 36 37 /** 38 * Broadcast Action: an RF field ON has been detected. 39 * 40 * <p class="note">This is an unreliable signal, and will be removed. 41 * <p class="note"> 42 * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission 43 * to receive. 44 */ 45 public static final String ACTION_RF_FIELD_ON_DETECTED = 46 "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED"; 47 48 /** 49 * Broadcast Action: an RF field OFF has been detected. 50 * 51 * <p class="note">This is an unreliable signal, and will be removed. 52 * <p class="note"> 53 * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission 54 * to receive. 55 */ 56 public static final String ACTION_RF_FIELD_OFF_DETECTED = 57 "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED"; 58 59 // protected by NfcAdapterExtras.class, and final after first construction, 60 // except for attemptDeadServiceRecovery() when NFC crashes - we accept a 61 // best effort recovery 62 private static INfcAdapterExtras sService; 63 private static final CardEmulationRoute ROUTE_OFF = 64 new CardEmulationRoute(CardEmulationRoute.ROUTE_OFF, null); 65 66 // contents protected by NfcAdapterExtras.class 67 private static final HashMap<NfcAdapter, NfcAdapterExtras> sNfcExtras = new HashMap(); 68 69 private final NfcExecutionEnvironment mEmbeddedEe; 70 private final CardEmulationRoute mRouteOnWhenScreenOn; 71 72 private final NfcAdapter mAdapter; 73 final String mPackageName; 74 75 /** get service handles */ initService(NfcAdapter adapter)76 private static void initService(NfcAdapter adapter) { 77 final INfcAdapterExtras service = adapter.getNfcAdapterExtrasInterface(); 78 if (service != null) { 79 // Leave stale rather than receive a null value. 80 sService = service; 81 } 82 } 83 84 /** 85 * Get the {@link NfcAdapterExtras} for the given {@link NfcAdapter}. 86 * 87 * <p class="note"> 88 * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission. 89 * 90 * @param adapter a {@link NfcAdapter}, must not be null 91 * @return the {@link NfcAdapterExtras} object for the given {@link NfcAdapter} 92 */ get(NfcAdapter adapter)93 public static NfcAdapterExtras get(NfcAdapter adapter) { 94 Context context = adapter.getContext(); 95 if (context == null) { 96 throw new UnsupportedOperationException( 97 "You must pass a context to your NfcAdapter to use the NFC extras APIs"); 98 } 99 100 synchronized (NfcAdapterExtras.class) { 101 if (sService == null) { 102 initService(adapter); 103 } 104 NfcAdapterExtras extras = sNfcExtras.get(adapter); 105 if (extras == null) { 106 extras = new NfcAdapterExtras(adapter); 107 sNfcExtras.put(adapter, extras); 108 } 109 return extras; 110 } 111 } 112 NfcAdapterExtras(NfcAdapter adapter)113 private NfcAdapterExtras(NfcAdapter adapter) { 114 mAdapter = adapter; 115 mPackageName = adapter.getContext().getPackageName(); 116 mEmbeddedEe = new NfcExecutionEnvironment(this); 117 mRouteOnWhenScreenOn = new CardEmulationRoute(CardEmulationRoute.ROUTE_ON_WHEN_SCREEN_ON, 118 mEmbeddedEe); 119 } 120 121 /** 122 * Immutable data class that describes a card emulation route. 123 */ 124 public final static class CardEmulationRoute { 125 /** 126 * Card Emulation is turned off on this NfcAdapter. 127 * <p>This is the default routing state after boot. 128 */ 129 public static final int ROUTE_OFF = 1; 130 131 /** 132 * Card Emulation is routed to {@link #nfcEe} only when the screen is on, 133 * otherwise it is turned off. 134 */ 135 public static final int ROUTE_ON_WHEN_SCREEN_ON = 2; 136 137 /** 138 * A route such as {@link #ROUTE_OFF} or {@link #ROUTE_ON_WHEN_SCREEN_ON}. 139 */ 140 public final int route; 141 142 /** 143 * The {@link NFcExecutionEnvironment} that is Card Emulation is routed to. 144 * <p>null if {@link #route} is {@link #ROUTE_OFF}, otherwise not null. 145 */ 146 public final NfcExecutionEnvironment nfcEe; 147 CardEmulationRoute(int route, NfcExecutionEnvironment nfcEe)148 public CardEmulationRoute(int route, NfcExecutionEnvironment nfcEe) { 149 if (route == ROUTE_OFF && nfcEe != null) { 150 throw new IllegalArgumentException("must not specifiy a NFC-EE with ROUTE_OFF"); 151 } else if (route != ROUTE_OFF && nfcEe == null) { 152 throw new IllegalArgumentException("must specifiy a NFC-EE for this route"); 153 } 154 this.route = route; 155 this.nfcEe = nfcEe; 156 } 157 } 158 159 /** 160 * NFC service dead - attempt best effort recovery 161 */ attemptDeadServiceRecovery(Exception e)162 void attemptDeadServiceRecovery(Exception e) { 163 Log.e(TAG, "NFC Adapter Extras dead - attempting to recover"); 164 mAdapter.attemptDeadServiceRecovery(e); 165 initService(mAdapter); 166 } 167 getService()168 INfcAdapterExtras getService() { 169 return sService; 170 } 171 172 /** 173 * Get the routing state of this NFC EE. 174 * 175 * <p class="note"> 176 * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission. 177 */ getCardEmulationRoute()178 public CardEmulationRoute getCardEmulationRoute() { 179 try { 180 int route = sService.getCardEmulationRoute(mPackageName); 181 return route == CardEmulationRoute.ROUTE_OFF ? 182 ROUTE_OFF : 183 mRouteOnWhenScreenOn; 184 } catch (RemoteException e) { 185 attemptDeadServiceRecovery(e); 186 return ROUTE_OFF; 187 } 188 } 189 190 /** 191 * Set the routing state of this NFC EE. 192 * 193 * <p>This routing state is not persisted across reboot. 194 * 195 * <p class="note"> 196 * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission. 197 * 198 * @param route a {@link CardEmulationRoute} 199 */ setCardEmulationRoute(CardEmulationRoute route)200 public void setCardEmulationRoute(CardEmulationRoute route) { 201 try { 202 sService.setCardEmulationRoute(mPackageName, route.route); 203 } catch (RemoteException e) { 204 attemptDeadServiceRecovery(e); 205 } 206 } 207 208 /** 209 * Get the {@link NfcExecutionEnvironment} that is embedded with the 210 * {@link NfcAdapter}. 211 * 212 * <p class="note"> 213 * Requires the {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission. 214 * 215 * @return a {@link NfcExecutionEnvironment}, or null if there is no embedded NFC-EE 216 */ getEmbeddedExecutionEnvironment()217 public NfcExecutionEnvironment getEmbeddedExecutionEnvironment() { 218 return mEmbeddedEe; 219 } 220 221 /** 222 * Authenticate the client application. 223 * 224 * Some implementations of NFC Adapter Extras may require applications 225 * to authenticate with a token, before using other methods. 226 * 227 * @param token a implementation specific token 228 * @throws java.lang.SecurityException if authentication failed 229 */ authenticate(byte[] token)230 public void authenticate(byte[] token) { 231 try { 232 sService.authenticate(mPackageName, token); 233 } catch (RemoteException e) { 234 attemptDeadServiceRecovery(e); 235 } 236 } 237 238 /** 239 * Returns the name of this adapter's driver. 240 * 241 * <p>Different NFC adapters may use different drivers. This value is 242 * informational and should not be parsed. 243 * 244 * @return the driver name, or empty string if unknown 245 */ getDriverName()246 public String getDriverName() { 247 try { 248 return sService.getDriverName(mPackageName); 249 } catch (RemoteException e) { 250 attemptDeadServiceRecovery(e); 251 return ""; 252 } 253 } 254 } 255