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