Infinito Nirone 7

白羽の矢を刺すスタイル

ホッ転トリ

TL;DR

実を言うと私はDrivemode, Inc.を退職します。 突然こんなこと言ってごめんね。 でも本当です。

本日が Drivemode, Inc. に籍がある最後の日になります。お疲れさまでした。 次も変わらず Android アプリのエンジニアで、明日からメルペイで働き始めます。六本木界隈の皆様よろしくおねがいします。

近況について

実のところ、10月中旬からずっと有給消化をしていて、引っ越しをやったり、ジャパンカップクリテリウムツール・ド・フランスさいたまクリテリウムの観戦をしたり、ジャパンカップのあと LottoNL Jumbo チームのアフターパーティーで選手たちとセルフィーを撮ったり、あとは吉祥寺ニワカを脱するためにハモニカ横丁やらなんやらでランチを楽しんだり、自転車でロングライドをちょくちょくやったりしました。Cafe Kiki という飯能にあるサイクリスト御用達のカフェへド平日に行ってしまい、開いてない悲しみを味わったりもしましたが、私は元気です。その近くにある古民家レストランぽれぽれというオシャレなところでランチを食べたところ、将来古民家で暮らしたい欲が湧きました。

有給消化している間、ほとんどの時間を一人でモクモクとでかけるなり作業するなりしていましたが、その中で自分にはリモートワークはあまり向かないなという気持ちを得ました。もしかするとやり方が違えばまた感想も変わるのかもしれませんが、自分にとっては物理的なオフィスに足を運んで仕事をするスタイルのほうが性に合っている気がします(もちろん、必要に応じてフレキシブルにできるのもも大事なんですが)。

Drivemode での日々について

Drivemode にはアメリカで会社が立ち上がってすぐのタイミングでジョインしたので、日本で仕事をするときは渋谷のコワーキングスペースを借りてやっていました。そのあと表参道にうつり単独でオフィスを構え、そこからさらに東新宿のマンションにうつって現在に至ります。

4年と4ヶ月くらい在籍しましたが、その間アメリカだけではなくルクセンブルクに行ったり、そのついでにイギリスを旅行したり、帰りにパリの空港(CDG)の乗り継ぎで迷子になったり、その当時のアプリがA3というアワードを受賞したときに有名なグラビアアイドルと写真を撮ってもらったり、東新宿にうつって日本のメンバーが増えてからは、オフィスで様々な国の料理が振る舞われたり、急にネットミームが飛び交い始めたり、あと最近はあまりできていなかったけど、DriveKaigi という名前からしパクリオマージュなイベントもやったりしました。

全く英語をしゃべれないところから、同僚たちにサポートしてもらって徐々に喋れるようになってきて、気がつけば一人でロンドンやらルクセンブルクにいくまでになりました。駅前留学やら語学サポートやら、そういったものも特に使いませんでしたが、とにかく仕事をする上で英語が使えなければどうにもならない環境で少しずつでもしゃべることを覚えていった感じです。

エンジニアとしては、プロダクト初期のカオスのなかで、実現したいことのためにその方法を調べては考え、PoCを組んでうまくいくか試すプロセスはとても興奮する体験です。失敗もたくさんありますが、最終的にうまく動いてくれたときの達成感はこのタイミングだからこそ感じられると思います。

このあたりの話の体系だったまとめが最近ツイッターの連続ツイートでまとめられたので貼っておきます。

そういえば、DroidKaigi の運営スタッフ業も Drivemode に入社して以後関わっています。気がつけば最古参のひとりになっていて、ここまであっという間だったなと振り返ってみると思います。

次のチャレンジについて

会社規模としてはこれまでと比べて圧倒的に大きくなるのですが、プロダクトのフェーズとしては初期段階のカオスの中を駆け抜けるチャレンジングな時期だと思います。すごい人たちがめちゃくちゃたくさん集まった環境で、それでも自分の存在感や価値を見失わず磨きをかけていける場所での仕事でもあると思っています。

そういえば、Drivemode にいるころから「どこで仕事しているのか」とよく聞かれてきましたが、これまでと変わらずこれからも日本で仕事をします。海外に行く機会は減ってしまうでしょうが……場所はご存知六本木になります。今までは渋谷や表参道、新宿など山手線に近いエリアで仕事をしてきましたが、ついに一歩踏み込んで六本木になります。入社を決意するまで自分が六本木で働くというイメージは全くもっていませんでしたが、引っ越しやら何やらでこれまでよりもアクセスのいい場所を選んでみたらなんとなく実感が湧いてきました。

干芋

http://amzn.asia/8gORgFr

さよなら吉祥寺

大学を卒業してからずっと住み続けた吉祥寺(正確には吉祥寺から北に離れた練馬区のエリア)からついに離れることにした。今の家は広くて設備も整っていて家賃もお手頃なので、およそ考えうる限り通勤時間の短縮以外に引っ越す理由は見当たらないのだけれど、もう7年以上も住み続けてきて、不便に思ったこともないわけではないし、ちょうど世間的には引っ越しが落ち着く時期でもあるので、重い腰を上げて引っ越すことにした。

もともとは渋谷にアクセスの良いところでお手頃に住める場所ということと、住みたい街ランキング上位の常連であること、漫画の影響でなんとなくのんびりできてよさそうな街であることという基準で吉祥寺に住むことにした。その思惑通り、吉祥寺にはだいたいなんでもあって電車で渋谷や新宿に出なくても大抵のことは済んだし、井の頭公園にいけばのんびりもできた。これは後付けではあるけど、ロードバイクを趣味にしているので奥多摩方面へのアクセスがよいのも助かる。ただ一方で、家が吉祥寺の中心からは離れていて、むしろ西武新宿線のエリアといったほうがよいくらいだったので、そこは家選びをしくったなとは思っている。実際電車も西武新宿線はそれほど便利とは言えないし……その分で家賃がお手頃だったのは間違いない。

それでもやはり吉祥寺は気に入っていて、行きつけのつけ麺屋や油そばのお店があったり、松亭があったり、あの雰囲気の中で過ごす機会が減ってしまうのは寂しい。そう思う程度には長居をしすぎたとも言えるけど、とにかく今は新居での生活を楽しみにしようと思う。なんせ前回の引っ越しが7年以上も前だったのもあって、引越の手続きなどの勝手を完全に忘れ去っていていろいろてんやわんやしてしまったためにちょっとストレスを溜めてしまったけれども、この引っ越しのタイミングの一時的なものだし、引っ越しが終わったあとの生活にも楽しみなことがたくさんあるので、やっていこうな。

MotionLayout 内にある RecyclerView のスクロールを制御する

Note

この記事はcom.android.support.constraint:constraint-layout:2.0.0-alpha2時点のMotionLayoutについての記述です。 まだアルファ版なので将来的に挙動が大きく変わる可能性があります。

MotionLayout 内にある RecyclerView をスクロール可能にする

MotionScene内の<OnSwipe>moveWhenScrollAtTopというプロパティがあり、これがtrueのときにRecyclerViewがスクロール可能になります。これがfalseになると、アニメーションが動くべき方向にドラッグしたときにアニメーションが始まってしまいます。

たとえば、MotionScene で定義した動きとして縦にスクロールするRecyclerView を下にドラッグするとアニメーションするような場合、moveWhenScrollAtToptrueの場合は RecylerView のもつ 0 番目のアイテムが一番上にきたときにアニメーションを開始し、falseのときはスクロールせずにアニメーションを開始します。

RecylerView のスクロールと MotionLayout のアニメーション開始を別に扱いたい

moveWhenScrollAtToptrueのとき、RecyclerView を勢いよくフリングすると、0 番目のアイテムが一番上に来た直後に MotionLayout へMotionEventが流れてアニメーションが動いてしまいます。この動きは RecyclerView の定義にあるもので、自分がこれ以上スクロールできない位置まで来ると、NestedScroll できる親にMotionEventが流れるようになっています。

もし RecyclerView のスクロールに続いて MotionLayout のアニメーションを動かしたくない(RecyclerView のスクロールジェスチャと MotionLayout のスワイプジェスチャを別にする)場合は、RecyclerView のnestedScrollingEnabledfalseにします。 ただし、RecyclerView の ViewPort が一番端にないときに MotionLayout のアニメーションを動かすと、つぎに RecyclerView でスクロールジェスチャをしようとすると RecyclerView はスクロールせず MotionLayout のアニメーションが始まってしまうバグが起きる可能性があります。

MotionLayout の MotionScene で touchAnchorId に指定した View のクリックをハンドルしたい

MotionLayout は ConstraintLayout 2.0.0 から導入された、スワイプジェスチャやクリックジェスチャで View をキーフレームアニメーションさせるときに使うレイアウトです。キーフレームアニメーションは<MotionScene>をルートとする XML リソースで記述でき、ジェスチャ判定を割り当てる View の指定と、アニメーションの開始・終了時点での View のプロパティを宣言すると、MotionLayout がよしなにアニメーションを作ってくれます。

<MotionScene
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:motion="http://schemas.android.com/apk/res-auto">
  <!-- constraintSetStart が開始時点の状態、constraintSetEnd が終了時点の状態 -->
  <Transition
    motion:constraintSetStart="@id/start"
    motion:constraintSetEnd="@id/end"
    motion:duration="500">
    <!-- スワイプジェスチャで動かすときの設定 -->
    <!-- touchAnchorId を使ってどの View にジェスチャ判定をさせるか指定する -->
    <OnSwipe
      motion:dragDirection="dragUp"
      motion:touchAnchorId="@id/swipe_gesture_anchor"
      motion:touchAnchorSide="bottom"
      motion:maxVelocity="1"
      motion:maxAcceleration="1"
      motion:moveWhenScrollAtTop="true"/>
  </Transition>
  <ConstraintSet android:id="@+id/start">
    <!-- ... -->
  </ConstraintSet>
  <ConstraintSet android:id="@+id/end">
    <!-- ... -->
  </ConstraintSet>
</MotionScene>

このとき、<OnSwipe>touchAnchorIdに指定した View でクリック判定をして何かしら処理をしようと思うといくつかの壁にぶち当たります。 touchAnchorId に指定した View で onTouchEvent を拾ってみると、MotionEvent.ACTION_DOWN は onTouchEvent に流れてくるものの、MotionEvent.ACTION_UP は onTouchEvent に流れてきません。 一方で、touchAnchorIdに指定した View に OnClickListener を設定すると、今度は MotionLayout 側に onTouchEvent が流れていきません(ジェスチャによるアニメーションが動かなくなる)。 MotionScene にはもう一つ<OnClick>もありますが、こちらはクリック時にキーフレームアニメーションを開始するためのもので、汎用的に何かしらの処理をトリガーするためのものではありません。

苦肉の策として次のようなコードを書いてみましたが、もうちょっといいやり方無いかな…

/* クリック判定を少し遅らせ、MotionLayout によるキーフレームアニメーションが始まらなければそのまま実行する */

val motionLayout: MotionLayout = // ...
val anchorView: View = // ...
val handler: Handler = // ...
val delayedClickTask: () -> Unit = {
  // ...
}

anchorView.setOnTouchListener { v, event ->
  handler.postDelayed(delayedClickTask, 300L)
  return@setOnTouchListener false
}
motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
  override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
    handler.removeCallbacks(delayedClickTask)
    // ...
  }
  override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
    // ...
  }
})

Android の Picture in Picture と Window Focus

TL;DR

他のアプリが Picture in Picture モードに入り、そのオーバーレイ表示が自分のアプリに重なる場合、自分のアプリのActivityViewonWindowFocusChanged(boolean)は次の順で計3回呼び出されます。

  1. onWindowFocusChanged(true): タスクが他のアプリから自分のアプリに切り替わったとき
  2. onWindowFocusChanged(false): Picture in Picture の表示(アニメーション)が完了してフォーカスがその表示に奪われたとき
  3. onWindowFocusChanged(true): 2からおよそ3〜4秒後に自分のアプリにフォーカスが戻ったとき

なお Android P Beta3 on Essential Phone と Android P on Essential Phone で確認しています。

Picture in Picture とは

Picture in Picture (PiP) は Android O で登場した機能で、アプリの画面 (Activity) を他のアプリに重ねてスクリーンの隅に小さく表示する機能です。たとえば Google Maps でナビゲーションをしているときに他のアプリに切り替えると、ナビゲーションの地図が自動で縮小されてスクリーンの隅に表示されるような機能を指して Picture in Picture と呼んでいます。

この機能はActivityを縮小すると同時にその縮小表示を他のアプリに重ねるため、Runtime Permissions とは別の権限を必要とします。インストールと同時に許可がおりるので普段はあまり気にすることはありませんが、Picture in Picture の表示やシステムの設定画面から、Picture in Picture の権限を取り消すことができます。

Window Focus とは

ざっくりいうと、KeyEvent や TouchEvent を受け付けるWindowを管理する仕組みです。

WindowそのものについてはAndroid を支える技術〈Ⅰ〉に解説がありますが、基本的には各ActivityはひとつのWindowに属し、WindowManagerの管理下に置かれるようになっています。WindowManagerに直接自分でViewを登録する方法もあり、この場合登録したViewは新たなWindowに属し、WindowManagerの管理下に入ります。つまり一つのアプリケーションで複数のWindowが存在し得ます。

Picture in Picture に入る処理

AndroidManifestでPicture in Pictureをサポートしている宣言をし、アプリケーションがActivity#enterPictureInPictureMode(PictureInPictureParams)を呼び出すとPicture in Pictureモードに入ります。このときの処理の実体は(ActivityManagerService#enterPictureInPictureMode(IBinder, PictureInPictureParams))https://android.googlesource.com/platform/frameworks/base.git/+/android-cts-8.1_r7/services/core/java/com/android/server/am/ActivityManagerService.java#8089にあります。おおよそ次のような順で Picture in Picture モードに入る処理を呼び出します。

  1. すでに Picture in Picture モードの場合は何もしない
  2. Activity が Picture in Picture モードをサポートしない場合も何もしない
  3. KeyGuard が表示されている場合は解除を試み、成功したら Picture in Picture モードに入る
  4. KeyGuard が表示されていないければ即座に Picture in Picture モードに入る

この処理の中の"Picture in Picture モードに入る"部分にあたるのはActivityStackSupervisor#moveActivityToPinnedStackLocked(ActivityRecord, Rect, float, boolean, String)です。 ここではフォアグラウンドにいた Activity を Picture in Picture モードにするため、画面のリサイズに関する計算やアニメーションの再生をしています。 このメソッドの最後で Picture in Picture の状態を監視しているリスナーに、Picture in Picture モードに入ったことを知らせています。

Picture in Picture モードに入ったかどうかを監視するリスナーのひとつに、PipManagerがあります。 このPipManagerはハンドセット端末用のものと、TV 端末用のものとがそれぞれ別に存在します。

ハンドセット端末用のPipManagerは、Picture in Picture の表示を管理する PipMenuActivityControllerや、タッチのインタラクションを制御するPipMotionHelperPipTouchHandlerなどへ処理を委譲します。

Picture in Picture に入ってからの処理

Picture in Picture の表示は SystemUI の PipMenuActivity が担当しています。このActivityは WindowFocus を取得してから 3500 ミリ秒後に、Picture in Picture のメニュー(閉じるボタンや設定ボタンなど)を非表示にします。このタイミングでPipMenuActivityから Window Focus が失われ、その時フォアグラウンドにいるアプリの Activity に Window Focus が移動します。冒頭のTL;DRに書いた"およそ3~4秒後"というのはまさに、この 3500 ミリ秒後にメニューを非表示にする処理によるものです。

Picture in Picture に入ったとき以外にも、Picture in Picture モードの表示をタップしてすこし拡大したときにもPipMenuActivityが立ち上がって WindowFocus を奪います。よって、ソフトキーボードが出ている状態で Picture in Picture の表示をタップするとソフトキーボードが閉じるというわけです。

Android O からの Service を foreground で動かすときのベストプラクティス

Android Oreo から、Oreo 以上のバージョンをtargetSdkVersionにしているアプリケーションがForeground Serviceを起動するには、Context#startForegroundService()によるサービスの起動とService#startForeground()による通知の表示の両方を実行しなければならなくなりました。またContext#startForegroundService()から5秒以内にService#startForeground()を呼び出さないと、ANRとしてクラッシュレポートが作成されます。

この"Context#startForegroundService()から5秒以内にService#startForeground()を呼ぶ"というのが厄介で、ServiceがContext#startForegroundService()で動き始めたら問答無用で5秒のカウントが始まるため、その5秒以内にService#stopSelf()などでサービスを止めてもANRになります。 さらに、ANRのときに吐き出されるスタックトレース(実際には例外が飛んでアプリがクラッシュするのでクラッシュレポートも作られるはず)には、Service#startForeground()を呼ばなかったこと以外の情報が皆無なので、ログ出力などツールの力を借りる必要があります。

ここでServiceの起動にいくつかのパラメータが必要なパターンを考えてみます。ServiceActivityと同様に自分で直接インスタンスをつくることはしないので、Intentにパラメータを突っ込んで起動させます。このとき、パラメータの検証をServiceの責務として、検証が通らないとき(≒Serviceが期待するパラメータでないとき)にService#stopSelf()で止めてしまうと、先程説明したとおりService#startForeground()を呼ばなかったとしてクラッシュしてしまいます。

よって、Android O からはContext#startForegroundService()Serviceを起動させる側がServiceにわたすパラメータの検証をしなければなりません。

岐阜について feat. 田舎fm

田舎fm の出演回で自分の出身地である岐阜について話しました。

komatatsu.github.io

そして現在の最新回でまたまた岐阜の話題がでました。

komatatsu.github.io

自分は岐阜の西の方で、最新話では岐阜の東の方の話がでてきて、岐阜の東西と北の認識に分かりを得たのでもう少しこの記事で深く掘り下げてみようかと思います。 ちなみに、岐阜がどこにあるか不明な方のために地図のリンクをはっておきます。琵琶湖は隣の滋賀県にあります。

岐阜県内の地域

岐阜県は大きく分けると美濃地方(名古屋に近い南の方)と飛騨地方(飛騨・高山のある北の方)の2つの地域に分かれます。さらに美濃地方は東西に広く、おおきく東濃(多治見市や恵那市中津川市など)と西濃(大垣市や関が原町、海津市など)に分けられますが、実際には真ん中のあたりも区分けして中濃(関市や美濃加茂市可児市など)と言うこともあります。カンガルーの西濃運輸の西濃はこの西濃のことを指しています。

地図にある線路や道路のつながりを見ると、岐阜を通る路線はだいたいどれも名古屋にたどり着くようになっています。

電車の場合、西は京都方面から東海道線がつながっていて、東は長野方面から中央線がつながっています。北は富山まで続く高山本線があり、飛騨・高山の観光地に行く場合は大阪や名古屋から出ている特急が便利です。 高速道路もだいたい似たようなつながり方をしていて、西は名神高速、東は中央道、北は東海北陸道があり、すべて名古屋にたどり着きます。

なので、西の人も東の人もちょっと街に出て遊ぶといえば名古屋で遊ぶことになります。北の飛騨・高山と名古屋は流石に離れているので、富山に遊びに出る人たちがいると聞いたことがあります。

逆に、東西の行き来はあまり便利ではありません。国道21号線(ほぼ中山道)が東西を結ぶ主要な道路ですが、それ以外には大きな国道が通っていません。 美濃地方の東西の移動に鉄道路線を使う場合は名古屋まで出たほうが楽です。

新幹線も通っていますが辺鄙なところにある謎の駅なので、たいてい名古屋まで電車で行って乗り換えるほうが便利だったりします。

岐阜というと「豪雪地帯」のイメージがある人もいるようですが、西濃やその近辺の平野部にはほとんど積もりません。滋賀県福井県の県境にある山々が雪雲を止めてしまうので、平野部にはその山を超えてくる強風(伊吹おろし)が吹き付けます。

岐阜県には岐阜弁という方言がありますが、地域によってアクセントや訛りに違いがあります。岐阜県を舞台にした某のうりんアニメはヒロインが岐阜弁を喋っていますが、西の方に住んでいた自分としては「三河弁っぽいものが混じっている!」という感じで聞き慣れなさがありました。西の方では関西色が強くなるので、東の方の人たちからすると「関西弁が混じっている!」というように聞こえるかもしれません。 岐阜弁独特の語彙に、「机をつる(運ぶ)」や「鍵をかう(しめる)」、「ご飯をつける(よそう)」などがあります。

岐阜の観光地

定番はやはり白川郷や飛騨・高山のスキーリゾート、下呂温泉でしょう。岐阜はわからなくても飛騨・高山はわかる人が多いような気がするのは気のせいでしょうか。 これは完全に個人の感想ですが、下呂温泉にあるホテルくさかべアルメリアは、長野県にある白樺リゾート池の平ホテルの次くらいに東海地区でCMをバンバン打っている気がします。

登山が好きなら伊吹山地伊吹山鈴鹿山脈・養老山地、飛騨山脈北アルプス)など目白押しです。伊吹山乗鞍岳では自転車のヒルクライムレースも開催されています。 城好きなら岐阜城大垣城・一夜城・郡上八幡城などがあります。

この他、自然を楽しむという意味では、西の方では根尾谷断層やその近くにある淡墨の桜が有名ですし、養老の滝やその近くにある天命反転地も有名です。東の方では恵那峡景勝地として有名で、近くに恵那峡ランドという遊園地もあります。

県の中央付近に目を向けると、美濃市ではツアー・オブ・ジャパンという自転車レースのステージが開催されていたり、各務原市(かかみがはら)には航空自衛隊の基地があり、その近所に航空宇宙博物館があります。

最近は岐阜を見に来る人たちがいるようで、例えば君の名は聖地巡礼だったり、聲の形聖地巡礼だったりと、何かしらの聖地巡礼で密かに人気のようです。聲の形は完全に地元なのでいつも見てたような景色がそこかしこに出てくるのが良かったです。ちなみにのうりんは実在する高校がモデルで岐阜の真ん中のあたりにあり、僕らはみんな河合荘岐阜市内が舞台です。

岐阜を見に来るときに使うであろう岐阜駅に来たときには、駅前にぽつんと立っている黄金の信長像を一度見ておくと、謎の満足感を得られると思います。

岐阜の食

自分はほぼ西濃でよくある食文化しか知らないのですが、おおよそ名古屋に似て赤味噌が中心です。味噌汁は塩辛さがありますが、おでんやとんかつ、豆腐、焼きなす等につける味噌には甘みがあります。「つけてみそかけてみそ」という商品が定番です。

茶店では朝からモーニングメニューをかこんでおばちゃんたちが無限に井戸端会議をしています。分厚いトースト、ゆで卵、コールスローサラダ、ヨーグルト、コーヒーはおそらく定番のモーニングです。 地元の喫茶店のほうがよりいろんなバリエーションが楽しめるかもしれませんが、コメダ珈琲でも一通りのモーニングが楽しめます。

岐阜県特有の食べ物というと地域によって様々ありますが、実際のところ他地域の食べ物はあまり知らないことも多いです。以前会社の人に、岐阜の郷土料理であるところの朴葉焼きが出るところに連れて行ってもらったことがありますが、その時初めてそういう料理があるのだということを知ったくらいです。

岐阜の気候

西濃の特に大垣市海津市あたりは内陸にもかかわらず海抜5m地帯でさながらオランダのような感じです。大きな河川がいくつも(揖斐川長良川木曽川が特に大きい)あり、雨季に河川の氾濫が起きやすく、その対策としてあちこちに堤防が作られています。地下水が豊富で湧いて出てくる水を汲みにくる人たちが集まるほどですが、その分夏はものすごくジメジメした暑さになります。 東濃では特に多治見市あたりは内陸の盆地で夏はひたすら暑いです。毎年のようにニュースで取り上げられています。

おわりに

気がついたら長々と書いてしまいましたが、そんな感じです。