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>
ただし、
<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
つまり、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;
}
}
<?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);
}
}
次のように 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つを指定する必要があるのです。
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 }
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
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 }
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>
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);
}
このメソッドをバックポートするときに、そのままコードを移植すると実行時に 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)
この現象を避けるには、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);
}
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;
}
を呼んでるんだけど 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)
UIスレッドで呼んじゃいけないのか。リファレンスに書いておいてほしかったなー。。。と思ったら register() のリファレンスには書いてないのに、なぜかエラーコードのリファレンスにだけ書いてあるとか。ううう。