Chrome から URL を送ったときの Android Beam の Intent は次のようになっています。
getAction() = android.nfc.action.NDEF_DISCOVERED
getType() = null
getScheme() = http
getData() = http://www.tensaikojo.com/
getCategories() = null
一方、アプリから NdefRecord.createMime() を使って、テキストデータを Android Beam で送った場合の Intent は次のようになっています。
getAction() = android.nfc.action.NDEF_DISCOVERED
getType() = application/vnd.net.yanzm.example
getScheme() = null
getData() = null
getCategories() = null
Advanced NFC | Android Developers のコードのように */* を DataType に指定した IntentFilter では、2番目の Android Beam は受けとれますが Chrome からの Android Beam は受けとれません(Android Beam を送ると、このアプリをすり抜けて Chrome アプリが起動します)。
- NfcAdapter mNfcAdapter;
- @Override
- protected void onResume() {
- super.onResume();
- if (mNfcAdapter != null) {
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
- getPendingIntent().addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
- IntentFilter ndef = IntentFilter.create(NfcAdapter.ACTION_NDEF_DISCOVERED, "*/*");
- IntentFilter[] intentFiltersArray = new IntentFilter[] { ndef };
- mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, null);
- }
- }
- @Override
- protected void onPause() {
- super.onPause();
- if (mNfcAdapter != null) {
- mNfcAdapter.disableForegroundDispatch(this);
- }
- }
- @Override
- protected void onResume() {
- super.onResume();
- if (mNfcAdapter != null) {
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
- getPendingIntent().addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
- // catch all ndef
- mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
- }
- }
- @Override
- protected void onResume() {
- super.onResume();
- if (mNfcAdapter != null) {
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
- getPendingIntent().addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
- IntentFilter ndef = IntentFilter.create(NfcAdapter.ACTION_NDEF_DISCOVERED, "*/*");
- IntentFilter ndef2 = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
- ndef2.addDataScheme("");
- ndef2.addDataScheme("http");
- ndef2.addDataScheme("https");
- IntentFilter[] intentFiltersArray = new IntentFilter[] { ndef, ndef2 };
- mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, null);
- }
- }
---------------------------------------------
ここからは解説というか内部コードのメモです。
NfcAdapter の enableForegroundDispatch() では、IntentFilter[] はそのまま NfcService の setForegroundDispatch() に渡されています。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/nfc/NfcAdapter.java#1011
- 1011 public void enableForegroundDispatch(Activity activity, PendingIntent intent,
- 1012 IntentFilter[] filters, String[][] techLists) {
- 1013 if (activity == null || intent == null) {
- 1014 throw new NullPointerException();
- 1015 }
- 1016 if (!activity.isResumed()) {
- 1017 throw new IllegalStateException("Foreground dispatch can only be enabled " +
- 1018 "when your activity is resumed");
- 1019 }
- 1020 try {
- 1021 TechListParcel parcel = null;
- 1022 if (techLists != null && techLists.length > 0) {
- 1023 parcel = new TechListParcel(techLists);
- 1024 }
- 1025 ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
- 1026 mForegroundDispatchListener);
- 1027 sService.setForegroundDispatch(intent, filters, parcel);
- 1028 } catch (RemoteException e) {
- 1029 attemptDeadServiceRecovery(e);
- 1030 }
- 1031 }
- 1032
NfcService でも IntentFilter[] は内部の null チェックがされるだけで、そのまま NfcDispatcher の setForegroundDispatch() に渡されます。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/packages/apps/Nfc/src/com/android/nfc/NfcService.java#837
- 837 public void setForegroundDispatch(PendingIntent intent,
- 838 IntentFilter[] filters, TechListParcel techListsParcel) {
- 839 mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
- 840
- 841 // Short-cut the disable path
- 842 if (intent == null && filters == null && techListsParcel == null) {
- 843 mNfcDispatcher.setForegroundDispatch(null, null, null);
- 844 return;
- 845 }
- 846
- 847 // Validate the IntentFilters
- 848 if (filters != null) {
- 849 if (filters.length == 0) {
- 850 filters = null;
- 851 } else {
- 852 for (IntentFilter filter : filters) {
- 853 if (filter == null) {
- 854 throw new IllegalArgumentException("null IntentFilter");
- 855 }
- 856 }
- 857 }
- 858 }
- 859
- 860 // Validate the tech lists
- 861 String[][] techLists = null;
- 862 if (techListsParcel != null) {
- 863 techLists = techListsParcel.getTechLists();
- 864 }
- 865
- 866 mNfcDispatcher.setForegroundDispatch(intent, filters, techLists);
- 867 }
setForegroundDispatch() に渡された IntentFilter[] は mOverrideFilters に保持され、dispatchTag() メソッドで利用されます。dispatchTag() は、タグをどのIntentに割り当てるか決めるメソッドです。この中で、Foreground Dispatch に対応するかどうかを tryOverrides() を呼び出して判定しています。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java#81
- 81 public synchronized void setForegroundDispatch(PendingIntent intent,
- 82 IntentFilter[] filters, String[][] techLists) {
- 83 if (DBG) Log.d(TAG, "Set Foreground Dispatch");
- 84 mOverrideIntent = intent;
- 85 mOverrideFilters = filters;
- 86 mOverrideTechLists = techLists;
- 87 }
- 182 /** Returns false if no activities were found to dispatch to */
- 183 public boolean dispatchTag(Tag tag) {
- 184 NdefMessage message = null;
- 185 Ndef ndef = Ndef.get(tag);
- 186 if (ndef != null) {
- 187 message = ndef.getCachedNdefMessage();
- 188 }
- 189 if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);
- 190
- 191 PendingIntent overrideIntent;
- 192 IntentFilter[] overrideFilters;
- 193 String[][] overrideTechLists;
- 194
- 195 DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
- 196 synchronized (this) {
- 197 overrideFilters = mOverrideFilters;
- 198 overrideIntent = mOverrideIntent;
- 199 overrideTechLists = mOverrideTechLists;
- 200 }
- 201
- 202 resumeAppSwitches();
- 203
- 204 if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters, overrideTechLists)) {
- 205 return true;
- 206 }
- 207
- 208 if (mHandoverManager.tryHandover(message)) {
- 209 if (DBG) Log.i(TAG, "matched BT HANDOVER");
- 210 return true;
- 211 }
- 212
- 213 if (tryNdef(dispatch, message)) {
- 214 return true;
- 215 }
- 216
- 217 if (tryTech(dispatch, tag)) {
- 218 return true;
- 219 }
- 220
- 221 dispatch.setTagIntent();
- 222 if (dispatch.tryStartActivity()) {
- 223 if (DBG) Log.i(TAG, "matched TAG");
- 224 return true;
- 225 }
- 226
- 227 if (DBG) Log.i(TAG, "no match");
- 228 return false;
- 229 }
tryOverrides() では、Android Beam は NDEF なので #239 の if に入ります。
ここでは isFilterMatch() を呼んで対応するものかどうか判定しています。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java#231
- 231 boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent,
- 232 IntentFilter[] overrideFilters, String[][] overrideTechLists) {
- 233 if (overrideIntent == null) {
- 234 return false;
- 235 }
- 236 Intent intent;
- 237
- 238 // NDEF
- 239 if (message != null) {
- 240 intent = dispatch.setNdefIntent();
- 241 if (intent != null &&
- 242 isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
- 243 try {
- 244 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
- 245 if (DBG) Log.i(TAG, "matched NDEF override");
- 246 return true;
- 247 } catch (CanceledException e) {
- 248 return false;
- 249 }
- 250 }
- 251 }
- 252
- 253 // TECH
- 254 intent = dispatch.setTechIntent();
- 255 if (isTechMatch(tag, overrideTechLists)) {
- 256 try {
- 257 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
- 258 if (DBG) Log.i(TAG, "matched TECH override");
- 259 return true;
- 260 } catch (CanceledException e) {
- 261 return false;
- 262 }
- 263 }
- 264
- 265 // TAG
- 266 intent = dispatch.setTagIntent();
- 267 if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
- 268 try {
- 269 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
- 270 if (DBG) Log.i(TAG, "matched TAG override");
- 271 return true;
- 272 } catch (CanceledException e) {
- 273 return false;
- 274 }
- 275 }
- 276 return false;
- 277 }
isFilterMatch() を見ると、IntentFilter[] が null で hasTechFilter が false のとき(つまり、String[][] overrideTechLists が null のとき)は true が返ります。
enableForegroundDispatch() の第3引数と第4引数に null を渡すと、全ての Android Beam が受けとれるようになるのは、ここに入るからです。
IntentFilter[] が null ではない場合、各 IntentFilter で match() を呼んでいます。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java#279
- 279 boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) {
- 280 if (filters != null) {
- 281 for (IntentFilter filter : filters) {
- 282 if (filter.match(mContentResolver, intent, false, TAG) >= 0) {
- 283 return true;
- 284 }
- 285 }
- 286 } else if (!hasTechFilter) {
- 287 return true; // always match if both filters and techlists are null
- 288 }
- 289 return false;
- 290 }
IntentFilter の match() の第3引数に false を指定しているので、intent.getType() が type として利用されます。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/content/IntentFilter.java#1068
- 1068 public final int match(ContentResolver resolver, Intent intent,
- 1069 boolean resolve, String logTag) {
- 1070 String type = resolve ? intent.resolveType(resolver) : intent.getType();
- 1071 return match(intent.getAction(), type, intent.getScheme(),
- 1072 intent.getData(), intent.getCategories(), logTag);
- 1073 }
- 1103 public final int match(String action, String type, String scheme,
- 1104 Uri data, Set<string> categories, String logTag) {
- 1105 if (action != null && !matchAction(action)) {
- 1106 if (false) Log.v(
- 1107 logTag, "No matching action " + action + " for " + this);
- 1108 return NO_MATCH_ACTION;
- 1109 }
- 1110
- 1111 int dataMatch = matchData(type, scheme, data);
- 1112 if (dataMatch < 0) {
- 1113 if (false) {
- 1114 if (dataMatch == NO_MATCH_TYPE) {
- 1115 Log.v(logTag, "No matching type " + type
- 1116 + " for " + this);
- 1117 }
- 1118 if (dataMatch == NO_MATCH_DATA) {
- 1119 Log.v(logTag, "No matching scheme/path " + data
- 1120 + " for " + this);
- 1121 }
- 1122 }
- 1123 return dataMatch;
- 1124 }
- 1125
- 1126 String categoryMismatch = matchCategories(categories);
- 1127 if (categoryMismatch != null) {
- 1128 if (false) {
- 1129 Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);
- 1130 }
- 1131 return NO_MATCH_CATEGORY;
- 1132 }
- 1133
- 1134 // It would be nice to treat container activities as more
- 1135 // important than ones that can be embedded, but this is not the way...
- 1136 if (false) {
- 1137 if (categories != null) {
- 1138 dataMatch -= mCategories.size() - categories.size();
- 1139 }
- 1140 }
- 1141
- 1142 return dataMatch;
- 1143 }
- tring>
Chrome からの Android Beam は scheme が http なので、addDataScheme() で http を追加していない IntentFilter では #939 の if に入ってしまいます。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/content/IntentFilter.java#matchData
- 899 public final int matchData(String type, String scheme, Uri data) {
- 900 final ArrayList<string> types = mDataTypes;
- 901 final ArrayList<string> schemes = mDataSchemes;
- 902 final ArrayList<authorityentry> authorities = mDataAuthorities;
- 903 final ArrayList<patternmatcher> paths = mDataPaths;
- 904
- 905 int match = MATCH_CATEGORY_EMPTY;
- 906
- 907 if (types == null && schemes == null) {
- 908 return ((type == null && data == null)
- 909 ? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);
- 910 }
- 911
- 912 if (schemes != null) {
- 913 if (schemes.contains(scheme != null ? scheme : "")) {
- 914 match = MATCH_CATEGORY_SCHEME;
- 915 } else {
- 916 return NO_MATCH_DATA;
- 917 }
- 918
- 919 if (authorities != null) {
- 920 int authMatch = matchDataAuthority(data);
- 921 if (authMatch >= 0) {
- 922 if (paths == null) {
- 923 match = authMatch;
- 924 } else if (hasDataPath(data.getPath())) {
- 925 match = MATCH_CATEGORY_PATH;
- 926 } else {
- 927 return NO_MATCH_DATA;
- 928 }
- 929 } else {
- 930 return NO_MATCH_DATA;
- 931 }
- 932 }
- 933 } else {
- 934 // Special case: match either an Intent with no data URI,
- 935 // or with a scheme: URI. This is to give a convenience for
- 936 // the common case where you want to deal with data in a
- 937 // content provider, which is done by type, and we don't want
- 938 // to force everyone to say they handle content: or file: URIs.
- 939 if (scheme != null && !"".equals(scheme)
- 940 && !"content".equals(scheme)
- 941 && !"file".equals(scheme)) {
- 942 return NO_MATCH_DATA;
- 943 }
- 944 }
- 945
- 946 if (types != null) {
- 947 if (findMimeType(type)) {
- 948 match = MATCH_CATEGORY_TYPE;
- 949 } else {
- 950 return NO_MATCH_TYPE;
- 951 }
- 952 } else {
- 953 // If no MIME types are specified, then we will only match against
- 954 // an Intent that does not have a MIME type.
- 955 if (type != null) {
- 956 return NO_MATCH_TYPE;
- 957 }
- 958 }
- 959
- 960 return match + MATCH_ADJUSTMENT_NORMAL;
- 961 }
- tternmatcher></authorityentry></string></string>
addDataScheme() で http が追加されていても、addDataType() で */* が指定されていると、Chrome からの Android Beam は type が null なので #950 に入ってしまいます。
よって、type が */* の IntentFilter と scheme が http の IntentFilter の2つを指定する必要があるのです。
0 件のコメント:
コメントを投稿