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を上書きすることもできます。
  1. CookieHandler cookieHandler = CookieHandler.getDefault();  
  2. CookieManager cookieManager = (CookieManager) cookieHandler;  
  3. CookieStore cookieStore = cookieManager.getCookieStore();  
  4. URI uri = URI.create(url);  
  5. List<HttpCookie> httpCookies = cookieStore.get(uri);  
  6. if (httpCookies != null && httpCookies.size() > 0) {  
  7.     HttpCookie httpCookie = httpCookies.get(0);  
  8.     httpCookie.setVersion(0);  
  9. }  


2014年5月22日木曜日

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

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

apply plugin: 'android'



apply plugin: 'android-library'

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


そうしないと、AndroidTestCase で
  1. Context context = getContext();  
  2. AssetManager assetManager = context.getAssets();  
  3. 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
  1. ...  
  2.              <android.preference.PreferenceFrameLayout android:id="@+id/prefs"  
  3.                      android:layout_width="match_parent"  
  4.                      android:layout_height="0dip"  
  5.                      android:layout_weight="1"  
  6.                  />  
  7. ...  


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
  1.     512     @Override  
  2.     513     protected void onCreate(Bundle savedInstanceState) {  
  3.     514         super.onCreate(savedInstanceState);  
  4.     515   
  5.     516         setContentView(com.android.internal.R.layout.preference_list_content);  
  6.     517   
  7. ...  
  8.     522         String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);  
  9. ...  
  10.     526   
  11.     527         if (savedInstanceState != null) {  
  12. ...  
  13.     540         } else {  
  14.     541             if (initialFragment != null && mSinglePane) {  
  15.     542                 // If we are just showing a fragment, we want to run in  
  16.     543                 // new fragment mode, but don't need to compute and show  
  17.     544                 // the headers.  
  18.     545                 switchToHeader(initialFragment, initialArguments);  
  19. ...  
  20.     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
  1. 1176     private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {  
  2. 1177         getFragmentManager().popBackStack(BACK_STACK_PREFS,  
  3. 1178                 FragmentManager.POP_BACK_STACK_INCLUSIVE);  
  4. 1179         if (!isValidFragment(fragmentName)) {  
  5. 1180             throw new IllegalArgumentException("Invalid fragment for this activity: "  
  6. 1181                     + fragmentName);  
  7. 1182         }  
  8. 1183         Fragment f = Fragment.instantiate(this, fragmentName, args);  
  9. 1184         FragmentTransaction transaction = getFragmentManager().beginTransaction();  
  10. 1185         transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);  
  11. 1186         transaction.replace(com.android.internal.R.id.prefs, f);  
  12. 1187         transaction.commitAllowingStateLoss();  
  13. 1188     }  
  14. 1189   
  15. 1190     /** 
  16. 1191      * When in two-pane mode, switch the fragment pane to show the given 
  17. 1192      * preference fragment. 
  18. 1193      * 
  19. 1194      * @param fragmentName The name of the fragment to display. 
  20. 1195      * @param args Optional arguments to supply to the fragment. 
  21. 1196      */  
  22. 1197     public void switchToHeader(String fragmentName, Bundle args) {  
  23. 1198         setSelectedHeader(null);  
  24. 1199         switchToHeaderInner(fragmentName, args, 0);  
  25. 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
  1.      44     public PreferenceFrameLayout(Context context, AttributeSet attrs) {  
  2.      45         this(context, attrs, com.android.internal.R.attr.preferenceFrameLayoutStyle);  
  3.      46     }  
  4.      47   
  5.      48     public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) {  
  6.      49         super(context, attrs, defStyle);  
  7.      50         TypedArray a = context.obtainStyledAttributes(attrs,  
  8.      51                 com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0);  
  9.      52   
  10.      53         float density = context.getResources().getDisplayMetrics().density;  
  11.      54         int defaultBorderTop = (int) (density * DEFAULT_BORDER_TOP + 0.5f);  
  12.      55         int defaultBottomPadding = (int) (density * DEFAULT_BORDER_BOTTOM + 0.5f);  
  13.      56         int defaultLeftPadding = (int) (density * DEFAULT_BORDER_LEFT + 0.5f);  
  14.      57         int defaultRightPadding = (int) (density * DEFAULT_BORDER_RIGHT + 0.5f);  
  15.      58   
  16.      59         mBorderTop = a.getDimensionPixelSize(  
  17.      60                 com.android.internal.R.styleable.PreferenceFrameLayout_borderTop,  
  18.      61                 defaultBorderTop);  
  19.      62         mBorderBottom = a.getDimensionPixelSize(  
  20.      63                 com.android.internal.R.styleable.PreferenceFrameLayout_borderBottom,  
  21.      64                 defaultBottomPadding);  
  22.      65         mBorderLeft = a.getDimensionPixelSize(  
  23.      66                 com.android.internal.R.styleable.PreferenceFrameLayout_borderLeft,  
  24.      67                 defaultLeftPadding);  
  25.      68         mBorderRight = a.getDimensionPixelSize(  
  26.      69                 com.android.internal.R.styleable.PreferenceFrameLayout_borderRight,  
  27.      70                 defaultRightPadding);  
  28.      71   
  29.      72         a.recycle();  
  30.      73     }  
  31. ...  
  32.      83     @Override  
  33.      84     public void addView(View child) {  
  34.      85         int borderTop = getPaddingTop();  
  35.      86         int borderBottom = getPaddingBottom();  
  36.      87         int borderLeft = getPaddingLeft();  
  37.      88         int borderRight = getPaddingRight();  
  38.      89   
  39.      90         android.view.ViewGroup.LayoutParams params = child.getLayoutParams();  
  40.      91         LayoutParams layoutParams = params instanceof PreferenceFrameLayout.LayoutParams  
  41.      92             ? (PreferenceFrameLayout.LayoutParams) child.getLayoutParams() : null;  
  42.      93         // Check on the id of the child before adding it.  
  43.      94         if (layoutParams != null && layoutParams.removeBorders) {  
  44.      95             if (mPaddingApplied) {  
  45.      96                 borderTop -= mBorderTop;  
  46.      97                 borderBottom -= mBorderBottom;  
  47.      98                 borderLeft -= mBorderLeft;  
  48.      99                 borderRight -= mBorderRight;  
  49.     100                 mPaddingApplied = false;  
  50.     101             }  
  51.     102         } else {  
  52.     103             // Add the padding to the view group after determining if the  
  53.     104             // padding already exists.  
  54.     105             if (!mPaddingApplied) {  
  55.     106                 borderTop += mBorderTop;  
  56.     107                 borderBottom += mBorderBottom;  
  57.     108                 borderLeft += mBorderLeft;  
  58.     109                 borderRight += mBorderRight;  
  59.     110                 mPaddingApplied = true;  
  60.     111             }  
  61.     112         }  
  62.     113   
  63.     114         int previousTop = getPaddingTop();  
  64.     115         int previousBottom = getPaddingBottom();  
  65.     116         int previousLeft = getPaddingLeft();  
  66.     117         int previousRight = getPaddingRight();  
  67.     118         if (previousTop != borderTop || previousBottom != borderBottom  
  68.     119                 || previousLeft != borderLeft || previousRight != borderRight) {  
  69.     120             setPadding(borderLeft, borderTop, borderRight, borderBottom);  
  70.     121         }  
  71.     122   
  72.     123         super.addView(child);  
  73.     124     }  
PreferenceFrameLayout のデフォルトのスタイルは preferenceFrameLayoutStyle で指定されている



■ 4.4 以降について

http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/themes.xml
  1.      43     <style name="Theme">  
  2. ...  
  3.     319         <item name="preferenceFragmentPaddingSide">@dimen/preference_fragment_padding_side</item>  
  4. ...  
  5.     400     </style>  
  6. ...  
  7.     906     <style name="Theme.Holo">  
  8. ...  
  9.    1193         <!-- PreferenceFrameLayout attributes -->  
  10.    1194         <item name="preferenceFrameLayoutStyle">@android:style/Widget.Holo.PreferenceFrameLayout</item>  
  11. ...  
  12.    1214     </style>  
  13.   
  14.    1221     <style name="Theme.Holo.Light" parent="Theme.Light">  
  15. ...  
  16.    1469         <!-- PreferenceFrameLayout attributes -->  
  17.    1470         <item name="preferenceFrameLayoutStyle">@android:style/Widget.Holo.PreferenceFrameLayout</item>  
  18. ...  
  19.    1563     </style>  
http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/styles.xml#2441
  1. 2441     <style name="Widget.Holo.PreferenceFrameLayout">  
  2. 2442         <item name="android:borderTop">0dip</item>  
  3. 2443         <item name="android:borderBottom">@dimen/preference_fragment_padding_bottom</item>  
  4. 2444         <item name="android:borderLeft">?attr/preferenceFragmentPaddingSide</item>  
  5. 2445         <item name="android:borderRight">?attr/preferenceFragmentPaddingSide</item>  
  6. 2446     </style>  
http://tools.oesf.biz/android-4.4.2_r1.0/xref/frameworks/base/core/res/res/values/dimens.xml#104
  1. 104     <!-- Preference fragment padding, sides -->  
  2. 105     <dimen name="preference_fragment_padding_side">16dp</dimen>  
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 なテーマ属性ではないため、
  1. <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">  
  2.     <item name="android:preferenceFragmentPaddingSide">0dp</item>  
  3. </style>  
のように指定してもコンパイルエラーになります。
次のように * をつけてテーマ属性が存在しているかどうかを無視するようにするとコンパイルできます。
  1. <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">  
  2.     <item name="*android:preferenceFragmentPaddingSide">0dp</item>  
  3. </style>  




■ 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
  1. 2420     <style name="Widget.Holo.PreferenceFrameLayout">  
  2. 2421         <item name="android:borderTop">0dip</item>  
  3. 2422         <item name="android:borderBottom">@dimen/preference_fragment_padding_bottom</item>  
  4. 2423         <item name="android:borderLeft">@dimen/preference_fragment_padding_side</item>  
  5. 2424         <item name="android:borderRight">@dimen/preference_fragment_padding_side</item>  
  6. 2425     </style>  
左右のパディング用には直接 @dimen/preference_fragment_padding_side が指定されています。

preferenceFragmentPaddingSide が使えないため、左右の余白を変えるには preferenceFrameLayoutStyle を上書きするしかなさそうです。
preferenceFrameLayoutStyle も public なテーマではないため、上書きするには * をつける必要があります。
ただし、4.4以降では上書きした preferenceFrameLayoutStyle の値が利用されますが、4.4 未満では指定しても挙動が変わりませんでした。残念。
  1. <style name="AppTheme" parent="android:Theme.Holo">  
  2.     <item name="*android:preferenceFrameLayoutStyle">@style/PreferenceFrameLayout</item>  
  3. </style>  
  4.   
  5. <style name="PreferenceFrameLayout">  
  6.     <item name="*android:borderTop">0dip</item>  
  7.     <item name="*android:borderBottom">0dip</item>  
  8.     <item name="*android:borderLeft">0dip</item>  
  9.     <item name="*android:borderRight">0dip</item>  
  10. </style>  



■ 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にすることで余白を削除できます。
  1. public class SettingsBaseFragment extends Fragment {  
  2.   
  3.     @Override  
  4.     public void onViewCreated(View view, Bundle savedInstanceState) {  
  5.         super.onViewCreated(view, savedInstanceState);  
  6.         ViewParent parent = view.getParent();  
  7.         ViewGroup container = (ViewGroup) parent;  
  8.         if (container != null) {  
  9.             container.setPadding(0000);  
  10.         }  
  11.     }  
  12. }  




2014年5月7日水曜日

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

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



これを表示しないようにするには、View.setOverScrollMode()で、OVER_SCROLL_NEVERを指定します。(デフォルトでは、OVER_SCROLL_ALWAYSになっています。)
  1. listView.setOverScrollMode(View.OVER_SCROLL_NEVER);  

setOverScrollMode()はAPI Level 9からなので、ViewPagerをAPI Level 9未満でも使う場合は、ViewCompat.setOverScrollMode()を使います。
  1. 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
  1. 840     @Override  
  2. 841     public void setOverScrollMode(int mode) {  
  3. 842         if (mode != OVER_SCROLL_NEVER) {  
  4. 843             if (mEdgeGlowTop == null) {  
  5. 844                 Context context = getContext();  
  6. 845                 mEdgeGlowTop = new EdgeEffect(context);  
  7. 846                 mEdgeGlowBottom = new EdgeEffect(context);  
  8. 847             }  
  9. 848         } else {  
  10. 849             mEdgeGlowTop = null;  
  11. 850             mEdgeGlowBottom = null;  
  12. 851         }  
  13. 852         super.setOverScrollMode(mode);  
  14. 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
  1. 2169     @Override  
  2. 2170     public void draw(Canvas canvas) {  
  3. 2171         super.draw(canvas);  
  4. 2172         boolean needsInvalidate = false;  
  5. 2173   
  6. 2174         final int overScrollMode = ViewCompat.getOverScrollMode(this);  
  7. 2175         if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||  
  8. 2176                 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&  
  9. 2177                         mAdapter != null && mAdapter.getCount() > 1)) {  
  10. 2178             if (!mLeftEdge.isFinished()) {  
  11. 2179                 final int restoreCount = canvas.save();  
  12. 2180                 final int height = getHeight() - getPaddingTop() - getPaddingBottom();  
  13. 2181                 final int width = getWidth();  
  14. 2182   
  15. 2183                 canvas.rotate(270);  
  16. 2184                 canvas.translate(-height + getPaddingTop(), mFirstOffset * width);  
  17. 2185                 mLeftEdge.setSize(height, width);  
  18. 2186                 needsInvalidate |= mLeftEdge.draw(canvas);  
  19. 2187                 canvas.restoreToCount(restoreCount);  
  20. 2188             }  
  21. 2189             if (!mRightEdge.isFinished()) {  
  22. 2190                 final int restoreCount = canvas.save();  
  23. 2191                 final int width = getWidth();  
  24. 2192                 final int height = getHeight() - getPaddingTop() - getPaddingBottom();  
  25. 2193   
  26. 2194                 canvas.rotate(90);  
  27. 2195                 canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);  
  28. 2196                 mRightEdge.setSize(height, width);  
  29. 2197                 needsInvalidate |= mRightEdge.draw(canvas);  
  30. 2198                 canvas.restoreToCount(restoreCount);  
  31. 2199             }  
  32. 2200         } else {  
  33. 2201             mLeftEdge.finish();  
  34. 2202             mRightEdge.finish();  
  35. 2203         }  
  36. 2204   
  37. 2205         if (needsInvalidate) {  
  38. 2206             // Keep animating  
  39. 2207             ViewCompat.postInvalidateOnAnimation(this);  
  40. 2208         }  
  41. 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
  1. 143     public void bind(ListView listView) {  
  2. 144         listView.setOnItemClickListener(this);  
  3. 145         listView.setAdapter(getRootAdapter());  
  4. 146   
  5. 147         onAttachedToActivity();  
  6. 148     }  
KitKat以前では、ListViewにHeader、Footerを追加するメソッドはsetAdapter()より前に呼ばなければいけない。
そのため、PreferenceFragmentを継承したクラスでListViewにHeader、Footerを追加するタイミングは、super.onActivityCreated()より以前でなければならない。
  1. public class SettingFragment extends PreferenceFragment {  
  2.   
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         addPreferencesFromResource(R.xml.prefs);  
  7.     }  
  8.   
  9.     @Override  
  10.     public void onViewCreated(View view, Bundle savedInstanceState) {  
  11.         super.onViewCreated(view, savedInstanceState);  
  12.   
  13.         TextView header = new TextView(getActivity());  
  14.         header.setText("Header");  
  15.   
  16.         ListView listView = (ListView) view.findViewById(android.R.id.list);  
  17.         listView.addHeaderView(header);  
  18.     }  
  19.   
  20.     @Override  
  21.     public void onActivityCreated(Bundle savedInstanceState) {  
  22.         super.onActivityCreated(savedInstanceState);  
  23.     }  
  24. }  




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

addPreferencesFromResource()

setPreferenceScreen()

postBindPreferences()

Handler

bindPreferences()

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

listView.setAdapter();