• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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