CardEmulation.java revision 52246087f4e2b5ad62b9cd6ea8c2cb58f624d4e7
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 36public final class CardEmulation { 37 static final String TAG = "CardEmulation"; 38 39 /** 40 * Activity action: ask the user to change the default 41 * card emulation service for a certain category. This will 42 * show a dialog that asks the user whether he wants to 43 * replace the current default service with the service 44 * identified with the ComponentName specified in 45 * {@link #EXTRA_SERVICE_COMPONENT}, for the category 46 * specified in {@link #EXTRA_CATEGORY} 47 */ 48 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 49 public static final String ACTION_CHANGE_DEFAULT = 50 "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT"; 51 52 /** 53 * The category extra for {@link #ACTION_CHANGE_DEFAULT} 54 * 55 * @see #ACTION_CHANGE_DEFAULT 56 */ 57 public static final String EXTRA_CATEGORY = "category"; 58 59 /** 60 * The ComponentName object passed in as a parcelable 61 * extra for {@link #ACTION_CHANGE_DEFAULT} 62 * 63 * @see #ACTION_CHANGE_DEFAULT 64 */ 65 public static final String EXTRA_SERVICE_COMPONENT = "component"; 66 67 /** 68 * The payment category can be used to indicate that an AID 69 * represents a payment application. 70 */ 71 public static final String CATEGORY_PAYMENT = "payment"; 72 73 /** 74 * If an AID group does not contain a category, or the 75 * specified category is not defined by the platform version 76 * that is parsing the AID group, all AIDs in the group will 77 * automatically be categorized under the {@link #CATEGORY_OTHER} 78 * category. 79 */ 80 public static final String CATEGORY_OTHER = "other"; 81 82 /** 83 * Return value for {@link #getSelectionModeForCategory(String)}. 84 * 85 * <p>In this mode, the user has set a default service for this 86 * AID category. If a remote reader selects any of the AIDs 87 * that the default service has registered in this category, 88 * that service will automatically be bound to to handle 89 * the transaction. 90 * 91 * <p>There are still cases where a service that is 92 * not the default for a category can selected: 93 * <p> 94 * If a remote reader selects an AID in this category 95 * that is not handled by the default service, and there is a set 96 * of other services {S} that do handle this AID, the 97 * user is asked if he wants to use any of the services in 98 * {S} instead. 99 * <p> 100 * As a special case, if the size of {S} is one, containing a single service X, 101 * and all AIDs X has registered in this category are not 102 * registered by any other service, then X will be 103 * selected automatically without asking the user. 104 * <p>Example: 105 * <ul> 106 * <li>Service A registers AIDs "1", "2" and "3" in the category 107 * <li>Service B registers AIDs "3" and "4" in the category 108 * <li>Service C registers AIDs "5" and "6" in the category 109 * </ul> 110 * In this case, the following will happen when service A 111 * is the default: 112 * <ul> 113 * <li>Reader selects AID "1", "2" or "3": service A is invoked automatically 114 * <li>Reader selects AID "4": the user is asked to confirm he 115 * wants to use service B, because its AIDs overlap with service A. 116 * <li>Reader selects AID "5" or "6": service C is invoked automatically, 117 * because all AIDs it has asked for are only registered by C, 118 * and there is no overlap. 119 * </ul> 120 * 121 */ 122 public static final int SELECTION_MODE_PREFER_DEFAULT = 0; 123 124 /** 125 * Return value for {@link #getSelectionModeForCategory(String)}. 126 * 127 * <p>In this mode, whenever an AID of this category is selected, 128 * the user is asked which service he wants to use to handle 129 * the transaction, even if there is only one matching service. 130 */ 131 public static final int SELECTION_MODE_ALWAYS_ASK = 1; 132 133 /** 134 * Return value for {@link #getSelectionModeForCategory(String)}. 135 * 136 * <p>In this mode, the user will only be asked to select a service 137 * if the selected AID has been registered by multiple applications. 138 */ 139 public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; 140 141 static boolean sIsInitialized = false; 142 static HashMap<Context, CardEmulation> sCardEmus = new HashMap(); 143 static INfcCardEmulation sService; 144 145 final Context mContext; 146 147 private CardEmulation(Context context, INfcCardEmulation service) { 148 mContext = context.getApplicationContext(); 149 sService = service; 150 } 151 152 public static synchronized CardEmulation getInstance(NfcAdapter adapter) { 153 if (adapter == null) throw new NullPointerException("NfcAdapter is null"); 154 Context context = adapter.getContext(); 155 if (context == null) { 156 Log.e(TAG, "NfcAdapter context is null."); 157 throw new UnsupportedOperationException(); 158 } 159 if (!sIsInitialized) { 160 IPackageManager pm = ActivityThread.getPackageManager(); 161 if (pm == null) { 162 Log.e(TAG, "Cannot get PackageManager"); 163 throw new UnsupportedOperationException(); 164 } 165 try { 166 if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { 167 Log.e(TAG, "This device does not support card emulation"); 168 throw new UnsupportedOperationException(); 169 } 170 } catch (RemoteException e) { 171 Log.e(TAG, "PackageManager query failed."); 172 throw new UnsupportedOperationException(); 173 } 174 sIsInitialized = true; 175 } 176 CardEmulation manager = sCardEmus.get(context); 177 if (manager == null) { 178 // Get card emu service 179 INfcCardEmulation service = adapter.getCardEmulationService(); 180 manager = new CardEmulation(context, service); 181 sCardEmus.put(context, manager); 182 } 183 return manager; 184 } 185 186 /** 187 * Allows an application to query whether a service is currently 188 * the default service to handle a card emulation category. 189 * 190 * <p>Note that if {@link #getSelectionModeForCategory(String)} 191 * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always 192 * return false. 193 * 194 * @param service The ComponentName of the service 195 * @param category The category 196 * @return whether service is currently the default service for the category. 197 */ 198 public boolean isDefaultServiceForCategory(ComponentName service, String category) { 199 try { 200 return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category); 201 } catch (RemoteException e) { 202 // Try one more time 203 recoverService(); 204 if (sService == null) { 205 Log.e(TAG, "Failed to recover CardEmulationService."); 206 return false; 207 } 208 try { 209 return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, 210 category); 211 } catch (RemoteException ee) { 212 Log.e(TAG, "Failed to recover CardEmulationService."); 213 return false; 214 } 215 } 216 } 217 218 /** 219 * 220 * Allows an application to query whether a service is currently 221 * the default handler for a specified ISO7816-4 Application ID. 222 * 223 * @param service The ComponentName of the service 224 * @param aid The ISO7816-4 Application ID 225 * @return 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 application 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 * application 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 app 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 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 * @hide 319 */ 320 public List<ApduServiceInfo> getServices(String category) { 321 try { 322 return sService.getServices(UserHandle.myUserId(), category); 323 } catch (RemoteException e) { 324 // Try one more time 325 recoverService(); 326 if (sService == null) { 327 Log.e(TAG, "Failed to recover CardEmulationService."); 328 return null; 329 } 330 try { 331 return sService.getServices(UserHandle.myUserId(), category); 332 } catch (RemoteException ee) { 333 Log.e(TAG, "Failed to reach CardEmulationService."); 334 return null; 335 } 336 } 337 } 338 339 void recoverService() { 340 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); 341 sService = adapter.getCardEmulationService(); 342 } 343} 344