Androidでおなじみのナビゲーションバー
私はあいにくお金がなくてPixel 3を購入できていないんですが、巷の噂によると
これがPixel 3のナビゲーションバーは
こんなのらしい。
そして、どうやら設定でこのあたりを変えることができるらしい。
- 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/
をみるに
どうやらこの画面にスワイプアップ時の挙動を変更する設定が存在するらしい。
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" />
たしかにありました。
設定できる端末とできない端末の違いは?
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つ条件がある。
1つめは、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 )
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。
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 }
設定値が切り替わった契機で、
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付近にこんなソースを見つけた。
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に送られるっぽい?
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をハンドリングしたりできるかというと、そういうわけではなさそう。
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