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