2013年10月24日木曜日

2.x と 3.x+ の Recent Apps からの起動は Intent に付く Flag が異なる

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_DISCOVEREDNfcAdapter.ACTION_TECH_DISCOVERED などになります。
NDEF データは Intent に保持されるので、それを利用する場合は問題ないのですが、カード検出後にカードと通信して直接データを読み取る場合は困ります。
カードがかざされたことによる起動なのか、Recent からの起動なのか調べるために Flag が 0 より大きいかどうかでチェックしていたのですが、2.3.6 では NfcAdapter.ACTION_TECH_DISCOVERED による起動時の Flag が 0 ではないことがわかってしまい、ちゃんと FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY をチェックしないとダメでしたね。。。



2013年10月23日水曜日

AppCompat は Dialog をサポートしていない

AppCompat には Dialog 系のテーマがありません。

AppCompat で用意されているテーマ
  • Theme.AppCompat
  • Theme.AppCompat.Light
  • Theme.AppCompat.Light.DarkActionBar


次のように自分でダイアログのテーマを作ることはできます。
android:windowBackground に指定する画像(下記だと @drawable/dialog_full_holo_light)を platform から取ってこないといけないですが。
  1. <resources xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   
  3.     <style name="AppBaseTheme" parent="Theme.AppCompat.Light"></style>  
  4.   
  5.     <style name="AppTheme" parent="AppBaseTheme">  
  6.         <item name="android:windowBackground">@drawable/bg</item>  
  7.     </style>  
  8.   
  9.     <style name="AppTheme.Dialog">  
  10.         <item name="android:windowFrame">@null</item>  
  11.         <item name="android:windowBackground">@drawable/dialog_full_holo_light</item>  
  12.         <item name="android:windowIsFloating">true</item>  
  13.         <item name="android:windowContentOverlay">@null</item>  
  14.         <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>  
  15.         <item name="android:windowActionBar">false</item>  
  16.         <item name="android:windowNoTitle">true</item>  
  17.         <item name="android:windowActionModeOverlay">true</item>  
  18.         <item name="android:windowCloseOnTouchOutside">false</item>  
  19.     </style>  
  20. </resources>  
ただし、
  1. <item name="android:windowActionBar">true</item>  
  2. <item name="android:windowNoTitle">false</item>  
とすると、IllegalStateException が発生します。
  1. java.lang.IllegalStateException: ActionBarImpl can only be used with a compatible window decor layout  
つまり、Dialog で AppCompat の ActionBar は利用できないようになっている、ということです。


なので、View で実装した DialogFragment を用意することになるのかな。
  1. public class SimpleDialogFragment extends DialogFragment {  
  2.   
  3.     public static SimpleDialogFragment getInstance(String title, String message) {  
  4.         Bundle args = new Bundle();  
  5.         args.putString("title", title);  
  6.         args.putString("message", message);  
  7.         SimpleDialogFragment f = new SimpleDialogFragment();  
  8.         f.setArguments(args);  
  9.         return f;  
  10.     }  
  11.   
  12.     @Override  
  13.     public Dialog onCreateDialog(Bundle savedInstanceState) {  
  14.         Bundle args = getArguments();  
  15.         String title = args.getString("title");  
  16.         String message = args.getString("message");  
  17.   
  18.         View view = LayoutInflater.from(getActivity())  
  19.                                   .inflate(R.layout.fragment_dialog, null);  
  20.   
  21.         TextView tv;  
  22.         // title  
  23.         tv = (TextView) view.findViewById(R.id.title);  
  24.         tv.setText(title);  
  25.         // message  
  26.         tv = (TextView) view.findViewById(R.id.message);  
  27.         tv.setText(message);  
  28.   
  29.         Dialog dialog = new Dialog(getActivity(), R.style.AppTheme_Dialog);  
  30.         dialog.setContentView(view);  
  31.   
  32.         return dialog;  
  33.     }  
  34. }  
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="wrap_content"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <TextView  
  8.         android:id="@+id/title"  
  9.         style="?attr/actionBarStyle"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="?attr/actionBarSize"  
  12.         android:gravity="center_vertical"  
  13.         android:paddingLeft="16dp"  
  14.         android:paddingRight="16dp"  
  15.         android:textAppearance="?android:attr/textAppearanceMedium" />  
  16.   
  17.     <ScrollView  
  18.         android:layout_width="match_parent"  
  19.         android:layout_height="match_parent" >  
  20.   
  21.         <TextView  
  22.             android:id="@+id/message"  
  23.             android:layout_width="match_parent"  
  24.             android:layout_height="wrap_content"  
  25.             android:layout_margin="16dip"  
  26.             android:autoLink="web|email"  
  27.             android:linksClickable="true" />  
  28.     </ScrollView>  
  29.   
  30. </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 アプリが起動します)。
  1. NfcAdapter mNfcAdapter;  
  2.   
  3. @Override  
  4. protected void onResume() {  
  5.     super.onResume();  
  6.     if (mNfcAdapter != null) {  
  7.         PendingIntent pendingIntent = PendingIntent.getActivity(this0,  
  8.                 getPendingIntent().addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);  
  9.   
  10.         IntentFilter ndef = IntentFilter.create(NfcAdapter.ACTION_NDEF_DISCOVERED, "*/*");  
  11.         IntentFilter[] intentFiltersArray = new IntentFilter[] { ndef };  
  12.   
  13.         mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, null);  
  14.     }  
  15. }  
  16.   
  17. @Override  
  18. protected void onPause() {  
  19.     super.onPause();  
  20.     if (mNfcAdapter != null) {  
  21.         mNfcAdapter.disableForegroundDispatch(this);  
  22.     }  
  23. }  
次のように enableForegroundDispatch() の第3引数と第4引数に null を渡すと、ともかく全部拾ってくれるようになります。
  1. @Override  
  2. protected void onResume() {  
  3.     super.onResume();  
  4.     if (mNfcAdapter != null) {  
  5.         PendingIntent pendingIntent = PendingIntent.getActivity(this0,  
  6.                 getPendingIntent().addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);  
  7.   
  8.         // catch all ndef  
  9.         mNfcAdapter.enableForegroundDispatch(this, pendingIntent, nullnull);  
  10.     }  
  11. }  
IntentFilter[] を指定して Chrome の AndroidBeam も受けとるには、次のように scheme を指定した IntentFilter も追加します。
  1. @Override  
  2. protected void onResume() {  
  3.     super.onResume();  
  4.     if (mNfcAdapter != null) {  
  5.         PendingIntent pendingIntent = PendingIntent.getActivity(this0,  
  6.                 getPendingIntent().addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);  
  7.           
  8.         IntentFilter ndef = IntentFilter.create(NfcAdapter.ACTION_NDEF_DISCOVERED, "*/*");  
  9.   
  10.         IntentFilter ndef2 = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);  
  11.         ndef2.addDataScheme("");  
  12.         ndef2.addDataScheme("http");  
  13.         ndef2.addDataScheme("https");  
  14.           
  15.         IntentFilter[] intentFiltersArray = new IntentFilter[] { ndef, ndef2 };  
  16.   
  17.         mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, null);  
  18.     }  
  19. }  



---------------------------------------------


ここからは解説というか内部コードのメモです。

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
  1. 1011     public void enableForegroundDispatch(Activity activity, PendingIntent intent,  
  2. 1012             IntentFilter[] filters, String[][] techLists) {  
  3. 1013         if (activity == null || intent == null) {  
  4. 1014             throw new NullPointerException();  
  5. 1015         }  
  6. 1016         if (!activity.isResumed()) {  
  7. 1017             throw new IllegalStateException("Foreground dispatch can only be enabled " +  
  8. 1018                     "when your activity is resumed");  
  9. 1019         }  
  10. 1020         try {  
  11. 1021             TechListParcel parcel = null;  
  12. 1022             if (techLists != null && techLists.length > 0) {  
  13. 1023                 parcel = new TechListParcel(techLists);  
  14. 1024             }  
  15. 1025             ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,  
  16. 1026                     mForegroundDispatchListener);  
  17. 1027             sService.setForegroundDispatch(intent, filters, parcel);  
  18. 1028         } catch (RemoteException e) {  
  19. 1029             attemptDeadServiceRecovery(e);  
  20. 1030         }  
  21. 1031     }  
  22. 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
  1. 837         public void setForegroundDispatch(PendingIntent intent,  
  2. 838                 IntentFilter[] filters, TechListParcel techListsParcel) {  
  3. 839             mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);  
  4. 840   
  5. 841             // Short-cut the disable path  
  6. 842             if (intent == null && filters == null && techListsParcel == null) {  
  7. 843                 mNfcDispatcher.setForegroundDispatch(nullnullnull);  
  8. 844                 return;  
  9. 845             }  
  10. 846   
  11. 847             // Validate the IntentFilters  
  12. 848             if (filters != null) {  
  13. 849                 if (filters.length == 0) {  
  14. 850                     filters = null;  
  15. 851                 } else {  
  16. 852                     for (IntentFilter filter : filters) {  
  17. 853                         if (filter == null) {  
  18. 854                             throw new IllegalArgumentException("null IntentFilter");  
  19. 855                         }  
  20. 856                     }  
  21. 857                 }  
  22. 858             }  
  23. 859   
  24. 860             // Validate the tech lists  
  25. 861             String[][] techLists = null;  
  26. 862             if (techListsParcel != null) {  
  27. 863                 techLists = techListsParcel.getTechLists();  
  28. 864             }  
  29. 865   
  30. 866             mNfcDispatcher.setForegroundDispatch(intent, filters, techLists);  
  31. 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
  1.  81     public synchronized void setForegroundDispatch(PendingIntent intent,  
  2.  82             IntentFilter[] filters, String[][] techLists) {  
  3.  83         if (DBG) Log.d(TAG, "Set Foreground Dispatch");  
  4.  84         mOverrideIntent = intent;  
  5.  85         mOverrideFilters = filters;  
  6.  86         mOverrideTechLists = techLists;  
  7.  87     }  
  8.   
  9. 182     /** Returns false if no activities were found to dispatch to */  
  10. 183     public boolean dispatchTag(Tag tag) {  
  11. 184         NdefMessage message = null;  
  12. 185         Ndef ndef = Ndef.get(tag);  
  13. 186         if (ndef != null) {  
  14. 187             message = ndef.getCachedNdefMessage();  
  15. 188         }  
  16. 189         if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);  
  17. 190   
  18. 191         PendingIntent overrideIntent;  
  19. 192         IntentFilter[] overrideFilters;  
  20. 193         String[][] overrideTechLists;  
  21. 194   
  22. 195         DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);  
  23. 196         synchronized (this) {  
  24. 197             overrideFilters = mOverrideFilters;  
  25. 198             overrideIntent = mOverrideIntent;  
  26. 199             overrideTechLists = mOverrideTechLists;  
  27. 200         }  
  28. 201   
  29. 202         resumeAppSwitches();  
  30. 203   
  31. 204         if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters, overrideTechLists)) {  
  32. 205             return true;  
  33. 206         }  
  34. 207   
  35. 208         if (mHandoverManager.tryHandover(message)) {  
  36. 209             if (DBG) Log.i(TAG, "matched BT HANDOVER");  
  37. 210             return true;  
  38. 211         }  
  39. 212   
  40. 213         if (tryNdef(dispatch, message)) {  
  41. 214             return true;  
  42. 215         }  
  43. 216   
  44. 217         if (tryTech(dispatch, tag)) {  
  45. 218             return true;  
  46. 219         }  
  47. 220   
  48. 221         dispatch.setTagIntent();  
  49. 222         if (dispatch.tryStartActivity()) {  
  50. 223             if (DBG) Log.i(TAG, "matched TAG");  
  51. 224             return true;  
  52. 225         }  
  53. 226   
  54. 227         if (DBG) Log.i(TAG, "no match");  
  55. 228         return false;  
  56. 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
  1. 231     boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent,  
  2. 232             IntentFilter[] overrideFilters, String[][] overrideTechLists) {  
  3. 233         if (overrideIntent == null) {  
  4. 234             return false;  
  5. 235         }  
  6. 236         Intent intent;  
  7. 237   
  8. 238         // NDEF  
  9. 239         if (message != null) {  
  10. 240             intent = dispatch.setNdefIntent();  
  11. 241             if (intent != null &&  
  12. 242                     isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {  
  13. 243                 try {  
  14. 244                     overrideIntent.send(mContext, Activity.RESULT_OK, intent);  
  15. 245                     if (DBG) Log.i(TAG, "matched NDEF override");  
  16. 246                     return true;  
  17. 247                 } catch (CanceledException e) {  
  18. 248                     return false;  
  19. 249                 }  
  20. 250             }  
  21. 251         }  
  22. 252   
  23. 253         // TECH  
  24. 254         intent = dispatch.setTechIntent();  
  25. 255         if (isTechMatch(tag, overrideTechLists)) {  
  26. 256             try {  
  27. 257                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);  
  28. 258                 if (DBG) Log.i(TAG, "matched TECH override");  
  29. 259                 return true;  
  30. 260             } catch (CanceledException e) {  
  31. 261                 return false;  
  32. 262             }  
  33. 263         }  
  34. 264   
  35. 265         // TAG  
  36. 266         intent = dispatch.setTagIntent();  
  37. 267         if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {  
  38. 268             try {  
  39. 269                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);  
  40. 270                 if (DBG) Log.i(TAG, "matched TAG override");  
  41. 271                 return true;  
  42. 272             } catch (CanceledException e) {  
  43. 273                 return false;  
  44. 274             }  
  45. 275         }  
  46. 276         return false;  
  47. 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
  1. 279     boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) {  
  2. 280         if (filters != null) {  
  3. 281             for (IntentFilter filter : filters) {  
  4. 282                 if (filter.match(mContentResolver, intent, false, TAG) >= 0) {  
  5. 283                     return true;  
  6. 284                 }  
  7. 285             }  
  8. 286         } else if (!hasTechFilter) {  
  9. 287             return true;  // always match if both filters and techlists are null  
  10. 288         }  
  11. 289         return false;  
  12. 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
  1. 1068     public final int match(ContentResolver resolver, Intent intent,  
  2. 1069             boolean resolve, String logTag) {  
  3. 1070         String type = resolve ? intent.resolveType(resolver) : intent.getType();  
  4. 1071         return match(intent.getAction(), type, intent.getScheme(),  
  5. 1072                      intent.getData(), intent.getCategories(), logTag);  
  6. 1073     }  
  7.   
  8. 1103     public final int match(String action, String type, String scheme,  
  9. 1104             Uri data, Set<string> categories, String logTag) {  
  10. 1105         if (action != null && !matchAction(action)) {  
  11. 1106             if (false) Log.v(  
  12. 1107                 logTag, "No matching action " + action + " for " + this);  
  13. 1108             return NO_MATCH_ACTION;  
  14. 1109         }  
  15. 1110   
  16. 1111         int dataMatch = matchData(type, scheme, data);  
  17. 1112         if (dataMatch < 0) {  
  18. 1113             if (false) {  
  19. 1114                 if (dataMatch == NO_MATCH_TYPE) {  
  20. 1115                     Log.v(logTag, "No matching type " + type  
  21. 1116                           + " for " + this);  
  22. 1117                 }  
  23. 1118                 if (dataMatch == NO_MATCH_DATA) {  
  24. 1119                     Log.v(logTag, "No matching scheme/path " + data  
  25. 1120                           + " for " + this);  
  26. 1121                 }  
  27. 1122             }  
  28. 1123             return dataMatch;  
  29. 1124         }  
  30. 1125   
  31. 1126         String categoryMismatch = matchCategories(categories);  
  32. 1127         if (categoryMismatch != null) {  
  33. 1128             if (false) {  
  34. 1129                 Log.v(logTag, "No matching category " + categoryMismatch + " for " + this);  
  35. 1130             }  
  36. 1131             return NO_MATCH_CATEGORY;  
  37. 1132         }  
  38. 1133   
  39. 1134         // It would be nice to treat container activities as more  
  40. 1135         // important than ones that can be embedded, but this is not the way...  
  41. 1136         if (false) {  
  42. 1137             if (categories != null) {  
  43. 1138                 dataMatch -= mCategories.size() - categories.size();  
  44. 1139             }  
  45. 1140         }  
  46. 1141   
  47. 1142         return dataMatch;  
  48. 1143     }  
  49. tring>  
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
  1. 899     public final int matchData(String type, String scheme, Uri data) {  
  2. 900         final ArrayList<string> types = mDataTypes;  
  3. 901         final ArrayList<string> schemes = mDataSchemes;  
  4. 902         final ArrayList<authorityentry> authorities = mDataAuthorities;  
  5. 903         final ArrayList<patternmatcher> paths = mDataPaths;  
  6. 904   
  7. 905         int match = MATCH_CATEGORY_EMPTY;  
  8. 906   
  9. 907         if (types == null && schemes == null) {  
  10. 908             return ((type == null && data == null)  
  11. 909                 ? (MATCH_CATEGORY_EMPTY+MATCH_ADJUSTMENT_NORMAL) : NO_MATCH_DATA);  
  12. 910         }  
  13. 911   
  14. 912         if (schemes != null) {  
  15. 913             if (schemes.contains(scheme != null ? scheme : "")) {  
  16. 914                 match = MATCH_CATEGORY_SCHEME;  
  17. 915             } else {  
  18. 916                 return NO_MATCH_DATA;  
  19. 917             }  
  20. 918   
  21. 919             if (authorities != null) {  
  22. 920                 int authMatch = matchDataAuthority(data);  
  23. 921                 if (authMatch >= 0) {  
  24. 922                     if (paths == null) {  
  25. 923                         match = authMatch;  
  26. 924                     } else if (hasDataPath(data.getPath())) {  
  27. 925                         match = MATCH_CATEGORY_PATH;  
  28. 926                     } else {  
  29. 927                         return NO_MATCH_DATA;  
  30. 928                     }  
  31. 929                 } else {  
  32. 930                     return NO_MATCH_DATA;  
  33. 931                 }  
  34. 932             }  
  35. 933         } else {  
  36. 934             // Special case: match either an Intent with no data URI,  
  37. 935             // or with a scheme: URI.  This is to give a convenience for  
  38. 936             // the common case where you want to deal with data in a  
  39. 937             // content provider, which is done by type, and we don't want  
  40. 938             // to force everyone to say they handle content: or file: URIs.  
  41. 939             if (scheme != null && !"".equals(scheme)  
  42. 940                     && !"content".equals(scheme)  
  43. 941                     && !"file".equals(scheme)) {  
  44. 942                 return NO_MATCH_DATA;  
  45. 943             }  
  46. 944         }  
  47. 945   
  48. 946         if (types != null) {  
  49. 947             if (findMimeType(type)) {  
  50. 948                 match = MATCH_CATEGORY_TYPE;  
  51. 949             } else {  
  52. 950                 return NO_MATCH_TYPE;  
  53. 951             }  
  54. 952         } else {  
  55. 953             // If no MIME types are specified, then we will only match against  
  56. 954             // an Intent that does not have a MIME type.  
  57. 955             if (type != null) {  
  58. 956                 return NO_MATCH_TYPE;  
  59. 957             }  
  60. 958         }  
  61. 959   
  62. 960         return match + MATCH_ADJUSTMENT_NORMAL;  
  63. 961     }  
  64. tternmatcher></authorityentry></string></string>  


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] はオーナーユーザーだと普通に動くっぽいのですが、サブユーザーはこのディレクトリに書込み権限がないので、書込もうとするとエラーになります。



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
  1. 139     public static final short TNF_EXTERNAL_TYPE = 0x04;  
  2.   
  3. 223     public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();  
  4.   
  5. 309     public static NdefRecord createApplicationRecord(String packageName) {  
  6. 310         if (packageName == nullthrow new NullPointerException("packageName is null");  
  7. 311         if (packageName.length() == 0throw new IllegalArgumentException("packageName is empty");  
  8. 312   
  9. 313         return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,  
  10. 314                 packageName.getBytes(Charsets.UTF_8));  
  11. 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
  1. 71     public ScrollingTabContainerView(Context context) {  
  2. 72         super(context);  
  3. 73         setHorizontalScrollBarEnabled(false);  
  4. 74   
  5. 75         ActionBarPolicy abp = ActionBarPolicy.get(context);  
  6. 76         setContentHeight(abp.getTabContainerHeight());  
  7. 77         mStackedTabMaxWidth = abp.getStackedTabMaxWidth();  
  8. 78   
  9. 79         mTabLayout = createTabLayout();  
  10. 80         addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,  
  11. 81                 ViewGroup.LayoutParams.MATCH_PARENT));  
  12. 82     }  
  13. 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
  1. 65     public int getTabContainerHeight() {  
  2. 66         TypedArray a = mContext.obtainStyledAttributes(null, R.styleable.ActionBar,  
  3. 67                 com.android.internal.R.attr.actionBarStyle, 0);  
  4. 68         int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);  
  5. 69         Resources r = mContext.getResources();  
  6. 70         if (!hasEmbeddedTabs()) {  
  7. 71             // Stacked tabs; limit the height  
  8. 72             height = Math.min(height,  
  9. 73                     r.getDimensionPixelSize(R.dimen.action_bar_stacked_max_height));  
  10. 74         }  
  11. 75         a.recycle();  
  12. 76         return height;  
  13. 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
  1. 229     <!-- Maximum height for a stacked tab bar as part of an action bar -->  
  2. 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
  1. public static NdefRecord createMime(String mimeType, byte[] mimeData) {  
  2.     if (mimeType == null)  
  3.         throw new NullPointerException("mimeType is null");  
  4.   
  5.     // We only do basic MIME type validation: trying to follow the  
  6.     // RFCs strictly only ends in tears, since there are lots of MIME  
  7.     // types in common use that are not strictly valid as per RFC rules  
  8.     mimeType = normalizeMimeType(mimeType);  
  9.     if (mimeType.length() == 0)  
  10.         throw new IllegalArgumentException("mimeType is empty");  
  11.     int slashIndex = mimeType.indexOf('/');  
  12.     if (slashIndex == 0)  
  13.         throw new IllegalArgumentException("mimeType must have major type");  
  14.     if (slashIndex == mimeType.length() - 1) {  
  15.         throw new IllegalArgumentException("mimeType must have minor type");  
  16.     }  
  17.     // missing '/' is allowed  
  18.   
  19.     // MIME RFCs suggest ASCII encoding for content-type  
  20.     byte[] typeBytes = mimeType.getBytes(Charsets.US_ASCII);  
  21.     return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typeBytes, null, mimeData);  
  22. }  
このメソッドをバックポートするときに、そのままコードを移植すると実行時に IllegalArgumentException が起こる場合があります。

4.3 の HTC One では起こりませんでしたが、4.0.4 の ARROWS V では起こりました。
(Jelly Bean なら起こらない?)
  1. 10-03 19:08:40.622: E/AndroidRuntime(2218): java.lang.IllegalArgumentException: Illegal null argument  
  2. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at android.os.Parcel.readException(Parcel.java:1351)  
  3. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at android.os.Parcel.readException(Parcel.java:1301)  
  4. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at android.nfc.INdefPushCallback$Stub$Proxy.createMessage(INdefPushCallback.java:95)  
  5. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at com.android.nfc.P2pLinkManager.prepareMessageToSend(P2pLinkManager.java:241)  
  6. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at com.android.nfc.P2pLinkManager.onLlcpActivated(P2pLinkManager.java:206)  
  7. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at com.android.nfc.NfcService$NfcServiceHandler.llcpActivated(NfcService.java:1845)  
  8. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at com.android.nfc.NfcService$NfcServiceHandler.handleMessage(NfcService.java:1718)  
  9. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at android.os.Handler.dispatchMessage(Handler.java:99)  
  10. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at android.os.Looper.loop(Looper.java:137)  
  11. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at android.app.ActivityThread.main(ActivityThread.java:4479)  
  12. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at java.lang.reflect.Method.invokeNative(Native Method)  
  13. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at java.lang.reflect.Method.invoke(Method.java:511)  
  14. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)  
  15. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)  
  16. 10-03 19:08:40.622: E/AndroidRuntime(2218):  at dalvik.system.NativeStart.main(Native Method)  
この現象を避けるには、NdefRecord のコンストラクタの第3引数を null から new byte[0] に変えます。
  1. public static NdefRecord createMime(String mimeType, byte[] mimeData) {  
  2.     ...  
  3.     return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, typeBytes, new byte[0], mimeData);  
  4. }  




2013年10月1日火曜日

Android GoogleCloudMessaging.register() はUIスレッドで呼んではいけない

  1. public static String getRegistrationId(Context context) {  
  2.     GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);  
  3.   
  4.     String registrationId = null;  
  5.     try {  
  6.         registrationId = gcm.register(Consts.PROJECT_NUMBER);  
  7.     } catch (IOException e) {  
  8.         e.printStackTrace();  
  9.     }  
  10.   
  11.     return registrationId;  
  12. }  
を呼んでるんだけど null が返ってくる、なぜだ。。。と思っていたら、IOException が発生していた。。。
  1. 10-01 18:17:18.489: W/System.err(6397): java.io.IOException: MAIN_THREAD  
  2. 10-01 18:17:18.489: W/System.err(6397):  at com.google.android.gms.gcm.GoogleCloudMessaging.register(Unknown Source)  
UIスレッドで呼んじゃいけないのか。リファレンスに書いておいてほしかったなー。。。

と思ったら register() のリファレンスには書いてないのに、なぜかエラーコードのリファレンスにだけ書いてあるとか。ううう。