1/*
2 * Copyright (C) 2013 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 android.nfc.cardemulation;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.app.ActivityThread;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.pm.IPackageManager;
25import android.content.pm.PackageManager;
26import android.nfc.INfcCardEmulation;
27import android.nfc.NfcAdapter;
28import android.os.RemoteException;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.util.Log;
32
33import java.util.HashMap;
34import java.util.List;
35
36/**
37 * This class can be used to query the state of
38 * NFC card emulation services.
39 *
40 * For a general introduction into NFC card emulation,
41 * please read the <a href="{@docRoot}guide/topics/nfc/ce.html">
42 * NFC card emulation developer guide</a>.</p>
43 *
44 * <p class="note">Use of this class requires the
45 * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
46 * on the device.
47 */
48public final class CardEmulation {
49    static final String TAG = "CardEmulation";
50
51    /**
52     * Activity action: ask the user to change the default
53     * card emulation service for a certain category. This will
54     * show a dialog that asks the user whether he wants to
55     * replace the current default service with the service
56     * identified with the ComponentName specified in
57     * {@link #EXTRA_SERVICE_COMPONENT}, for the category
58     * specified in {@link #EXTRA_CATEGORY}
59     */
60    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
61    public static final String ACTION_CHANGE_DEFAULT =
62            "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
63
64    /**
65     * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
66     *
67     * @see #ACTION_CHANGE_DEFAULT
68     */
69    public static final String EXTRA_CATEGORY = "category";
70
71    /**
72     * The service {@link ComponentName} object passed in as an
73     * extra for {@link #ACTION_CHANGE_DEFAULT}.
74     *
75     * @see #ACTION_CHANGE_DEFAULT
76     */
77    public static final String EXTRA_SERVICE_COMPONENT = "component";
78
79    /**
80     * Category used for NFC payment services.
81     */
82    public static final String CATEGORY_PAYMENT = "payment";
83
84    /**
85     * Category that can be used for all other card emulation
86     * services.
87     */
88    public static final String CATEGORY_OTHER = "other";
89
90    /**
91     * Return value for {@link #getSelectionModeForCategory(String)}.
92     *
93     * <p>In this mode, the user has set a default service for this
94     *    category.
95     *
96     * <p>When using ISO-DEP card emulation with {@link HostApduService}
97     *    or {@link OffHostApduService}, if a remote NFC device selects
98     *    any of the Application IDs (AIDs)
99     *    that the default service has registered in this category,
100     *    that service will automatically be bound to to handle
101     *    the transaction.
102     */
103    public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
104
105    /**
106     * Return value for {@link #getSelectionModeForCategory(String)}.
107     *
108     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
109     *    or {@link OffHostApduService}, whenever an Application ID (AID) of this category
110     *    is selected, the user is asked which service he wants to use to handle
111     *    the transaction, even if there is only one matching service.
112     */
113    public static final int SELECTION_MODE_ALWAYS_ASK = 1;
114
115    /**
116     * Return value for {@link #getSelectionModeForCategory(String)}.
117     *
118     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
119     *    or {@link OffHostApduService}, the user will only be asked to select a service
120     *    if the Application ID (AID) selected by the reader has been registered by multiple
121     *    services. If there is only one service that has registered for the AID,
122     *    that service will be invoked directly.
123     */
124    public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
125
126    static boolean sIsInitialized = false;
127    static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
128    static INfcCardEmulation sService;
129
130    final Context mContext;
131
132    private CardEmulation(Context context, INfcCardEmulation service) {
133        mContext = context.getApplicationContext();
134        sService = service;
135    }
136
137    /**
138     * Helper to get an instance of this class.
139     *
140     * @param adapter A reference to an NfcAdapter object.
141     * @return
142     */
143    public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
144        if (adapter == null) throw new NullPointerException("NfcAdapter is null");
145        Context context = adapter.getContext();
146        if (context == null) {
147            Log.e(TAG, "NfcAdapter context is null.");
148            throw new UnsupportedOperationException();
149        }
150        if (!sIsInitialized) {
151            IPackageManager pm = ActivityThread.getPackageManager();
152            if (pm == null) {
153                Log.e(TAG, "Cannot get PackageManager");
154                throw new UnsupportedOperationException();
155            }
156            try {
157                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
158                    Log.e(TAG, "This device does not support card emulation");
159                    throw new UnsupportedOperationException();
160                }
161            } catch (RemoteException e) {
162                Log.e(TAG, "PackageManager query failed.");
163                throw new UnsupportedOperationException();
164            }
165            sIsInitialized = true;
166        }
167        CardEmulation manager = sCardEmus.get(context);
168        if (manager == null) {
169            // Get card emu service
170            INfcCardEmulation service = adapter.getCardEmulationService();
171            manager = new CardEmulation(context, service);
172            sCardEmus.put(context, manager);
173        }
174        return manager;
175    }
176
177    /**
178     * Allows an application to query whether a service is currently
179     * the default service to handle a card emulation category.
180     *
181     * <p>Note that if {@link #getSelectionModeForCategory(String)}
182     * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT},
183     * this method will always return false. That is because in these
184     * selection modes a default can't be set at the category level. For categories where
185     * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or
186     * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use
187     * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service
188     * is the default for a specific AID.
189     *
190     * @param service The ComponentName of the service
191     * @param category The category
192     * @return whether service is currently the default service for the category.
193     *
194     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
195     */
196    public boolean isDefaultServiceForCategory(ComponentName service, String category) {
197        try {
198            return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category);
199        } catch (RemoteException e) {
200            // Try one more time
201            recoverService();
202            if (sService == null) {
203                Log.e(TAG, "Failed to recover CardEmulationService.");
204                return false;
205            }
206            try {
207                return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
208                        category);
209            } catch (RemoteException ee) {
210                Log.e(TAG, "Failed to recover CardEmulationService.");
211                return false;
212            }
213        }
214    }
215
216    /**
217     *
218     * Allows an application to query whether a service is currently
219     * the default handler for a specified ISO7816-4 Application ID.
220     *
221     * @param service The ComponentName of the service
222     * @param aid The ISO7816-4 Application ID
223     * @return whether the service is the default handler for the specified AID
224     *
225     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
226     */
227    public boolean isDefaultServiceForAid(ComponentName service, String aid) {
228        try {
229            return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
230        } catch (RemoteException e) {
231            // Try one more time
232            recoverService();
233            if (sService == null) {
234                Log.e(TAG, "Failed to recover CardEmulationService.");
235                return false;
236            }
237            try {
238                return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
239            } catch (RemoteException ee) {
240                Log.e(TAG, "Failed to reach CardEmulationService.");
241                return false;
242            }
243        }
244    }
245
246    /**
247     * Returns the service selection mode for the passed in category.
248     * Valid return values are:
249     * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
250     *    service for this category, which will be preferred.
251     * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
252     *    every time what service he would like to use in this category.
253     * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
254     *    to pick a service if there is a conflict.
255     * @param category The category, for example {@link #CATEGORY_PAYMENT}
256     * @return the selection mode for the passed in category
257     */
258    public int getSelectionModeForCategory(String category) {
259        if (CATEGORY_PAYMENT.equals(category)) {
260            String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
261                    Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
262            if (defaultComponent != null) {
263                return SELECTION_MODE_PREFER_DEFAULT;
264            } else {
265                return SELECTION_MODE_ALWAYS_ASK;
266            }
267        } else {
268            // All other categories are in "only ask if conflict" mode
269            return SELECTION_MODE_ASK_IF_CONFLICT;
270        }
271    }
272
273    /**
274     * @hide
275     */
276    public boolean setDefaultServiceForCategory(ComponentName service, String category) {
277        try {
278            return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category);
279        } catch (RemoteException e) {
280            // Try one more time
281            recoverService();
282            if (sService == null) {
283                Log.e(TAG, "Failed to recover CardEmulationService.");
284                return false;
285            }
286            try {
287                return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service,
288                        category);
289            } catch (RemoteException ee) {
290                Log.e(TAG, "Failed to reach CardEmulationService.");
291                return false;
292            }
293        }
294    }
295
296    /**
297     * @hide
298     */
299    public boolean setDefaultForNextTap(ComponentName service) {
300        try {
301            return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
302        } catch (RemoteException e) {
303            // Try one more time
304            recoverService();
305            if (sService == null) {
306                Log.e(TAG, "Failed to recover CardEmulationService.");
307                return false;
308            }
309            try {
310                return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
311            } catch (RemoteException ee) {
312                Log.e(TAG, "Failed to reach CardEmulationService.");
313                return false;
314            }
315        }
316    }
317
318    /**
319     * @hide
320     */
321    public List<ApduServiceInfo> getServices(String category) {
322        try {
323            return sService.getServices(UserHandle.myUserId(), category);
324        } catch (RemoteException e) {
325            // Try one more time
326            recoverService();
327            if (sService == null) {
328                Log.e(TAG, "Failed to recover CardEmulationService.");
329                return null;
330            }
331            try {
332                return sService.getServices(UserHandle.myUserId(), category);
333            } catch (RemoteException ee) {
334                Log.e(TAG, "Failed to reach CardEmulationService.");
335                return null;
336            }
337        }
338    }
339
340    void recoverService() {
341        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
342        sService = adapter.getCardEmulationService();
343    }
344}
345