2014年5月24日土曜日

HttpURLConnection + CookieManager で RFC 2109 しかサポートしていないサーバーに対応する

HttpClient + CookiePolicy.BROWSER_COMPATIBILITY

でうまくいっていたものが、

HttpURLConnection + CookieManager

でうまくいかないときは、サーバーが RFC 2965 に対応していない可能性があります。RFC 2109 しかサポートしていないサーバーに対応するには、HttpCookieのバージョンに 0 を指定します。

このことは、HttpURLConnectionのページ の「Sessions with Cookies」に書いてあります。このページでは新しくHttpCookieを作っていますが、次のように保存されているHttpCookieを上書きすることもできます。 CookieHandler cookieHandler = CookieHandler.getDefault(); CookieManager cookieManager = (CookieManager) cookieHandler; CookieStore cookieStore = cookieManager.getCookieStore(); URI uri = URI.create(url); List<HttpCookie> httpCookies = cookieStore.get(uri); if (httpCookies != null && httpCookies.size() > 0) { HttpCookie httpCookie = httpCookies.get(0); httpCookie.setVersion(0); }

2014年5月22日木曜日

AndroidStudio でテスト用ファイルを使うときの設定

例えば、androidTest/assets に hoge.html を用意して、テストからこのファイルを利用する場合、

apply plugin: 'android'



apply plugin: 'android-library'

に変更しておく必要があります。


そうしないと、AndroidTestCase で Context context = getContext(); AssetManager assetManager = context.getAssets(); InputStream in = assetManager.open("hoge.html"); としたときに、FileNotFoundException が起こります。
java.io.FileNotFoundException: hoge.html
at android.content.res.AssetManager.openAsset(Native Method)



2014年5月10日土曜日

Preference に指定された Fragment に遷移したときのレイアウトについて

0. Preferenceandroid:fragment 属性で Fragment を指定すると、タップしたときにそのFragmentに遷移する


1. PreferenceActivity のレイアウトは com.android.internal.R.layout.preference_list_content である。

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/layout/preference_list_content.xml ... <android.preference.PreferenceFrameLayout android:id="@+id/prefs" android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" /> ...

2. PreferenceActivity では、EXTRA_SHOW_FRAGMENT というキーに Fragment の完全修飾名を入れることで、起動時に表示する Fragment を指定できる。

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceActivity.java#512 512 @Override 513 protected void onCreate(Bundle savedInstanceState) { 514 super.onCreate(savedInstanceState); 515 516 setContentView(com.android.internal.R.layout.preference_list_content); 517 ... 522 String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT); ... 526 527 if (savedInstanceState != null) { ... 540 } else { 541 if (initialFragment != null && mSinglePane) { 542 // If we are just showing a fragment, we want to run in 543 // new fragment mode, but don't need to compute and show 544 // the headers. 545 switchToHeader(initialFragment, initialArguments); ... 656 }

3. EXTRA_SHOW_FRAGMENT で指定された Fragment は com.android.internal.R.id.prefs という id のコンテナに replace される

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceActivity.java#1176 1176 private void switchToHeaderInner(String fragmentName, Bundle args, int direction) { 1177 getFragmentManager().popBackStack(BACK_STACK_PREFS, 1178 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1179 if (!isValidFragment(fragmentName)) { 1180 throw new IllegalArgumentException("Invalid fragment for this activity: " 1181 + fragmentName); 1182 } 1183 Fragment f = Fragment.instantiate(this, fragmentName, args); 1184 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 1185 transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); 1186 transaction.replace(com.android.internal.R.id.prefs, f); 1187 transaction.commitAllowingStateLoss(); 1188 } 1189 1190 /** 1191 * When in two-pane mode, switch the fragment pane to show the given 1192 * preference fragment. 1193 * 1194 * @param fragmentName The name of the fragment to display. 1195 * @param args Optional arguments to supply to the fragment. 1196 */ 1197 public void switchToHeader(String fragmentName, Bundle args) { 1198 setSelectedHeader(null); 1199 switchToHeaderInner(fragmentName, args, 0); 1200 } 1.で見たように、com.android.internal.R.id.prefs は PreferenceFrameLayout である。


4. PreferenceFrameLayout は子ビューの追加時にスタイル属性で指定された値分だけパディングを追加する

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceFrameLayout.java 44 public PreferenceFrameLayout(Context context, AttributeSet attrs) { 45 this(context, attrs, com.android.internal.R.attr.preferenceFrameLayoutStyle); 46 } 47 48 public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) { 49 super(context, attrs, defStyle); 50 TypedArray a = context.obtainStyledAttributes(attrs, 51 com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0); 52 53 float density = context.getResources().getDisplayMetrics().density; 54 int defaultBorderTop = (int) (density * DEFAULT_BORDER_TOP + 0.5f); 55 int defaultBottomPadding = (int) (density * DEFAULT_BORDER_BOTTOM + 0.5f); 56 int defaultLeftPadding = (int) (density * DEFAULT_BORDER_LEFT + 0.5f); 57 int defaultRightPadding = (int) (density * DEFAULT_BORDER_RIGHT + 0.5f); 58 59 mBorderTop = a.getDimensionPixelSize( 60 com.android.internal.R.styleable.PreferenceFrameLayout_borderTop, 61 defaultBorderTop); 62 mBorderBottom = a.getDimensionPixelSize( 63 com.android.internal.R.styleable.PreferenceFrameLayout_borderBottom, 64 defaultBottomPadding); 65 mBorderLeft = a.getDimensionPixelSize( 66 com.android.internal.R.styleable.PreferenceFrameLayout_borderLeft, 67 defaultLeftPadding); 68 mBorderRight = a.getDimensionPixelSize( 69 com.android.internal.R.styleable.PreferenceFrameLayout_borderRight, 70 defaultRightPadding); 71 72 a.recycle(); 73 } ... 83 @Override 84 public void addView(View child) { 85 int borderTop = getPaddingTop(); 86 int borderBottom = getPaddingBottom(); 87 int borderLeft = getPaddingLeft(); 88 int borderRight = getPaddingRight(); 89 90 android.view.ViewGroup.LayoutParams params = child.getLayoutParams(); 91 LayoutParams layoutParams = params instanceof PreferenceFrameLayout.LayoutParams 92 ? (PreferenceFrameLayout.LayoutParams) child.getLayoutParams() : null; 93 // Check on the id of the child before adding it. 94 if (layoutParams != null && layoutParams.removeBorders) { 95 if (mPaddingApplied) { 96 borderTop -= mBorderTop; 97 borderBottom -= mBorderBottom; 98 borderLeft -= mBorderLeft; 99 borderRight -= mBorderRight; 100 mPaddingApplied = false; 101 } 102 } else { 103 // Add the padding to the view group after determining if the 104 // padding already exists. 105 if (!mPaddingApplied) { 106 borderTop += mBorderTop; 107 borderBottom += mBorderBottom; 108 borderLeft += mBorderLeft; 109 borderRight += mBorderRight; 110 mPaddingApplied = true; 111 } 112 } 113 114 int previousTop = getPaddingTop(); 115 int previousBottom = getPaddingBottom(); 116 int previousLeft = getPaddingLeft(); 117 int previousRight = getPaddingRight(); 118 if (previousTop != borderTop || previousBottom != borderBottom 119 || previousLeft != borderLeft || previousRight != borderRight) { 120 setPadding(borderLeft, borderTop, borderRight, borderBottom); 121 } 122 123 super.addView(child); 124 } PreferenceFrameLayout のデフォルトのスタイルは preferenceFrameLayoutStyle で指定されている



■ 4.4 以降について

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/themes.xml 43 ... 906 1221 http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#2441 2441 http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/dimens.xml#104 104 105 16dp Holoテーマの preferenceFrameLayoutStyle には Widget.Holo.PreferenceFrameLayout が指定されています。
Widget.Holo.PreferenceFrameLayout では、左右のパディング用に
?attr/preferenceFragmentPaddingSide を参照しており、この値には @dimen/preference_fragment_padding_side がセットされています。

@dimen/preference_fragment_padding_side は、values/dimens.xml では 16dp が、values-600dp/dimens.xml では 24dp が、values-720dp/dimens.xml では 32dp が指定されています。

このため、Preferenceに指定されているFragmentに遷移した場合、左右に余白が入ります。

わかりやすいように、Fragmentの背景を赤にしています。




android:preferenceFragmentPaddingSide を指定すれば左右の余白値を変えられそうですが、public なテーマ属性ではないため、 のように指定してもコンパイルエラーになります。
次のように * をつけてテーマ属性が存在しているかどうかを無視するようにするとコンパイルできます。



■ 4.4 未満について

4.4 未満ではテーマ属性に preferenceFragmentPaddingSide がありません。

http://tools.oesf.biz/android-4.3.1_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#2420 2420 左右のパディング用には直接 @dimen/preference_fragment_padding_side が指定されています。

preferenceFragmentPaddingSide が使えないため、左右の余白を変えるには preferenceFrameLayoutStyle を上書きするしかなさそうです。
preferenceFrameLayoutStyle も public なテーマではないため、上書きするには * をつける必要があります。
ただし、4.4以降では上書きした preferenceFrameLayoutStyle の値が利用されますが、4.4 未満では指定しても挙動が変わりませんでした。残念。


■ 4.1.2 未満について

4.1.2 未満では Theme.Holo.Light に preferenceFrameLayoutStyle の指定がありません。それによって困った挙動になっています。
つまり、Theme.Holo では余白が入るのですが、Theme.Holo.Light や Theme.Holo.Light.DarkActionBar では余白が入りません。

http://tools.oesf.biz/android-4.1.1_r1.0/xref/frameworks/base/core/res/res/values/themes.xml

4.1.1 with Theme.Holo




4.1.1 with Theme.Holo.Light.DarkActionBar



「Theme.Light は parent が指定されてないので Theme を継承してるが、Theme.Holo.Light は parent に Theme.Light が指定されているので、Theme.Holo は継承されない」 ということを忘れていたのでしょうか、気をつけましょう。




■ 4.4 未満で余白をなくすには

これまで見てきたように、4.4未満ではテーマで余白サイズを変えることができません。 また、4.1.2未満では Theme.Holo.Light で余白が入らないという問題もあります。 そのため、どのバージョンのデバイスであっても余白が入らないようにしてしまうのが、この問題の現実的な解決方法でしょう。

PreferenceFrameLayoutはFragmentのコンテナになるため、親のViewGroupのpaddingを0にすることで余白を削除できます。 public class SettingsBaseFragment extends Fragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ViewParent parent = view.getParent(); ViewGroup container = (ViewGroup) parent; if (container != null) { container.setPadding(0, 0, 0, 0); } } }



2014年5月7日水曜日

Android 端までスクロールしたときのエフェクトを消す

ListViewやScrollView、ViewPagerなどで端までスクロールすると、水色(KitKat以前)や灰色(KitKat以後)のエフェクトが表示されます。



これを表示しないようにするには、View.setOverScrollMode()で、OVER_SCROLL_NEVERを指定します。(デフォルトでは、OVER_SCROLL_ALWAYSになっています。) listView.setOverScrollMode(View.OVER_SCROLL_NEVER);
setOverScrollMode()はAPI Level 9からなので、ViewPagerをAPI Level 9未満でも使う場合は、ViewCompat.setOverScrollMode()を使います。 ViewCompat.setOverScrollMode(viewPager, ViewCompat.OVER_SCROLL_NEVER);

-------

解説

端のエフェクトはEdgeEffect(http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/widget/EdgeEffect.java)というクラスが担っています。

AbsListViewでは、OVER_SCROLL_NEVERが指定されると、EdgeEffect用の変数にnullを指定するようになっています。

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/widget/AbsListView.java#840 840 @Override 841 public void setOverScrollMode(int mode) { 842 if (mode != OVER_SCROLL_NEVER) { 843 if (mEdgeGlowTop == null) { 844 Context context = getContext(); 845 mEdgeGlowTop = new EdgeEffect(context); 846 mEdgeGlowBottom = new EdgeEffect(context); 847 } 848 } else { 849 mEdgeGlowTop = null; 850 mEdgeGlowBottom = null; 851 } 852 super.setOverScrollMode(mode); 853 } ScrollView、HorizontalScrollViewでも同じようになっています。

ViewPagerでは、描画時にOverScrollModeを取得して、OVER_SCROLL_NEVERの場合はエフェクトを描画しないようになっています。

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/support/v4/java/android/support/v4/view/ViewPager.java#2169 2169 @Override 2170 public void draw(Canvas canvas) { 2171 super.draw(canvas); 2172 boolean needsInvalidate = false; 2173 2174 final int overScrollMode = ViewCompat.getOverScrollMode(this); 2175 if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 2176 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && 2177 mAdapter != null && mAdapter.getCount() > 1)) { 2178 if (!mLeftEdge.isFinished()) { 2179 final int restoreCount = canvas.save(); 2180 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 2181 final int width = getWidth(); 2182 2183 canvas.rotate(270); 2184 canvas.translate(-height + getPaddingTop(), mFirstOffset * width); 2185 mLeftEdge.setSize(height, width); 2186 needsInvalidate |= mLeftEdge.draw(canvas); 2187 canvas.restoreToCount(restoreCount); 2188 } 2189 if (!mRightEdge.isFinished()) { 2190 final int restoreCount = canvas.save(); 2191 final int width = getWidth(); 2192 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 2193 2194 canvas.rotate(90); 2195 canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width); 2196 mRightEdge.setSize(height, width); 2197 needsInvalidate |= mRightEdge.draw(canvas); 2198 canvas.restoreToCount(restoreCount); 2199 } 2200 } else { 2201 mLeftEdge.finish(); 2202 mRightEdge.finish(); 2203 } 2204 2205 if (needsInvalidate) { 2206 // Keep animating 2207 ViewCompat.postInvalidateOnAnimation(this); 2208 } 2209 }



2014年5月6日火曜日

PreferenceFragmentでHeader, Footerを追加するタイミング

onActivityCreated()より前にaddPreferencesFromResource()を呼んだ場合、onActivityCreated()でListViewのsetAdapter()が呼ばれる。
http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceFragment.java

addPreferencesFromResource()

setPreferenceScreen()

onActivityCreated()

bindPreferences()

final PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.bind(getListView());

listView.setAdapter();

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/java/android/preference/PreferenceScreen.java#143 143 public void bind(ListView listView) { 144 listView.setOnItemClickListener(this); 145 listView.setAdapter(getRootAdapter()); 146 147 onAttachedToActivity(); 148 } KitKat以前では、ListViewにHeader、Footerを追加するメソッドはsetAdapter()より前に呼ばなければいけない。
そのため、PreferenceFragmentを継承したクラスでListViewにHeader、Footerを追加するタイミングは、super.onActivityCreated()より以前でなければならない。 public class SettingFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.prefs); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); TextView header = new TextView(getActivity()); header.setText("Header"); ListView listView = (ListView) view.findViewById(android.R.id.list); listView.addHeaderView(header); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } }



*ちなみに、onActivityCreated()より後にaddPreferencesFromResource()を呼んだ場合、Handlerを介してそのタイミングでListViewのsetAdapter()が呼ばれる

addPreferencesFromResource()

setPreferenceScreen()

postBindPreferences()

Handler

bindPreferences()

final PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.bind(getListView());

listView.setAdapter();