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:
TreeHugger Robot
2021-06-02 02:26:57 +00:00
committed by Automerger Merge Worker
18 changed files with 1748 additions and 34 deletions
@@ -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>
+10 -1
View File
@@ -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 */);
}
}