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
17package com.android.nfc_extras;
18
19import java.util.HashMap;
20
21import android.content.Context;
22import android.nfc.INfcAdapterExtras;
23import android.nfc.NfcAdapter;
24import android.os.RemoteException;
25import 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 */
34public 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 */
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     */
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
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
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     */
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
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     */
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     */
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     */
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     */
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     */
246    public String getDriverName() {
247        try {
248            return sService.getDriverName(mPackageName);
249        } catch (RemoteException e) {
250            attemptDeadServiceRecovery(e);
251            return "";
252        }
253    }
254}
255