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);
}
}
次のように enableForegroundDispatch() の第3引数と第4引数に null を渡すと、ともかく全部拾ってくれるようになります。
@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);
}
}
IntentFilter[] を指定して Chrome の AndroidBeam も受けとるには、次のように scheme を指定した IntentFilter も追加します。
@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 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 }
Chrome からの Android Beam が上記の IntentFilter で受けとれないのは matchData() の戻り値が 0 未満になるのが原因です。
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 types = mDataTypes;
901 final ArrayList schemes = mDataSchemes;
902 final ArrayList authorities = mDataAuthorities;
903 final ArrayList 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 }
addDataScheme() で http が追加されていても、addDataType() で */* が指定されていると、Chrome からの Android Beam は type が null なので #950 に入ってしまいます。
よって、type が */* の IntentFilter と scheme が http の IntentFilter の2つを指定する必要があるのです。
0 件のコメント:
コメントを投稿