Suica Reader で試した結果なので、launchMode の設定によって変わってくるかもしれません。
Suica Reader では launchMode に singleTask を設定しています。
■ 2.3.6 (Nexus S)
NfcAdapter.ACTION_TECH_DISCOVERED による起動
flag = 268435456 = 0x10000000
FLAG_ACTIVITY_NEW_TASK
上記のあと、Recent Apps から起動
flag = 269484032 = 0x10100000
FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
■ 4.3 (初代 Nexus 7)
NfcAdapter.ACTION_TECH_DISCOVERED による起動
flag = 0
上記のあと、Recent Apps から起動
flag = 269500416 = 0x10104000
FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY | FLAG_ACTIVITY_TASK_ON_HOME
2.3.6 との違いは FLAG_ACTIVITY_TASK_ON_HOME です。この Flag は API Level 11 からです。
2.3.6 と 4.3 では NfcAdapter.ACTION_TECH_DISCOVERED による起動時の Flag も違っていました。
Recent から起動したときの Intent は前回起動したときの Intent になります。
つまり、カードをかざして起動したあと、Recent から起動すると、カードをかざしていないのに Intent の Action は NfcAdapter.ACTION_NDEF_DISCOVERED や NfcAdapter.ACTION_TECH_DISCOVERED などになります。
NDEF データは Intent に保持されるので、それを利用する場合は問題ないのですが、カード検出後にカードと通信して直接データを読み取る場合は困ります。
カードがかざされたことによる起動なのか、Recent からの起動なのか調べるために Flag が 0 より大きいかどうかでチェックしていたのですが、2.3.6 では NfcAdapter.ACTION_TECH_DISCOVERED による起動時の Flag が 0 ではないことがわかってしまい、ちゃんと FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY をチェックしないとダメでしたね。。。
2013年10月24日木曜日
2013年10月23日水曜日
AppCompat は Dialog をサポートしていない
AppCompat には Dialog 系のテーマがありません。
AppCompat で用意されているテーマ
次のように自分でダイアログのテーマを作ることはできます。
android:windowBackground に指定する画像(下記だと @drawable/dialog_full_holo_light)を platform から取ってこないといけないですが。
なので、View で実装した DialogFragment を用意することになるのかな。
# ちなみに ActionBar Sherlock は Dialog サポートしてます
AppCompat で用意されているテーマ
- Theme.AppCompat
- Theme.AppCompat.Light
- Theme.AppCompat.Light.DarkActionBar
次のように自分でダイアログのテーマを作ることはできます。
android:windowBackground に指定する画像(下記だと @drawable/dialog_full_holo_light)を platform から取ってこないといけないですが。
- <resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="AppBaseTheme" parent="Theme.AppCompat.Light"></style>
- <style name="AppTheme" parent="AppBaseTheme">
- <item name="android:windowBackground">@drawable/bg</item>
- </style>
- <style name="AppTheme.Dialog">
- <item name="android:windowFrame">@null</item>
- <item name="android:windowBackground">@drawable/dialog_full_holo_light</item>
- <item name="android:windowIsFloating">true</item>
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
- <item name="android:windowActionBar">false</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowActionModeOverlay">true</item>
- <item name="android:windowCloseOnTouchOutside">false</item>
- </style>
- </resources>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="AppBaseTheme" parent="Theme.AppCompat.Light"></style>
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:windowBackground">@drawable/bg</item>
</style>
<style name="AppTheme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowBackground">@drawable/dialog_full_holo_light</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowCloseOnTouchOutside">false</item>
</style>
</resources>
ただし、
- <item name="android:windowActionBar">true</item>
- <item name="android:windowNoTitle">false</item>
<item name="android:windowActionBar">true</item>
<item name="android:windowNoTitle">false</item>
とすると、IllegalStateException が発生します。
- java.lang.IllegalStateException: ActionBarImpl can only be used with a compatible window decor layout
java.lang.IllegalStateException: ActionBarImpl can only be used with a compatible window decor layout
つまり、Dialog で AppCompat の ActionBar は利用できないようになっている、ということです。
なので、View で実装した DialogFragment を用意することになるのかな。
- public class SimpleDialogFragment extends DialogFragment {
- public static SimpleDialogFragment getInstance(String title, String message) {
- Bundle args = new Bundle();
- args.putString("title", title);
- args.putString("message", message);
- SimpleDialogFragment f = new SimpleDialogFragment();
- f.setArguments(args);
- return f;
- }
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Bundle args = getArguments();
- String title = args.getString("title");
- String message = args.getString("message");
- View view = LayoutInflater.from(getActivity())
- .inflate(R.layout.fragment_dialog, null);
- TextView tv;
- // title
- tv = (TextView) view.findViewById(R.id.title);
- tv.setText(title);
- // message
- tv = (TextView) view.findViewById(R.id.message);
- tv.setText(message);
- Dialog dialog = new Dialog(getActivity(), R.style.AppTheme_Dialog);
- dialog.setContentView(view);
- return dialog;
- }
- }
public class SimpleDialogFragment extends DialogFragment {
public static SimpleDialogFragment getInstance(String title, String message) {
Bundle args = new Bundle();
args.putString("title", title);
args.putString("message", message);
SimpleDialogFragment f = new SimpleDialogFragment();
f.setArguments(args);
return f;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
String title = args.getString("title");
String message = args.getString("message");
View view = LayoutInflater.from(getActivity())
.inflate(R.layout.fragment_dialog, null);
TextView tv;
// title
tv = (TextView) view.findViewById(R.id.title);
tv.setText(title);
// message
tv = (TextView) view.findViewById(R.id.message);
tv.setText(message);
Dialog dialog = new Dialog(getActivity(), R.style.AppTheme_Dialog);
dialog.setContentView(view);
return dialog;
}
}
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" >
- <TextView
- android:id="@+id/title"
- style="?attr/actionBarStyle"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:gravity="center_vertical"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:textAppearance="?android:attr/textAppearanceMedium" />
- <ScrollView
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <TextView
- android:id="@+id/message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="16dip"
- android:autoLink="web|email"
- android:linksClickable="true" />
- </ScrollView>
- </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
style="?attr/actionBarStyle"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dip"
android:autoLink="web|email"
android:linksClickable="true" />
</ScrollView>
</LinearLayout>
# ちなみに ActionBar Sherlock は Dialog サポートしてます
Android Beam を Foreground Dispatch で受けとるときの注意点
Chrome から送られてくる Android Beam を Foreground Dispatch で受けとるにはちょっと注意が必要です。
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 の enableForegroundDispatch() では、IntentFilter[] はそのまま NfcService の setForegroundDispatch() に渡されています。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/nfc/NfcAdapter.java#1011
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
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
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
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
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
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
addDataScheme() で http が追加されていても、addDataType() で */* が指定されていると、Chrome からの Android Beam は type が null なので #950 に入ってしまいます。
よって、type が */* の IntentFilter と scheme が http の IntentFilter の2つを指定する必要があるのです。
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);
- }
- }
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);
- }
- }
@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);
- }
- }
@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
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 }
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 }
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 }
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 }
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>
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<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>
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つを指定する必要があるのです。
2013年10月22日火曜日
マルチユーザーのデータベースパスは /data/user/xx/[PACKAGE_NAME]/databases/[DATABASE_NAME]
昔々は
/data/data/[PACKAGE_NAME]/databases/[DATABASE_NAME]
だったのですが、マルチユーザーだとタイトルみたいなパスになります。
ContextWrapper の getDatabasePath() を使っていればちゃんと適切なパスを返してくれます。
/data/data/[PACKAGE_NAME]/databases/[DATABASE_NAME] はオーナーユーザーだと普通に動くっぽいのですが、サブユーザーはこのディレクトリに書込み権限がないので、書込もうとするとエラーになります。
/data/data/[PACKAGE_NAME]/databases/[DATABASE_NAME]
だったのですが、マルチユーザーだとタイトルみたいなパスになります。
ContextWrapper の getDatabasePath() を使っていればちゃんと適切なパスを返してくれます。
/data/data/[PACKAGE_NAME]/databases/[DATABASE_NAME] はオーナーユーザーだと普通に動くっぽいのですが、サブユーザーはこのディレクトリに書込み権限がないので、書込もうとするとエラーになります。
2013年10月21日月曜日
AAR(Android Application Record)メモ
Android以外から Android Beam 送るときに必要なのでメモ。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/nfc/NdefRecord.java#309
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/nfc/NdefRecord.java#309
- 139 public static final short TNF_EXTERNAL_TYPE = 0x04;
- 223 public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
- 309 public static NdefRecord createApplicationRecord(String packageName) {
- 310 if (packageName == null) throw new NullPointerException("packageName is null");
- 311 if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
- 312
- 313 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
- 314 packageName.getBytes(Charsets.UTF_8));
- 315 }
139 public static final short TNF_EXTERNAL_TYPE = 0x04;
223 public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
309 public static NdefRecord createApplicationRecord(String packageName) {
310 if (packageName == null) throw new NullPointerException("packageName is null");
311 if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
312
313 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
314 packageName.getBytes(Charsets.UTF_8));
315 }
2013年10月19日土曜日
ActionBar のタブの高さは 48dp 以上にできない
ActionBar のサイズは 48dp 以上にできるのになー。。。
ActionBar のタブは ScrollingTabContainerView です。
このコンストラクタで setContentHeight() を呼んで高さを設定しています。
高さには ActionBarPolicy の getTabContainerHeight() の値を指定しています。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/com/android/internal/widget/ScrollingTabContainerView.java#71
つまり、タブの高さは R.dimen.action_bar_stacked_max_height より大きくならないということです。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/com/android/internal/view/ActionBarPolicy.java#65
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/res/res/values/dimens.xml#229
ActionBar のタブは ScrollingTabContainerView です。
このコンストラクタで setContentHeight() を呼んで高さを設定しています。
高さには ActionBarPolicy の getTabContainerHeight() の値を指定しています。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/com/android/internal/widget/ScrollingTabContainerView.java#71
- 71 public ScrollingTabContainerView(Context context) {
- 72 super(context);
- 73 setHorizontalScrollBarEnabled(false);
- 74
- 75 ActionBarPolicy abp = ActionBarPolicy.get(context);
- 76 setContentHeight(abp.getTabContainerHeight());
- 77 mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
- 78
- 79 mTabLayout = createTabLayout();
- 80 addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- 81 ViewGroup.LayoutParams.MATCH_PARENT));
- 82 }
- 83
71 public ScrollingTabContainerView(Context context) {
72 super(context);
73 setHorizontalScrollBarEnabled(false);
74
75 ActionBarPolicy abp = ActionBarPolicy.get(context);
76 setContentHeight(abp.getTabContainerHeight());
77 mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
78
79 mTabLayout = createTabLayout();
80 addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
81 ViewGroup.LayoutParams.MATCH_PARENT));
82 }
83
ActionBarPolicy の getTabContainerHeight() では、android:actionBarStyle の android:height と R.dimen.action_bar_stacked_max_height の最小値を返しています。つまり、タブの高さは R.dimen.action_bar_stacked_max_height より大きくならないということです。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/com/android/internal/view/ActionBarPolicy.java#65
- 65 public int getTabContainerHeight() {
- 66 TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar,
- 67 com.android.internal.R.attr.actionBarStyle, 0);
- 68 int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
- 69 Resources r = mContext.getResources();
- 70 if (!hasEmbeddedTabs()) {
- 71 // Stacked tabs; limit the height
- 72 height = Math.min(height,
- 73 r.getDimensionPixelSize(R.dimen.action_bar_stacked_max_height));
- 74 }
- 75 a.recycle();
- 76 return height;
- 77 }
65 public int getTabContainerHeight() {
66 TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar,
67 com.android.internal.R.attr.actionBarStyle, 0);
68 int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
69 Resources r = mContext.getResources();
70 if (!hasEmbeddedTabs()) {
71 // Stacked tabs; limit the height
72 height = Math.min(height,
73 r.getDimensionPixelSize(R.dimen.action_bar_stacked_max_height));
74 }
75 a.recycle();
76 return height;
77 }
R.dimen.action_bar_stacked_max_height は 48dp なので、これより大きくできません。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/res/res/values/dimens.xml#229
- 229 <!-- Maximum height for a stacked tab bar as part of an action bar -->
- 230 <dimen name="action_bar_stacked_max_height">48dp</dimen>
229 <!-- Maximum height for a stacked tab bar as part of an action bar -->
230 <dimen name="action_bar_stacked_max_height">48dp</dimen>
2013年10月3日木曜日
Android NdefRecord.createMime() をバックポートするときの注意点
API Level 16 から NdefRecord に createMime() というメソッドが追加されています。
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/nfc/NdefRecord.java#409
4.3 の HTC One では起こりませんでしたが、4.0.4 の ARROWS V では起こりました。
(Jelly Bean なら起こらない?)
http://tools.oesf.biz/android-4.2.0_r1.0/xref/frameworks/base/core/java/android/nfc/NdefRecord.java#409
- public static NdefRecord createMime(String mimeType, byte[] mimeData) {
- if (mimeType == null)
- throw new NullPointerException("mimeType is null");
- // We only do basic MIME type validation: trying to follow the
- // RFCs strictly only ends in tears, since there are lots of MIME
- // types in common use that are not strictly valid as per RFC rules
- mimeType = normalizeMimeType(mimeType);
- if (mimeType.length() == 0)
- throw new IllegalArgumentException("mimeType is empty");
- int slashIndex = mimeType.indexOf('/');
- if (slashIndex == 0)
- throw new IllegalArgumentException("mimeType must have major type");
- if (slashIndex == mimeType.length() - 1) {
- throw new IllegalArgumentException("mimeType must have minor type");
- }
- // missing '/' is allowed
- // MIME RFCs suggest ASCII encoding for content-type
- byte[] typeBytes = mimeType.getBytes(Charsets.US_ASCII);
- return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typeBytes, null, mimeData);
- }
public static NdefRecord createMime(String mimeType, byte[] mimeData) {
if (mimeType == null)
throw new NullPointerException("mimeType is null");
// We only do basic MIME type validation: trying to follow the
// RFCs strictly only ends in tears, since there are lots of MIME
// types in common use that are not strictly valid as per RFC rules
mimeType = normalizeMimeType(mimeType);
if (mimeType.length() == 0)
throw new IllegalArgumentException("mimeType is empty");
int slashIndex = mimeType.indexOf('/');
if (slashIndex == 0)
throw new IllegalArgumentException("mimeType must have major type");
if (slashIndex == mimeType.length() - 1) {
throw new IllegalArgumentException("mimeType must have minor type");
}
// missing '/' is allowed
// MIME RFCs suggest ASCII encoding for content-type
byte[] typeBytes = mimeType.getBytes(Charsets.US_ASCII);
return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typeBytes, null, mimeData);
}
このメソッドをバックポートするときに、そのままコードを移植すると実行時に IllegalArgumentException が起こる場合があります。
4.3 の HTC One では起こりませんでしたが、4.0.4 の ARROWS V では起こりました。
(Jelly Bean なら起こらない?)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): java.lang.IllegalArgumentException: Illegal null argument
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Parcel.readException(Parcel.java:1351)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Parcel.readException(Parcel.java:1301)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at android.nfc.INdefPushCallback$Stub$Proxy.createMessage(INdefPushCallback.java:95)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.P2pLinkManager.prepareMessageToSend(P2pLinkManager.java:241)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.P2pLinkManager.onLlcpActivated(P2pLinkManager.java:206)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.NfcService$NfcServiceHandler.llcpActivated(NfcService.java:1845)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.NfcService$NfcServiceHandler.handleMessage(NfcService.java:1718)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Handler.dispatchMessage(Handler.java:99)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Looper.loop(Looper.java:137)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at android.app.ActivityThread.main(ActivityThread.java:4479)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at java.lang.reflect.Method.invokeNative(Native Method)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at java.lang.reflect.Method.invoke(Method.java:511)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
- 10-03 19:08:40.622: E/AndroidRuntime(2218): at dalvik.system.NativeStart.main(Native Method)
10-03 19:08:40.622: E/AndroidRuntime(2218): java.lang.IllegalArgumentException: Illegal null argument
10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Parcel.readException(Parcel.java:1351)
10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Parcel.readException(Parcel.java:1301)
10-03 19:08:40.622: E/AndroidRuntime(2218): at android.nfc.INdefPushCallback$Stub$Proxy.createMessage(INdefPushCallback.java:95)
10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.P2pLinkManager.prepareMessageToSend(P2pLinkManager.java:241)
10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.P2pLinkManager.onLlcpActivated(P2pLinkManager.java:206)
10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.NfcService$NfcServiceHandler.llcpActivated(NfcService.java:1845)
10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.nfc.NfcService$NfcServiceHandler.handleMessage(NfcService.java:1718)
10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Handler.dispatchMessage(Handler.java:99)
10-03 19:08:40.622: E/AndroidRuntime(2218): at android.os.Looper.loop(Looper.java:137)
10-03 19:08:40.622: E/AndroidRuntime(2218): at android.app.ActivityThread.main(ActivityThread.java:4479)
10-03 19:08:40.622: E/AndroidRuntime(2218): at java.lang.reflect.Method.invokeNative(Native Method)
10-03 19:08:40.622: E/AndroidRuntime(2218): at java.lang.reflect.Method.invoke(Method.java:511)
10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
10-03 19:08:40.622: E/AndroidRuntime(2218): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
10-03 19:08:40.622: E/AndroidRuntime(2218): at dalvik.system.NativeStart.main(Native Method)
この現象を避けるには、NdefRecord のコンストラクタの第3引数を null から new byte[0] に変えます。
- public static NdefRecord createMime(String mimeType, byte[] mimeData) {
- ...
- return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typeBytes, new byte[0], mimeData);
- }
public static NdefRecord createMime(String mimeType, byte[] mimeData) {
...
return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typeBytes, new byte[0], mimeData);
}
2013年10月1日火曜日
Android GoogleCloudMessaging.register() はUIスレッドで呼んではいけない
- public static String getRegistrationId(Context context) {
- GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
- String registrationId = null;
- try {
- registrationId = gcm.register(Consts.PROJECT_NUMBER);
- } catch (IOException e) {
- e.printStackTrace();
- }
- return registrationId;
- }
public static String getRegistrationId(Context context) {
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
String registrationId = null;
try {
registrationId = gcm.register(Consts.PROJECT_NUMBER);
} catch (IOException e) {
e.printStackTrace();
}
return registrationId;
}
を呼んでるんだけど null が返ってくる、なぜだ。。。と思っていたら、IOException が発生していた。。。
- 10-01 18:17:18.489: W/System.err(6397): java.io.IOException: MAIN_THREAD
- 10-01 18:17:18.489: W/System.err(6397): at com.google.android.gms.gcm.GoogleCloudMessaging.register(Unknown Source)
10-01 18:17:18.489: W/System.err(6397): java.io.IOException: MAIN_THREAD
10-01 18:17:18.489: W/System.err(6397): at com.google.android.gms.gcm.GoogleCloudMessaging.register(Unknown Source)
UIスレッドで呼んじゃいけないのか。リファレンスに書いておいてほしかったなー。。。と思ったら register() のリファレンスには書いてないのに、なぜかエラーコードのリファレンスにだけ書いてあるとか。ううう。
登録:
投稿 (Atom)