Android 9.0 pie でランチャーアプリによってナビゲーションバーの挙動が変わるっぽいところのソースを読む

Androidでおなじみのナビゲーションバー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/20e2bccb-7628-3b93-4eed-f4123d144771.png)

私はあいにくお金がなくてPixel 3を購入できていないんですが、巷の噂によると
これがPixel 3のナビゲーションバーは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/f5b891b9-cd5c-b463-9e36-3aa4ce52aaad.png)

こんなのらしい。

そして、どうやら設定でこのあたりを変えることができるらしい。

* https://android.benigumo.com/20180809/pie-navigation-bar/
* https://android.benigumo.com/20190508/android-q-beta3-navigation-bar/

あとは、**ホームアプリによってもこのナビゲーションの挙動が変わる**（？）らしい。

System UIというシステムアプリがホームアプリによって挙動が変わる？！というあたりが気になったので、久々にソース呼んでみました。

あらかじめ書いておきますが、完全に自分用メモです。

-------

## 設定で変えられるところ

手元には実端末がなくてエミュレータしかないんですが、
https://android.benigumo.com/20180809/pie-navigation-bar/
をみるに
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/19298a5d-6e3f-b208-ed6d-694281d0a005.png)
どうやらこの画面にスワイプアップ時の挙動を変更する設定が存在するらしい。

http://androidxref.com/9.0.0_r3/xref/packages/apps/Settings/res/xml/gestures.xml
設定画面のリソースを見ると

```
48    <Preference
49        android:key="gesture_swipe_up_input_summary"
50        android:title="@string/swipe_up_to_switch_apps_title"
51        android:fragment="com.android.settings.gestures.SwipeUpGestureSettings"
52        settings:controller="com.android.settings.gestures.SwipeUpPreferenceController" />
```

たしかにありました。

### 設定できる端末とできない端末の違いは？

http://androidxref.com/9.0.0_r3/xref/packages/apps/Settings/src/com/android/settings/gestures/SwipeUpPreferenceController.java#44

```
44    static boolean isGestureAvailable(Context context) {
45        if (!context.getResources().getBoolean(R.bool.config_swipe_up_gesture_setting_available)) {
46            return false;
47        }
48
49        final ComponentName recentsComponentName = ComponentName.unflattenFromString(
50                context.getString(R.string.config_recentsComponentName));
51        final Intent quickStepIntent = new Intent(ACTION_QUICKSTEP)
52                .setPackage(recentsComponentName.getPackageName());
53        if (context.getPackageManager().resolveService(quickStepIntent,
54                PackageManager.MATCH_SYSTEM_ONLY) == null) {
55            return false;
56        }
57        return true;
58    }
```

2つ条件がある。

１つめは、config_swipe_up_gesture_setting_available っていうのが端末のconfigでtrueになっていないとダメらしい。

http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/values/config.xml#3448 を見るに、おそらくデフォルトはfalse。

```
3444    <!-- Whether or not swipe up gesture is enabled by default -->
3445    <bool name="config_swipe_up_gesture_default">false</bool>
3446
3447    <!-- Whether or not swipe up gesture's opt-in setting is available on this device -->
3448    <bool name="config_swipe_up_gesture_setting_available">false</bool>
```

2つ目はちょっと複雑だけど、

config_recentsComponentName っていうのが端末のconfigにあって、そこにRecentAppsを表示するコンポーネントが書いてある。(AOSPだと `com.android.launcher3/com.android.quickstep.RecentsActivity` )

んで、そいつと同じパッケージに属する子で `Intent.ACTION_QUICKSTEP` を拾ってくれる子だったらOK。（AOSPだと [packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java](http://androidxref.com/9.0.0_r3/xref/packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java) ）

frameworks/resのconfig.xmlもLauncher3も端末出荷状態で入ってるんだから、2つ目のif文なくてもいいんじゃね？って思ったけど、端末出荷状態からLauncher3がアップデートしてACTION_QUICKSTEPを拾わなくなってもバグらないようにするための配慮かな？

## SwipeUpPreferenceController は何のon/offをしているのか

Settingsアプリは、 `SWIPE_UP_TO_SWITCH_APPS_ENABLED` のシステム設定値をon/offしているだけ。
http://androidxref.com/9.0.0_r3/xref/packages/apps/Settings/src/com/android/settings/gestures/SwipeUpPreferenceController.java

これの変化を監視してるのは、AOSPだとLauncher3。

http://androidxref.com/9.0.0_r3/xref/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewInteractionState.java#220

```
219        @Override
220        public void onChange(boolean selfChange) {
221            super.onChange(selfChange);
222            mHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
223            mHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, getValue() ? 1 : 0, 0).sendToTarget();
224        }
```

設定値が切り替わった契機で、

http://androidxref.com/9.0.0_r3/xref/packages/apps/Launcher3/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java#61

```
61    public static TouchController[] createTouchControllers(Launcher launcher) {
62        boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
63                .isSwipeUpGestureEnabled();
64        if (!swipeUpEnabled) {
65            return new TouchController[] {
66                    launcher.getDragController(),
67                    new OverviewToAllAppsTouchController(launcher),
68                    new LauncherTaskViewController(launcher)};
69        }
70        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
71            return new TouchController[] {
72                    launcher.getDragController(),
73                    new OverviewToAllAppsTouchController(launcher),
74                    new LandscapeEdgeSwipeController(launcher),
75                    new LauncherTaskViewController(launcher)};
76        } else {
77            return new TouchController[] {
78                    launcher.getDragController(),
79                    new PortraitStatesTouchController(launcher),
80                    new LauncherTaskViewController(launcher)};
81        }
82    }
```

このあたりの、タッチイベントハンドラのクラス群のどれを使うべきか？、というのを再評価される。

## Quickstep

さっきからちょいちょい出てきてる quickstep っていう単語。あとはOverviewとか。
どうもこいつがキモにみえる。

SystemUIのNavigationBarView付近にこんなソースを見つけた。

http://androidxref.com/9.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java

```
233                if (mIsVertical) {
234                    exceededScrubTouchSlop =
235                            yDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && yDiff > xDiff;
236                    exceededSwipeUpTouchSlop =
237                            xDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && xDiff > yDiff;
238                    pos = y;
239                    touchDown = mTouchDownY;
240                    offset = pos - mTrackRect.top;
241                    trackSize = mTrackRect.height();
242                } else {
243                    exceededScrubTouchSlop =
244                            xDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && xDiff > yDiff;
245                    exceededSwipeUpTouchSlop =
246                            yDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && yDiff > xDiff;
247                    pos = x;
248                    touchDown = mTouchDownX;
249                    offset = pos - mTrackRect.left;
250                    trackSize = mTrackRect.width();
251                }
```

このあたりのロジックを流し見るに、

* 上に24dp以上動かしたら、NavigationBarからLauncher3に「quickstepのハンドリングよろしく」って通知がいく
* 左右に24dp以上動かしたら、NavigationBarからLauncher3に「quick scrubのハンドリングよろしく」って通知がいく

みたいな感じか。あとは、そもそもMotionEventは基本的にNavigationBarからLauncher3に送られるっぽい？

http://androidxref.com/9.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java#proxyMotionEvents

```
488    private boolean proxyMotionEvents(MotionEvent event) {
489        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
490        event.transform(mTransformGlobalMatrix);
491        try {
492            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
493                overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget());
494            }
495            overviewProxy.onMotionEvent(event);
496            if (DEBUG_OVERVIEW_PROXY) {
497                Log.d(TAG_OPS, "Send MotionEvent: " + event.toString());
498            }
499            return true;
500        } catch (RemoteException e) {
501            Log.e(TAG, "Callback failed", e);
502        } finally {
503            event.transform(mTransformLocalMatrix);
504        }
505        return false;
506    }
```

## NavigationBarから送出されるタッチイベントは誰でも受け取れるのか？

「ランチャーアプリによってナビゲーションバーの挙動が変わる」ってところが個人的に気になってたんだけど、サードパーティーのランチャーアプリがPixelLauncherのようにRecentAppsの画面を表示したりQuickstepをハンドリングしたりできるかというと、そういうわけではなさそう。

http://androidxref.com/9.0.0_r3/xref/frameworks/base/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java

```
257    public OverviewProxyService(Context context) {
258        mContext = context;
259        mHandler = new Handler();
260        mConnectionBackoffAttempts = 0;
261        mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
262                com.android.internal.R.string.config_recentsComponentName));
263        mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
264                .setPackage(mRecentsComponentName.getPackageName());
```


このように、端末ベンダーがxmlファイルでconfig_recentsComponentNameを決め打ちしているものしかQuickstepをさばく対象にはなりえないようだ。

WebViewみたいに、いずれサードパーティーアプリに開かれるようになるのかもしれないけど、Android 9.0時点ではそのような口はない。

　


## まとめ

* NavigationBarViewがLauncher3 (端末ベンダーがxmlファイルで変更はできる)にタッチイベントとかquick scrub, quickstep などを送出するようになった
* サードパーティーのランチャーアプリに導線が開かれているわけではない。端末ベンダーがxmlで指定したコンポーネントだけがNavigationBarViewのタッチイベントを受け取ったりRecentAppsを表示したりできる。

ちなみに、調べてる途中に見つけたのだけど、中国のスーパーハカーがこの記事に書いてないところまで結構事細かに書いている。
https://www.jianshu.com/p/83ce8731fb13