Merge "Initial commit of rotation contextual button." into sc-v2-dev am: 161027dc3e
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/14726215 Change-Id: I94511bef7e039864310710283c6cbece83e9372b
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector android:name="root"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28.0"
|
||||
android:viewportHeight="28.0">
|
||||
<!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
|
||||
<group android:name="icon" android:pivotX="14" android:pivotY="14"
|
||||
android:scaleX="1">
|
||||
<!-- Tint color to be set directly -->
|
||||
<path android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
|
||||
</group>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
|
||||
<!-- Repeat all animations 5 times but don't fade out at the end -->
|
||||
<target android:name="root">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="icon">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="100"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="-90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector android:name="root"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28.0"
|
||||
android:viewportHeight="28.0">
|
||||
<!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
|
||||
<group android:name="icon" android:pivotX="14" android:pivotY="14"
|
||||
android:scaleX="1">
|
||||
<!-- Tint color to be set directly -->
|
||||
<path android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
|
||||
</group>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
|
||||
<!-- Repeat all animations 5 times but don't fade out at the end -->
|
||||
<target android:name="root">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="icon">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="100"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="0">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="0">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="0">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="0">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="0">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector android:name="root"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28.0"
|
||||
android:viewportHeight="28.0">
|
||||
<!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
|
||||
<group android:name="icon" android:pivotX="14" android:pivotY="14"
|
||||
android:scaleX="-1">
|
||||
<!-- Tint color to be set directly -->
|
||||
<path android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
|
||||
</group>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
|
||||
<!-- Repeat all animations 5 times but don't fade out at the end -->
|
||||
<target android:name="root">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="icon">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="100"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="0"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="90">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector android:name="root"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="28.0"
|
||||
android:viewportHeight="28.0">
|
||||
<!-- Use scaleX to flip icon so arrows always point in the direction of motion -->
|
||||
<group android:name="icon" android:pivotX="14" android:pivotY="14"
|
||||
android:scaleX="-1">
|
||||
<!-- Tint color to be set directly -->
|
||||
<path android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12.02,10.83L9.25,8.06l2.77,-2.77l1.12,1.12l-0.85,0.86h5.16c0.72,0 1.31,0.56 1.31,1.26v9.16l-1.58,-1.58V8.85h-4.89l0.86,0.86L12.02,10.83zM15.98,17.17l-1.12,1.12l0.85,0.86h-4.88v-7.26L9.25,10.3v9.17c0,0.7 0.59,1.26 1.31,1.26h5.16v0.01l-0.85,0.85l1.12,1.12l2.77,-2.77L15.98,17.17z"/>
|
||||
</group>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
|
||||
<!-- Repeat all animations 5 times but don't fade out at the end -->
|
||||
<target android:name="root">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
<!-- Linear fade out -->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="1700"
|
||||
android:valueFrom="1"
|
||||
android:valueTo="0"
|
||||
android:interpolator="@android:anim/linear_interpolator"/>
|
||||
<!-- Linear fade in-->
|
||||
<objectAnimator android:propertyName="alpha"
|
||||
android:duration="100"
|
||||
android:startOffset="100"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:interpolator="@android:anim/linear_interpolator" />
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
<target android:name="icon">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="sequentially">
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="100"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="180">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="180">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="180">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="180">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
|
||||
<!-- Reset rotation position for fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:startOffset="1300"
|
||||
android:duration="100"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="90"/>
|
||||
|
||||
<!-- Icon rotation with start timing offset after fade in -->
|
||||
<objectAnimator android:propertyName="rotation"
|
||||
android:duration="600"
|
||||
android:valueFrom="90"
|
||||
android:valueTo="180">
|
||||
<aapt:attr name="android:interpolator">
|
||||
<pathInterpolator android:pathData="M 0.0,0.0 c0.408,1.181 0.674,1.08 1.0,1.0"/>
|
||||
</aapt:attr>
|
||||
</objectAnimator>
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
@@ -29,7 +29,7 @@
|
||||
android:layout_gravity="bottom" >
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/system_button_layout"
|
||||
android:id="@+id/nav_button_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
|
||||
@@ -44,6 +44,15 @@
|
||||
android:forceHasOverlappingRendering="false"
|
||||
android:gravity="center" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/contextual_button_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
|
||||
android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
|
||||
android:forceHasOverlappingRendering="false"
|
||||
android:gravity="center" />
|
||||
|
||||
</com.android.launcher3.taskbar.TaskbarView>
|
||||
|
||||
<com.android.launcher3.taskbar.ImeBarView
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.widget.ImageView;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
import com.android.launcher3.taskbar.contextual.RotationContextButton;
|
||||
|
||||
/**
|
||||
* Creates Buttons for Taskbar for 3 button nav.
|
||||
@@ -68,6 +69,11 @@ public class ButtonProvider {
|
||||
return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
|
||||
}
|
||||
|
||||
public RotationContextButton getContextualRotation() {
|
||||
// Rotation suggestion button
|
||||
return new RotationContextButton(mContext);
|
||||
}
|
||||
|
||||
private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
|
||||
ImageView buttonView = new ImageView(mContext);
|
||||
buttonView.setImageResource(drawableId);
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
public class ImeBarView extends RelativeLayout {
|
||||
|
||||
private ButtonProvider mButtonProvider;
|
||||
private View mImeView;
|
||||
|
||||
public ImeBarView(Context context) {
|
||||
@@ -41,8 +40,8 @@ public class ImeBarView extends RelativeLayout {
|
||||
}
|
||||
|
||||
public void init(ButtonProvider buttonProvider) {
|
||||
mButtonProvider = buttonProvider;
|
||||
|
||||
// TODO (b/187966005), maybe need to replace ime switcher button with
|
||||
// RotationContextButton when device rotates
|
||||
ActivityContext context = getActivityContext();
|
||||
RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
|
||||
context.getDeviceProfile().iconSizePx,
|
||||
@@ -56,13 +55,13 @@ public class ImeBarView extends RelativeLayout {
|
||||
downParams.addRule(ALIGN_PARENT_START);
|
||||
|
||||
// Down Arrow
|
||||
View downView = mButtonProvider.getDown();
|
||||
View downView = buttonProvider.getDown();
|
||||
downView.setLayoutParams(downParams);
|
||||
downView.setRotation(-90);
|
||||
addView(downView);
|
||||
|
||||
// IME switcher button
|
||||
mImeView = mButtonProvider.getImeSwitcher();
|
||||
mImeView = buttonProvider.getImeSwitcher();
|
||||
mImeView.setLayoutParams(imeParams);
|
||||
addView(mImeView);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
|
||||
|
||||
/**
|
||||
* A data source which integrates with a Launcher instance
|
||||
* TODO: Rename to have Launcher prefix
|
||||
@@ -51,6 +50,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
private @Nullable Animator mAnimator;
|
||||
private boolean mIsAnimatingToLauncher;
|
||||
private ContextualRotationNotifier mContextualRotationNotifier;
|
||||
|
||||
public LauncherTaskbarUIController(
|
||||
BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
|
||||
@@ -67,7 +67,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate() {
|
||||
protected void onCreate(ContextualRotationNotifier notifier) {
|
||||
mContextualRotationNotifier = notifier;
|
||||
mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
|
||||
mTaskbarAnimationController.init();
|
||||
mHotseatController.init();
|
||||
@@ -82,6 +83,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
// End this first, in case it relies on properties that are about to be cleaned up.
|
||||
mAnimator.end();
|
||||
}
|
||||
mContextualRotationNotifier = null;
|
||||
mTaskbarStateHandler.setAnimationController(null);
|
||||
mTaskbarAnimationController.cleanup();
|
||||
mHotseatController.cleanup();
|
||||
@@ -105,6 +107,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
@Override
|
||||
public void updateTaskbarVisibilityAlpha(float alpha) {
|
||||
mTaskbarView.setAlpha(alpha);
|
||||
if (mContextualRotationNotifier != null) {
|
||||
mContextualRotationNotifier.onTaskbarVisibilityChanged(alpha == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -272,4 +277,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
void updateTaskbarScale(float scale);
|
||||
void updateTaskbarTranslationY(float translationY);
|
||||
}
|
||||
|
||||
public interface ContextualRotationNotifier {
|
||||
void onTaskbarVisibilityChanged(boolean showing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
|
||||
import com.android.launcher3.taskbar.contextual.RotationButtonController;
|
||||
import com.android.launcher3.touch.ItemClickHandler;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.Themes;
|
||||
@@ -88,7 +89,9 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
private int mLastRequestedNonFullscreenHeight;
|
||||
|
||||
private final SysUINavigationMode.Mode mNavMode;
|
||||
private final SystemTaskbarNotificationManager mSystemTaskbarNotificationManager;
|
||||
private final TaskbarNavButtonController mNavButtonController;
|
||||
private final RotationButtonController mRotationButtonController;
|
||||
|
||||
private final boolean mIsSafeModeEnabled;
|
||||
|
||||
@@ -98,12 +101,43 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
private final View.OnClickListener mOnTaskbarIconClickListener;
|
||||
private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
|
||||
|
||||
private final TaskbarManager.SystemTaskbarNotifier mSystemTaskbarNotifier =
|
||||
new TaskbarManager.SystemTaskbarNotifier() {
|
||||
@Override
|
||||
public void updateImeStatus(int displayId, int vis, int backDisposition,
|
||||
boolean showImeSwitcher) {
|
||||
/*
|
||||
* When in 3 button nav, sysui flags don't get called since we prevent
|
||||
* sysui nav bar from instantiating at all, which is what's responsible for
|
||||
* sending sysui state flags over.
|
||||
*/
|
||||
mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
mRotationButtonController.onRotationProposal(rotation, isValid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(int displayId, int state1, int state2, boolean animate) {
|
||||
mRotationButtonController.onDisable2FlagChanged(state2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemBarAttributesChanged(int displayId, int behavior) {
|
||||
mRotationButtonController.onBehaviorChanged(displayId, behavior);
|
||||
}
|
||||
};
|
||||
|
||||
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
|
||||
TaskbarNavButtonController buttonController) {
|
||||
TaskbarNavButtonController buttonController,
|
||||
SystemTaskbarNotificationManager systemTaskbarNotificationManager) {
|
||||
super(windowContext, Themes.getActivityThemeRes(windowContext));
|
||||
mDeviceProfile = dp;
|
||||
mNavButtonController = buttonController;
|
||||
mNavMode = SysUINavigationMode.getMode(windowContext);
|
||||
mSystemTaskbarNotificationManager = systemTaskbarNotificationManager;
|
||||
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
|
||||
() -> getPackageManager().isSafeMode());
|
||||
|
||||
@@ -118,7 +152,11 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
|
||||
mDragLayer = (TaskbarDragLayer) mLayoutInflater
|
||||
.inflate(R.layout.taskbar, null, false);
|
||||
mIconController = new TaskbarIconController(this, mDragLayer);
|
||||
|
||||
mRotationButtonController = new RotationButtonController(this,
|
||||
R.color.popup_color_primary_light, R.color.popup_color_primary_light);
|
||||
mIconController = new TaskbarIconController(this, mDragLayer,
|
||||
mRotationButtonController);
|
||||
|
||||
Display display = windowContext.getDisplay();
|
||||
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
|
||||
@@ -149,8 +187,13 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
|
||||
);
|
||||
|
||||
mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
|
||||
mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener,
|
||||
mNavMode);
|
||||
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
|
||||
if (mNavMode == Mode.THREE_BUTTONS) {
|
||||
mSystemTaskbarNotificationManager
|
||||
.registerSystemTaskbarNotifications(mSystemTaskbarNotifier);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canShowNavButtons() {
|
||||
@@ -189,7 +232,10 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
mUIController.onDestroy();
|
||||
mUIController = uiController;
|
||||
mIconController.setUIController(mUIController);
|
||||
mUIController.onCreate();
|
||||
mUIController.onCreate(mRotationButtonController::onTaskBarVisibilityChange);
|
||||
if (mNavMode == Mode.THREE_BUTTONS) {
|
||||
mRotationButtonController.init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,6 +245,11 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
setUIController(TaskbarUIController.DEFAULT);
|
||||
mIconController.onDestroy();
|
||||
mWindowManager.removeViewImmediate(mDragLayer);
|
||||
if (mNavMode == Mode.THREE_BUTTONS) {
|
||||
mSystemTaskbarNotificationManager.removeSystemTaskbarNotifications(
|
||||
mSystemTaskbarNotifier);
|
||||
mRotationButtonController.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
void onNavigationButtonClick(@TaskbarButton int buttonType) {
|
||||
@@ -212,16 +263,6 @@ public class TaskbarActivityContext extends ContextThemeWrapper implements Activ
|
||||
mIconController.setImeIsVisible(isImeVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
|
||||
* instantiating at all, which is what's responsible for sending sysui state flags over.
|
||||
*
|
||||
* @param vis IME visibility flag
|
||||
*/
|
||||
public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
|
||||
mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,8 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AlphaUpdateListener;
|
||||
import com.android.launcher3.taskbar.contextual.RotationButtonController;
|
||||
import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
|
||||
|
||||
/**
|
||||
@@ -45,18 +47,22 @@ public class TaskbarIconController {
|
||||
|
||||
private final TaskbarView mTaskbarView;
|
||||
private final ImeBarView mImeBarView;
|
||||
private final RotationButtonController mRotationButtonController;
|
||||
|
||||
@NonNull
|
||||
private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
|
||||
|
||||
TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
|
||||
TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer,
|
||||
RotationButtonController rotationButtonController) {
|
||||
mActivity = activity;
|
||||
mDragLayer = dragLayer;
|
||||
mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
|
||||
mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
|
||||
mRotationButtonController = rotationButtonController;
|
||||
}
|
||||
|
||||
public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
|
||||
public void init(OnClickListener clickListener, OnLongClickListener longClickListener,
|
||||
SysUINavigationMode.Mode navMode) {
|
||||
mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
|
||||
mUIController.alignRealHotseatWithTaskbar());
|
||||
|
||||
@@ -67,6 +73,9 @@ public class TaskbarIconController {
|
||||
mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
|
||||
|
||||
mDragLayer.init(new TaskbarDragLayerCallbacks(), mTaskbarView);
|
||||
if (navMode == SysUINavigationMode.Mode.THREE_BUTTONS) {
|
||||
mRotationButtonController.setRotationButton(mTaskbarView.getContextualRotationButton());
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
@@ -127,6 +136,12 @@ public class TaskbarIconController {
|
||||
mTaskbarView.mSystemButtonContainer, mTempRect);
|
||||
insetsInfo.touchableRegion.set(mTempRect);
|
||||
}
|
||||
if (mTaskbarView.mContextualButtonContainer.getVisibility() == VISIBLE) {
|
||||
mDragLayer.getDescendantRectRelativeToSelf(
|
||||
mTaskbarView.mContextualButtonContainer, mTempRect);
|
||||
insetsInfo.touchableRegion.union(mTempRect);
|
||||
}
|
||||
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,11 +40,14 @@ import com.android.quickstep.SysUINavigationMode;
|
||||
import com.android.quickstep.SysUINavigationMode.Mode;
|
||||
import com.android.quickstep.TouchInteractionService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class to manager taskbar lifecycle
|
||||
* Class to manage taskbar lifecycle
|
||||
*/
|
||||
public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
|
||||
SysUINavigationMode.NavigationModeChangeListener {
|
||||
SysUINavigationMode.NavigationModeChangeListener, SystemTaskbarNotificationManager {
|
||||
|
||||
private final Context mContext;
|
||||
private final DisplayController mDisplayController;
|
||||
@@ -59,6 +62,8 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
|
||||
private boolean mUserUnlocked = false;
|
||||
|
||||
private List<SystemTaskbarNotifier> mSystemTaskbarNotifiers = new ArrayList<>();
|
||||
|
||||
public TaskbarManager(TouchInteractionService service) {
|
||||
mDisplayController = DisplayController.INSTANCE.get(service);
|
||||
mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service);
|
||||
@@ -124,7 +129,7 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
return;
|
||||
}
|
||||
mTaskbarActivityContext = new TaskbarActivityContext(
|
||||
mContext, dp.copy(mContext), mNavButtonController);
|
||||
mContext, dp.copy(mContext), mNavButtonController, this);
|
||||
mTaskbarActivityContext.init();
|
||||
if (mLauncher != null) {
|
||||
mTaskbarActivityContext.setUIController(
|
||||
@@ -132,6 +137,9 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - I don't think this is the best place for these pass through methods,
|
||||
// maybe directly in TaskbarIconController?
|
||||
|
||||
/**
|
||||
* See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
|
||||
* @param systemUiStateFlags The latest SystemUiStateFlags
|
||||
@@ -143,6 +151,16 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
}
|
||||
}
|
||||
|
||||
public void registerSystemTaskbarNotifications(SystemTaskbarNotifier notifier) {
|
||||
if (!mSystemTaskbarNotifiers.contains(notifier)) {
|
||||
mSystemTaskbarNotifiers.add(notifier);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSystemTaskbarNotifications(SystemTaskbarNotifier notifier) {
|
||||
mSystemTaskbarNotifiers.remove(notifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
|
||||
* instantiating at all, which is what's responsible for sending sysui state flags over.
|
||||
@@ -153,8 +171,26 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
*/
|
||||
public void updateImeStatus(int displayId, int vis, int backDisposition,
|
||||
boolean showImeSwitcher) {
|
||||
if (mTaskbarActivityContext != null) {
|
||||
mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher);
|
||||
for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
|
||||
notifier.updateImeStatus(displayId, vis, backDisposition, showImeSwitcher);
|
||||
}
|
||||
}
|
||||
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
|
||||
notifier.onRotationProposal(rotation, isValid);
|
||||
}
|
||||
}
|
||||
|
||||
public void disable(int displayId, int state1, int state2, boolean animate) {
|
||||
for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
|
||||
notifier.disable(displayId, state1, state2, animate);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSystemBarAttributesChanged(int displayId, int behavior) {
|
||||
for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
|
||||
notifier.onSystemBarAttributesChanged(displayId, behavior);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,4 +202,18 @@ public class TaskbarManager implements DisplayController.DisplayInfoChangeListen
|
||||
mDisplayController.removeChangeListener(this);
|
||||
mSysUINavigationMode.removeModeChangeListener(this);
|
||||
}
|
||||
|
||||
public interface SystemTaskbarNotifier {
|
||||
void updateImeStatus(int displayId, int vis, int backDisposition,
|
||||
boolean showImeSwitcher);
|
||||
void onRotationProposal(int rotation, boolean isValid);
|
||||
void disable(int displayId, int state1, int state2, boolean animate);
|
||||
void onSystemBarAttributesChanged(int displayId, int behavior);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface SystemTaskbarNotificationManager {
|
||||
void registerSystemTaskbarNotifications(TaskbarManager.SystemTaskbarNotifier notifier);
|
||||
void removeSystemTaskbarNotifications(TaskbarManager.SystemTaskbarNotifier notifier);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class TaskbarUIController {
|
||||
*/
|
||||
public void alignRealHotseatWithTaskbar() { }
|
||||
|
||||
protected void onCreate() { }
|
||||
protected void onCreate(LauncherTaskbarUIController.ContextualRotationNotifier notifier) { }
|
||||
|
||||
protected void onDestroy() { }
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.taskbar.contextual.RotationContextButton;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
/**
|
||||
@@ -67,6 +68,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
|
||||
LinearLayout mSystemButtonContainer;
|
||||
LinearLayout mHotseatIconsContainer;
|
||||
LinearLayout mContextualButtonContainer;
|
||||
|
||||
// Delegate touches to the closest view if within mIconTouchSize.
|
||||
private boolean mDelegateTargeted;
|
||||
@@ -79,6 +81,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
|
||||
/** Provider of buttons added to taskbar in 3 button nav */
|
||||
private ButtonProvider mButtonProvider;
|
||||
private RotationContextButton mContextualRotationButton;
|
||||
|
||||
private boolean mDisableRelayout;
|
||||
private boolean mAreHolesAllowed;
|
||||
@@ -112,8 +115,9 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mSystemButtonContainer = findViewById(R.id.system_button_layout);
|
||||
mSystemButtonContainer = findViewById(R.id.nav_button_layout);
|
||||
mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
|
||||
mContextualButtonContainer = findViewById(R.id.contextual_button_layout);
|
||||
}
|
||||
|
||||
protected void init(TaskbarIconController.TaskbarViewCallbacks callbacks,
|
||||
@@ -132,6 +136,10 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
|
||||
int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
|
||||
updateHotseatItems(new ItemInfo[numHotseatIcons]);
|
||||
|
||||
if (mActivityContext.canShowNavButtons()) {
|
||||
createContextualRegion();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,6 +386,16 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa
|
||||
}
|
||||
}
|
||||
|
||||
private void createContextualRegion() {
|
||||
mContextualRotationButton = mButtonProvider.getContextualRotation();
|
||||
mContextualRotationButton.setVisibility(GONE);
|
||||
mContextualButtonContainer.addView(mContextualRotationButton);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RotationContextButton getContextualRotationButton() {
|
||||
return mContextualRotationButton;
|
||||
}
|
||||
// FolderIconParent implemented methods.
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.taskbar.contextual;
|
||||
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* Interface of a rotation button that interacts {@link RotationButtonController}.
|
||||
* This interface exists because of the two different styles of rotation button in Sysui,
|
||||
* one in contextual for 3 button nav and a floating rotation button for gestural.
|
||||
* Keeping the interface for eventual migration of floating button, so some methods are
|
||||
* pass through to "super" while others are trivially implemented.
|
||||
*
|
||||
* Changes:
|
||||
* * Directly use AnimatedVectorDrawable instead of KeyButtonDrawable
|
||||
*/
|
||||
public interface RotationButton {
|
||||
void setRotationButtonController(RotationButtonController rotationButtonController);
|
||||
View getCurrentView();
|
||||
boolean show();
|
||||
boolean hide();
|
||||
boolean isVisible();
|
||||
void updateIcon(int lightIconColor, int darkIconColor);
|
||||
void setOnClickListener(View.OnClickListener onClickListener);
|
||||
void setOnHoverListener(View.OnHoverListener onHoverListener);
|
||||
AnimatedVectorDrawable getImageDrawable();
|
||||
void setDarkIntensity(float darkIntensity);
|
||||
default boolean acceptRotationProposal() {
|
||||
return getCurrentView() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.taskbar.contextual;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
|
||||
import static com.android.launcher3.anim.Interpolators.LINEAR;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.DrawableRes;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.StatusBarManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.IRotationWatcher;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import com.android.internal.logging.UiEvent;
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.internal.logging.UiEventLoggerImpl;
|
||||
import com.android.internal.view.RotationPolicy;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.systemui.shared.recents.utilities.Utilities;
|
||||
import com.android.systemui.shared.recents.utilities.ViewRippler;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Copied over from the SysUI equivalent class. Known issues/things not ported over
|
||||
* * When rotation button visible and in auto-hide mode, we ask auto-hide controller to
|
||||
* keep the navbar around longer. Will need to implement if we use auto-hide on taskbar
|
||||
*
|
||||
* Contains logic that deals with showing a rotate suggestion button with animation.
|
||||
*/
|
||||
public class RotationButtonController {
|
||||
|
||||
private static final String TAG = "StatusBar/RotationButtonController";
|
||||
private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
|
||||
private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
|
||||
|
||||
private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
|
||||
|
||||
private final Context mContext;
|
||||
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
|
||||
private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
|
||||
private final ViewRippler mViewRippler = new ViewRippler();
|
||||
private final DisplayController mDisplayController;
|
||||
private RotationButton mRotationButton;
|
||||
|
||||
private int mLastRotationSuggestion;
|
||||
private boolean mPendingRotationSuggestion;
|
||||
private boolean mHoveringRotationSuggestion;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final TaskStackListenerImpl mTaskStackListener;
|
||||
private boolean mListenersRegistered = false;
|
||||
private boolean mIsTaskbarShowing;
|
||||
@SuppressLint("InlinedApi")
|
||||
private @WindowInsetsController.Behavior
|
||||
int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
|
||||
private boolean mSkipOverrideUserLockPrefsOnce;
|
||||
private final int mLightIconColor;
|
||||
private final int mDarkIconColor;
|
||||
private int mIconResId = R.drawable.ic_sysbar_rotate_button_ccw_start_90;
|
||||
|
||||
private final Runnable mRemoveRotationProposal =
|
||||
() -> setRotateSuggestionButtonState(false /* visible */);
|
||||
private final Runnable mCancelPendingRotationProposal =
|
||||
() -> mPendingRotationSuggestion = false;
|
||||
private Animator mRotateHideAnimator;
|
||||
|
||||
|
||||
private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
|
||||
@Override
|
||||
public void onRotationChanged(final int rotation) throws RemoteException {
|
||||
// We need this to be scheduled as early as possible to beat the redrawing of
|
||||
// window in response to the orientation change.
|
||||
mMainThreadHandler.postAtFrontOfQueue(() -> {
|
||||
// If the screen rotation changes while locked, potentially update lock to flow with
|
||||
// new screen rotation and hide any showing suggestions.
|
||||
if (isRotationLocked()) {
|
||||
if (shouldOverrideUserLockPrefs(rotation)) {
|
||||
setRotationLockedAtAngle(rotation);
|
||||
}
|
||||
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if rotation suggestions disabled2 flag exists in flag
|
||||
* @param disable2Flags see if rotation suggestion flag exists in this flag
|
||||
* @return whether flag exists
|
||||
*/
|
||||
static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
|
||||
return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
|
||||
}
|
||||
|
||||
public RotationButtonController(Context context, @ColorInt int lightIconColor,
|
||||
@ColorInt int darkIconColor) {
|
||||
mContext = context;
|
||||
mLightIconColor = lightIconColor;
|
||||
mDarkIconColor = darkIconColor;
|
||||
|
||||
mAccessibilityManager = AccessibilityManager.getInstance(context);
|
||||
mTaskStackListener = new TaskStackListenerImpl();
|
||||
mDisplayController = DisplayController.INSTANCE.getNoCreate();
|
||||
}
|
||||
|
||||
public void setRotationButton(RotationButton rotationButton) {
|
||||
mRotationButton = rotationButton;
|
||||
mRotationButton.setRotationButtonController(this);
|
||||
mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
|
||||
mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
|
||||
}
|
||||
|
||||
public void init() {
|
||||
registerListeners();
|
||||
if (mDisplayController.getInfo().id != DEFAULT_DISPLAY) {
|
||||
// Currently there is no accelerometer sensor on non-default display, disable fixed
|
||||
// rotation for non-default display
|
||||
onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
unregisterListeners();
|
||||
}
|
||||
|
||||
private void registerListeners() {
|
||||
if (mListenersRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
mListenersRegistered = true;
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.watchRotation(mRotationWatcher, mDisplayController.getInfo().id);
|
||||
} catch (IllegalArgumentException e) {
|
||||
mListenersRegistered = false;
|
||||
Log.w(TAG, "RegisterListeners for the display failed");
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RegisterListeners caught a RemoteException", e);
|
||||
return;
|
||||
}
|
||||
|
||||
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
|
||||
}
|
||||
|
||||
void unregisterListeners() {
|
||||
if (!mListenersRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
mListenersRegistered = false;
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
|
||||
return;
|
||||
}
|
||||
|
||||
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
|
||||
}
|
||||
|
||||
void setRotationLockedAtAngle(int rotationSuggestion) {
|
||||
RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
|
||||
}
|
||||
|
||||
public boolean isRotationLocked() {
|
||||
return RotationPolicy.isRotationLocked(mContext);
|
||||
}
|
||||
|
||||
public void setRotateSuggestionButtonState(boolean visible) {
|
||||
setRotateSuggestionButtonState(visible, false /* force */);
|
||||
}
|
||||
|
||||
void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
|
||||
// At any point the button can become invisible because an a11y service became active.
|
||||
// Similarly, a call to make the button visible may be rejected because an a11y service is
|
||||
// active. Must account for this.
|
||||
// Rerun a show animation to indicate change but don't rerun a hide animation
|
||||
if (!visible && !mRotationButton.isVisible()) return;
|
||||
|
||||
final View view = mRotationButton.getCurrentView();
|
||||
if (view == null) return;
|
||||
|
||||
final AnimatedVectorDrawable currentDrawable = mRotationButton.getImageDrawable();
|
||||
if (currentDrawable == null) return;
|
||||
|
||||
// Clear any pending suggestion flag as it has either been nullified or is being shown
|
||||
mPendingRotationSuggestion = false;
|
||||
mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
|
||||
|
||||
// Handle the visibility change and animation
|
||||
if (visible) { // Appear and change (cannot force)
|
||||
// Stop and clear any currently running hide animations
|
||||
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
|
||||
mRotateHideAnimator.cancel();
|
||||
}
|
||||
mRotateHideAnimator = null;
|
||||
|
||||
// Reset the alpha if any has changed due to hide animation
|
||||
view.setAlpha(1f);
|
||||
|
||||
// Run the rotate icon's animation if it has one
|
||||
currentDrawable.reset();
|
||||
currentDrawable.start();
|
||||
|
||||
// TODO(b/187754252): No idea why this doesn't work. If we remove the "false"
|
||||
// we see the animation show the pressed state... but it only shows the first time.
|
||||
if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
|
||||
|
||||
// Set visibility unless a11y service is active.
|
||||
mRotationButton.show();
|
||||
} else { // Hide
|
||||
mViewRippler.stop(); // Prevent any pending ripples, force hide or not
|
||||
|
||||
if (force) {
|
||||
// If a hide animator is running stop it and make invisible
|
||||
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
|
||||
mRotateHideAnimator.pause();
|
||||
}
|
||||
mRotationButton.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't start any new hide animations if one is running
|
||||
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
|
||||
|
||||
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
|
||||
fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
|
||||
fadeOut.setInterpolator(LINEAR);
|
||||
fadeOut.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mRotationButton.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mRotateHideAnimator = fadeOut;
|
||||
fadeOut.start();
|
||||
}
|
||||
}
|
||||
|
||||
void setDarkIntensity(float darkIntensity) {
|
||||
mRotationButton.setDarkIntensity(darkIntensity);
|
||||
}
|
||||
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
int windowRotation = mDisplayController.getInfo().rotation;
|
||||
|
||||
if (!mRotationButton.acceptRotationProposal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This method will be called on rotation suggestion changes even if the proposed rotation
|
||||
// is not valid for the top app. Use invalid rotation choices as a signal to remove the
|
||||
// rotate button if shown.
|
||||
if (!isValid) {
|
||||
setRotateSuggestionButtonState(false /* visible */);
|
||||
return;
|
||||
}
|
||||
|
||||
// If window rotation matches suggested rotation, remove any current suggestions
|
||||
if (rotation == windowRotation) {
|
||||
mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
|
||||
setRotateSuggestionButtonState(false /* visible */);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare to show the navbar icon by updating the icon style to change anim params
|
||||
mLastRotationSuggestion = rotation; // Remember rotation for click
|
||||
final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
|
||||
if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
|
||||
mIconResId = rotationCCW
|
||||
? R.drawable.ic_sysbar_rotate_button_ccw_start_90
|
||||
: R.drawable.ic_sysbar_rotate_button_cw_start_90;
|
||||
} else { // 90 or 270
|
||||
mIconResId = rotationCCW
|
||||
? R.drawable.ic_sysbar_rotate_button_ccw_start_0
|
||||
: R.drawable.ic_sysbar_rotate_button_ccw_start_0;
|
||||
}
|
||||
mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
|
||||
|
||||
if (canShowRotationButton()) {
|
||||
// The navbar is visible / it's in visual immersive mode, so show the icon right away
|
||||
showAndLogRotationSuggestion();
|
||||
} else {
|
||||
// If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
|
||||
// visible given some time limit.
|
||||
mPendingRotationSuggestion = true;
|
||||
mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
|
||||
mMainThreadHandler.postDelayed(mCancelPendingRotationProposal,
|
||||
NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisable2FlagChanged(int state2) {
|
||||
final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
|
||||
if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
|
||||
}
|
||||
|
||||
public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
|
||||
if (mDisplayController.getInfo().id != displayId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBehavior != behavior) {
|
||||
mBehavior = behavior;
|
||||
showPendingRotationButtonIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTaskBarVisibilityChange(boolean showing) {
|
||||
if (mIsTaskbarShowing != showing) {
|
||||
mIsTaskbarShowing = showing;
|
||||
showPendingRotationButtonIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void showPendingRotationButtonIfNeeded() {
|
||||
if (canShowRotationButton() && mPendingRotationSuggestion) {
|
||||
showAndLogRotationSuggestion();
|
||||
}
|
||||
}
|
||||
|
||||
/** Return true when either the task bar is visible or it's in visual immersive mode. */
|
||||
@SuppressLint("InlinedApi")
|
||||
private boolean canShowRotationButton() {
|
||||
return mIsTaskbarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
|
||||
}
|
||||
|
||||
public @DrawableRes
|
||||
int getIconResId() {
|
||||
return mIconResId;
|
||||
}
|
||||
|
||||
public @ColorInt int getLightIconColor() {
|
||||
return mLightIconColor;
|
||||
}
|
||||
|
||||
public @ColorInt int getDarkIconColor() {
|
||||
return mDarkIconColor;
|
||||
}
|
||||
|
||||
private void onRotateSuggestionClick(View v) {
|
||||
mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
|
||||
incrementNumAcceptedRotationSuggestionsIfNeeded();
|
||||
setRotationLockedAtAngle(mLastRotationSuggestion);
|
||||
}
|
||||
|
||||
private boolean onRotateSuggestionHover(View v, MotionEvent event) {
|
||||
final int action = event.getActionMasked();
|
||||
mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
|
||||
|| (action == MotionEvent.ACTION_HOVER_MOVE);
|
||||
rescheduleRotationTimeout(true /* reasonHover */);
|
||||
return false; // Must return false so a11y hover events are dispatched correctly.
|
||||
}
|
||||
|
||||
private void onRotationSuggestionsDisabled() {
|
||||
// Immediately hide the rotate button and clear any planned removal
|
||||
setRotateSuggestionButtonState(false /* visible */, true /* force */);
|
||||
mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
|
||||
}
|
||||
|
||||
private void showAndLogRotationSuggestion() {
|
||||
setRotateSuggestionButtonState(true /* visible */);
|
||||
rescheduleRotationTimeout(false /* reasonHover */);
|
||||
mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes {@link #shouldOverrideUserLockPrefs} always return {@code false} once. It is used to
|
||||
* avoid losing original user rotation when display rotation is changed by entering the fixed
|
||||
* orientation overview.
|
||||
*/
|
||||
void setSkipOverrideUserLockPrefsOnce() {
|
||||
mSkipOverrideUserLockPrefsOnce = true;
|
||||
}
|
||||
|
||||
private boolean shouldOverrideUserLockPrefs(final int rotation) {
|
||||
if (mSkipOverrideUserLockPrefsOnce) {
|
||||
mSkipOverrideUserLockPrefsOnce = false;
|
||||
return false;
|
||||
}
|
||||
// Only override user prefs when returning to the natural rotation (normally portrait).
|
||||
// Don't let apps that force landscape or 180 alter user lock.
|
||||
return rotation == NATURAL_ROTATION;
|
||||
}
|
||||
|
||||
private void rescheduleRotationTimeout(final boolean reasonHover) {
|
||||
// May be called due to a new rotation proposal or a change in hover state
|
||||
if (reasonHover) {
|
||||
// Don't reschedule if a hide animator is running
|
||||
if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
|
||||
// Don't reschedule if not visible
|
||||
if (!mRotationButton.isVisible()) return;
|
||||
}
|
||||
|
||||
// Stop any pending removal
|
||||
mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
|
||||
// Schedule timeout
|
||||
mMainThreadHandler.postDelayed(mRemoveRotationProposal,
|
||||
computeRotationProposalTimeout());
|
||||
}
|
||||
|
||||
private int computeRotationProposalTimeout() {
|
||||
return mAccessibilityManager.getRecommendedTimeoutMillis(
|
||||
mHoveringRotationSuggestion ? 16000 : 5000,
|
||||
AccessibilityManager.FLAG_CONTENT_CONTROLS);
|
||||
}
|
||||
|
||||
private boolean isRotateSuggestionIntroduced() {
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
|
||||
>= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
|
||||
}
|
||||
|
||||
private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
|
||||
// Get the number of accepted suggestions
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
final int numSuggestions = Settings.Secure.getInt(cr,
|
||||
Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
|
||||
|
||||
// Increment the number of accepted suggestions only if it would change intro mode
|
||||
if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
|
||||
Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
|
||||
numSuggestions + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private class TaskStackListenerImpl extends TaskStackChangeListener {
|
||||
// Invalidate any rotation suggestion on task change or activity orientation change
|
||||
// Note: all callbacks happen on main thread
|
||||
|
||||
@Override
|
||||
public void onTaskStackChanged() {
|
||||
setRotateSuggestionButtonState(false /* visible */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(int taskId) {
|
||||
setRotateSuggestionButtonState(false /* visible */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskMovedToFront(int taskId) {
|
||||
setRotateSuggestionButtonState(false /* visible */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
|
||||
// Only hide the icon if the top task changes its requestedOrientation
|
||||
// Launcher can alter its requestedOrientation while it's not on top, don't hide on this
|
||||
Optional.ofNullable(ActivityManagerWrapper.getInstance())
|
||||
.map(ActivityManagerWrapper::getRunningTask)
|
||||
.ifPresent(a -> {
|
||||
if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
|
||||
@UiEvent(doc = "The rotation button was shown")
|
||||
ROTATION_SUGGESTION_SHOWN(206),
|
||||
@UiEvent(doc = "The rotation button was clicked")
|
||||
ROTATION_SUGGESTION_ACCEPTED(207);
|
||||
|
||||
private final int mId;
|
||||
RotationButtonEvent(int id) {
|
||||
mId = id;
|
||||
}
|
||||
@Override public int getId() {
|
||||
return mId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.taskbar.contextual;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
|
||||
/** Containing logic for the rotation button in nav bar. */
|
||||
public class RotationContextButton extends ImageView implements RotationButton {
|
||||
|
||||
private AnimatedVectorDrawable mImageDrawable;
|
||||
|
||||
public RotationContextButton(Context context) {
|
||||
super(context);
|
||||
setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRotationButtonController(RotationButtonController rotationButtonController) {
|
||||
// TODO(b/187754252) UI polish, different icons based on light/dark context, etc
|
||||
mImageDrawable = (AnimatedVectorDrawable) getContext()
|
||||
.getDrawable(rotationButtonController.getIconResId());
|
||||
setImageDrawable(mImageDrawable);
|
||||
mImageDrawable.setCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getCurrentView() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean show() {
|
||||
setVisibility(VISIBLE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hide() {
|
||||
setVisibility(GONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIcon(int lightIconColor, int darkIconColor) {
|
||||
// TODO(b/187754252): UI Polish
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(View.OnClickListener onClickListener) {
|
||||
super.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnHoverListener(View.OnHoverListener onHoverListener) {
|
||||
super.setOnHoverListener(onHoverListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatedVectorDrawable getImageDrawable() {
|
||||
return mImageDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDarkIntensity(float darkIntensity) {
|
||||
// TODO(b/187754252) UI polish
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
super.setVisibility(visibility);
|
||||
|
||||
if (visibility != View.VISIBLE && mImageDrawable != null) {
|
||||
mImageDrawable.clearAnimationCallbacks();
|
||||
mImageDrawable.reset();
|
||||
}
|
||||
|
||||
// Start the rotation animation once it becomes visible
|
||||
if (visibility == View.VISIBLE && mImageDrawable != null) {
|
||||
mImageDrawable.reset();
|
||||
mImageDrawable.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptRotationProposal() {
|
||||
return isAttachedToWindow();
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) {
|
||||
public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) {
|
||||
WindowBounds wb = new WindowBounds(bounds, insets);
|
||||
MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
|
||||
}
|
||||
@@ -269,8 +269,34 @@ public class TouchInteractionService extends Service implements PluginListener<O
|
||||
@Override
|
||||
public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
|
||||
int backDisposition, boolean showImeSwitcher) {
|
||||
MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus(
|
||||
displayId, vis, backDisposition, showImeSwitcher));
|
||||
executeForTaskbarManager(() -> mTaskbarManager
|
||||
.updateImeStatus(displayId, vis, backDisposition, showImeSwitcher));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotationProposal(int rotation, boolean isValid) {
|
||||
executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable(int displayId, int state1, int state2, boolean animate) {
|
||||
executeForTaskbarManager(() -> mTaskbarManager
|
||||
.disable(displayId, state1, state2, animate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemBarAttributesChanged(int displayId, int behavior) {
|
||||
executeForTaskbarManager(() -> mTaskbarManager
|
||||
.onSystemBarAttributesChanged(displayId, behavior));
|
||||
}
|
||||
|
||||
private void executeForTaskbarManager(final Runnable r) {
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
if (mTaskbarManager == null) {
|
||||
return;
|
||||
}
|
||||
r.run();
|
||||
});
|
||||
}
|
||||
|
||||
public TaskbarManager getTaskbarManager() {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.quickstep;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsetsController;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.taskbar.contextual.RotationButton;
|
||||
import com.android.launcher3.taskbar.contextual.RotationButtonController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** SysUI equivalent */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class NavigationBarRotationContextTest {
|
||||
private static final int DEFAULT_ROTATE = 0;
|
||||
private static final int DEFAULT_DISPLAY = 0;
|
||||
|
||||
|
||||
private RotationButtonController mRotationButtonController;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Context mTargetContext = InstrumentationRegistry.getTargetContext();
|
||||
final View view = new View(mTargetContext);
|
||||
RotationButton rotationButton = mock(RotationButton.class);
|
||||
mRotationButtonController = new RotationButtonController(mTargetContext, 0, 0);
|
||||
mRotationButtonController.setRotationButton(rotationButton);
|
||||
// Due to a mockito issue, only spy the object after setting the initial state
|
||||
mRotationButtonController = spy(mRotationButtonController);
|
||||
final AnimatedVectorDrawable kbd = mock(AnimatedVectorDrawable.class);
|
||||
doReturn(view).when(rotationButton).getCurrentView();
|
||||
doReturn(true).when(rotationButton).acceptRotationProposal();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInvalidRotationProposal() {
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE + 1,
|
||||
false /* isValid */);
|
||||
verify(mRotationButtonController, times(1))
|
||||
.setRotateSuggestionButtonState(false /* visible */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSameRotationProposal() {
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE,
|
||||
true /* isValid */);
|
||||
verify(mRotationButtonController, times(1))
|
||||
.setRotateSuggestionButtonState(false /* visible */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRotationProposalShowButtonShowNav() {
|
||||
// No navigation bar should not call to set visibility state
|
||||
mRotationButtonController.onBehaviorChanged(DEFAULT_DISPLAY,
|
||||
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
mRotationButtonController.onTaskBarVisibilityChange(false /* showing */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
false /* visible */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
|
||||
// No navigation bar with rotation change should not call to set visibility state
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE + 1,
|
||||
true /* isValid */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
false /* visible */);
|
||||
verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
|
||||
// Since rotation has changed rotation should be pending, show mButton when showing nav bar
|
||||
mRotationButtonController.onTaskBarVisibilityChange(true /* showing */);
|
||||
verify(mRotationButtonController, times(1)).setRotateSuggestionButtonState(
|
||||
true /* visible */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnRotationProposalShowButton() {
|
||||
// Navigation bar being visible should not call to set visibility state
|
||||
mRotationButtonController.onTaskBarVisibilityChange(true /* showing */);
|
||||
verify(mRotationButtonController, times(0))
|
||||
.setRotateSuggestionButtonState(false /* visible */);
|
||||
verify(mRotationButtonController, times(0))
|
||||
.setRotateSuggestionButtonState(true /* visible */);
|
||||
|
||||
// Navigation bar is visible and rotation requested
|
||||
mRotationButtonController.onRotationProposal(DEFAULT_ROTATE + 1,
|
||||
true /* isValid */);
|
||||
verify(mRotationButtonController, times(1))
|
||||
.setRotateSuggestionButtonState(true /* visible */);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user