TimePickerの実装に闇があった（解決済み）


Lollipopの前身であるAndroid L Previewが登場してから、もうすぐ１年が経とうとしています。皆さんもLollipop触ってますか？

今回は、LollipopのAndroid SDKコードでたまたま見かけたアレな状況を紹介したいと思います。

TimePickerの新UI
--------------------

[TimePicker](http://developer.android.com/reference/android/widget/TimePicker.html)はAPI Level 1のときから存在する、最も古いUIコンポーネントの1つです。

Lollipopでは、従来のロール方式での時刻選択に加えて、新たに時計の文字盤を模したUIが追加されましたね。これらはモード選択によって、どちらも利用できるようになっています。

近年のNexusユーザーにとっては、そろそろ慣れ親しんだコントロールになってきていることでしょう。

![device-2015-05-21-223608 のコピー.png](https://qiita-image-store.s3.amazonaws.com/0/2276/eb300c31-5c23-0dea-77e9-bbf4bd577654.png "device-2015-05-21-223608 のコピー.png")

```xml:spinnerモード
<TimePicker
    ...
    android:timePickerMode="spinner" />
```

```xml:clockモード
<TimePicker
    ...
    android:timePickerMode="clock" />
```

ハックしようと覗いてみたら
--------------------

TimePickerを使っているとよくあるのが、「時計をN分刻みで選択できるようにしたい」という要件です。

これについては、StackOverflowを始めとして、いくつかの記事が公開されています。私はこちらの記事を参考にして、これまでTimePickerをハックしてきました。

* [[Android] 刻む「分」が指定できる拡張TimePickerを作る【解析編】 ｜ Developers.IO](http://dev.classmethod.jp/smartphone/increment-minutes-custom-timepicker-01/)

簡単に言うと、「TimePickerは`mMinuteSpinner`というNumberPicker型フィールドを持っているから、リフレクションでぶっこ抜いて制御しようぜ！」というやつです。

### 内部実装が従来とは違う

前述のとおり、TimePickerは必ずしもNumberPickerを並べただけのコントロールではなくなってしまったので、実装コードも大幅に変わっています。具体的には、SpinnerモードとClockモードのDelegateを作り、前述の`android:timePickerMode`の値に応じて内部実装を切り替えているのです。

ということで、前述のハックはLollipopでは動きません。一工夫する必要がありそうです。（この辺の話はまた別の機会に）

### 名は体を表・・・さない！？

新しいTimePickerのコードを掘り返していったところ、奇妙なものを見つけました。

![スクリーンショット 2015-05-21 15.39.07.png](https://qiita-image-store.s3.amazonaws.com/0/2276/1585ab84-a349-0118-3a76-30cc6612d680.png "スクリーンショット 2015-05-21 15.39.07.png")
* https://github.com/android/platform_frameworks_base/blob/android-5.0.0_r1/core/java/android/widget/TimePickerClockDelegate.java
* https://github.com/android/platform_frameworks_base/blob/android-5.0.0_r1/core/java/android/widget/TimePickerSpinnerDelegate.java

あ・・・ありのまま、今、起こった事を話すぜ！

「Clock用DelegateがNumberPickerを持ち、Spinner用Delegateが文字盤ビューを持っていた」

な・・・何を言っているのか、わからねーと思うが　

おれも、何を見たのか、わからなかった・・・

頭がどうにかなりそうだった・・・デザインパターンだとか命名規則だとか

そんなチャチなもんじゃあ、断じてねえ

もっと恐ろしいものの片鱗を、味わったぜ・・・

API Level 21 の闇
--------------------

というわけで、名前と実装が逆転しているのを見つけてしまったわけですが、実際問題、レイアウトXMLに対して `timePickerMode` を指定する分には、ちゃんと動作しています。クラス名が逆なのだから、動作も逆になってしまいそうなものですが・・・

と不思議に思いながら、TimePickerがDelegateを切り替えている部分に何となく目を向けてみました。

![スクリーンショット 2015-05-21 18.11.54.png](https://qiita-image-store.s3.amazonaws.com/0/2276/0ab3bec3-f7e8-3120-dd72-129707859fcb.png "スクリーンショット 2015-05-21 18.11.54.png")
https://github.com/android/platform_frameworks_base/blob/android-5.0.0_r1/core/java/android/widget/TimePicker.java#L87-L97

・・・

ん！？

![スクリーンショット 2015-05-21 18.11.54 のコピー.png](https://qiita-image-store.s3.amazonaws.com/0/2276/a66a4fa2-87ea-8821-c8ee-2b50231efb06.png "スクリーンショット 2015-05-21 18.11.54 のコピー.png")

こらこらこらこらー！！？

命名ミスったまま進んでしまったのか、何かのタイミングでRename系リファクタリングのミスがあったのかは知りませんが、宣言のたすき掛けによって、見事に前述の矛盾を誤魔化しています。

API Level 22 で直ってるよ
--------------------

流石にこのままで放置されるはずもなく、現行最新版のAPI Level 22では、この問題は修正されています。

```java:TimePicker.java
switch (mode) {
    case MODE_CLOCK:
        mDelegate = new TimePickerClockDelegate(
                this, context, attrs, defStyleAttr, defStyleRes);
        break;
    case MODE_SPINNER:
    default:
        mDelegate = new TimePickerSpinnerDelegate(
                this, context, attrs, defStyleAttr, defStyleRes);
        break;
}
```
https://github.com/android/platform_frameworks_base/blob/android-5.1.1_r3/core/java/android/widget/TimePicker.java#L87-L97

ファイル名を取り替えるだけといえばだけなんですが、Git的には実装が丸々消されて書き換えられた感じになるので、修正コミットが大惨事になってました。

> "Swap names for clock delegates so they are correct"
> 3 changed files  with 1,444 additions and 1,444 deletions.
> https://github.com/android/platform_frameworks_base/commit/daf33ed85353ab7d7a7668dd0e3f9a66f0d5583f

大変だなあ（他人事）

まとめ
--------------------

面白いもの見つけちゃったなあと思いました（小並感）