Merge "Fixing touch events ignored in some cases" into sc-dev am: e0e64b4851
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/15362818
Change-Id: Ia2e97076a790bcd3e6c5bcbae79231bd1815f64a
diff --git a/Android.bp b/Android.bp
index 1b6ffe4..45d022f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -112,6 +112,7 @@
"androidx.preference_preference",
"androidx.slice_slice-view",
"androidx.cardview_cardview",
+ "com.google.android.material_material",
"iconloader_base",
],
manifest: "AndroidManifest-common.xml",
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index d725a16..4eecf29 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -144,7 +144,7 @@
<activity
android:name="com.android.launcher3.settings.SettingsActivity"
android:label="@string/settings_button_text"
- android:theme="@style/HomeSettingsTheme"
+ android:theme="@style/HomeSettings.Theme"
android:exported="true"
android:autoRemoveFromRecents="true">
<intent-filter>
diff --git a/lint-baseline-launcher3.xml b/lint-baseline-launcher3.xml
index 9a68405..94345a6 100644
--- a/lint-baseline-launcher3.xml
+++ b/lint-baseline-launcher3.xml
@@ -576,12 +576,12 @@
<issue
id="NewApi"
message="Call requires API level 31 (current min is 26): `android.appwidget.AppWidgetHostView#setColorResources`"
- errorLine1=" view.setColorResources(mWallpaperColorResources);"
- errorLine2=" ~~~~~~~~~~~~~~~~~">
+ errorLine1=" setColorResources(mWallpaperColorResources);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
- line="381"
- column="18"/>
+ line="528"
+ column="17"/>
</issue>
<issue
@@ -591,7 +591,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="packages/apps/Launcher3/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java"
- line="270"
+ line="288"
column="61"/>
</issue>
diff --git a/quickstep/res/drawable/ic_sysbar_accessibility_button.xml b/quickstep/res/drawable/ic_sysbar_accessibility_button.xml
new file mode 100644
index 0000000..e0d5406
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_accessibility_button.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="21dp"
+ android:height="21dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1S6.11,6.7 3.5,6L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1L20.5,6zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2s-2,0.9 -2,2S10.9,6 12,6z"
+ android:fillColor="@android:color/white"
+ />
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml
new file mode 100644
index 0000000..ff5cb9e
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_0.xml
@@ -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>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml
new file mode 100644
index 0000000..90fedb1
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_ccw_start_90.xml
@@ -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>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml
new file mode 100644
index 0000000..a89e7a3
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_0.xml
@@ -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>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml
new file mode 100644
index 0000000..0dc67b0
--- /dev/null
+++ b/quickstep/res/drawable/ic_sysbar_rotate_button_cw_start_90.xml
@@ -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>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index e680233..c0e0862 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -13,12 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
<com.android.launcher3.taskbar.TaskbarDragLayer
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/taskbar_container"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:clipChildren="false">
<com.android.launcher3.taskbar.TaskbarView
android:id="@+id/taskbar_view"
@@ -26,30 +27,52 @@
android:layout_height="wrap_content"
android:gravity="center"
android:forceHasOverlappingRendering="false"
+ android:layout_gravity="bottom"
+ android:clipChildren="false" />
+
+ <FrameLayout
+ android:id="@+id/navbuttons_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:layout_gravity="bottom" >
- <LinearLayout
- android:id="@+id/system_button_layout"
+ <FrameLayout
+ android:id="@+id/start_contextual_buttons"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
- android:forceHasOverlappingRendering="false"
- android:gravity="center" />
+ android:gravity="center_vertical"
+ android:layout_gravity="start"/>
<LinearLayout
- android:id="@+id/hotseat_icons_layout"
+ android:id="@+id/end_nav_buttons"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:forceHasOverlappingRendering="false"
- android:gravity="center" />
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
+ android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+ android:gravity="center_vertical"
+ android:layout_gravity="end"/>
- </com.android.launcher3.taskbar.TaskbarView>
+ <FrameLayout
+ android:id="@+id/end_contextual_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
+ android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+ android:gravity="center_vertical"
+ android:layout_gravity="end"/>
+ </FrameLayout>
- <com.android.launcher3.taskbar.ImeBarView
- android:id="@+id/ime_bar_view"
- android:layout_width="wrap_content"
+ <View
+ android:id="@+id/stashed_handle"
+ tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="gone"/>
+ tools:comment2="TODO: Tint dynamically"
+ android:background="?android:attr/textColorPrimary"
+ android:clipToOutline="true"
+ android:layout_gravity="bottom"/>
</com.android.launcher3.taskbar.TaskbarDragLayer>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_nav_button.xml b/quickstep/res/layout/taskbar_nav_button.xml
new file mode 100644
index 0000000..985f928
--- /dev/null
+++ b/quickstep/res/layout/taskbar_nav_button.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/taskbar_nav_buttons_size"
+ android:layout_height="@dimen/taskbar_nav_buttons_size"
+ android:background="@drawable/taskbar_icon_click_feedback_roundrect"
+ android:scaleType="center"/>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 6cc64e0..5fc969d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -16,7 +16,7 @@
<resources>
<dimen name="task_thumbnail_icon_size">48dp</dimen>
- <dimen name="task_thumbnail_icon_size_grid">32dp</dimen>
+ <dimen name="task_thumbnail_icon_size_grid">40dp</dimen>
<!-- For screens without rounded corners -->
<dimen name="task_corner_radius_small">2dp</dimen>
<!-- For Launchers that want to override the default dialog corner radius -->
@@ -39,11 +39,10 @@
<dimen name="overview_actions_horizontal_margin">16dp</dimen>
<dimen name="overview_grid_top_margin">77dp</dimen>
- <dimen name="overview_grid_bottom_margin">90dp</dimen>
+ <dimen name="overview_grid_bottom_margin">70dp</dimen>
<dimen name="overview_grid_side_margin">54dp</dimen>
<dimen name="overview_grid_row_spacing">42dp</dimen>
- <dimen name="overview_grid_focus_vertical_margin">90dp</dimen>
- <dimen name="split_placeholder_size">110dp</dimen>
+ <dimen name="overview_grid_focus_vertical_margin">40dp</dimen>
<!-- These speeds are in dp/s -->
<dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
@@ -70,6 +69,8 @@
<item name="content_scale" format="float" type="dimen">0.97</item>
<dimen name="closing_window_trans_y">115dp</dimen>
+ <dimen name="quick_switch_scaling_scroll_threshold">100dp</dimen>
+
<dimen name="recents_empty_message_text_size">16sp</dimen>
<dimen name="recents_empty_message_text_padding">16dp</dimen>
@@ -150,11 +151,12 @@
<!-- Taskbar -->
<dimen name="taskbar_size">60dp</dimen>
- <dimen name="taskbar_icon_size">44dp</dimen>
<dimen name="taskbar_icon_touch_size">48dp</dimen>
<dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
- <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
- <dimen name="taskbar_icon_spacing">8dp</dimen>
<dimen name="taskbar_folder_margin">16dp</dimen>
<dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
+ <dimen name="taskbar_nav_buttons_size">48dp</dimen>
+ <dimen name="taskbar_stashed_size">24dp</dimen>
+ <dimen name="taskbar_stashed_handle_width">220dp</dimen>
+ <dimen name="taskbar_stashed_handle_height">6dp</dimen>
</resources>
diff --git a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index 7c97b93..f82fbcc 100644
--- a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -34,26 +34,16 @@
import android.os.Process;
import android.os.UserHandle;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shadows.ShadowDeviceFlag;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherModelHelper;
-import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import org.junit.Before;
import org.junit.Test;
@@ -67,9 +57,6 @@
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.util.ReflectionHelpers;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@@ -87,7 +74,6 @@
private Context mContext;
private LauncherModelHelper mModelHelper;
private UserHandle mUserHandle;
- private InvariantDeviceProfile mTestProfile;
@Mock
private IconCache mIconCache;
@@ -103,7 +89,6 @@
mContext = RuntimeEnvironment.application;
mModelHelper = new LauncherModelHelper();
mUserHandle = Process.myUserHandle();
- mTestProfile = new InvariantDeviceProfile();
// 2 widgets, app4/provider1 & app5/provider1, have already been added to the workspace.
mModelHelper.initializeData("/widgets_predication_update_task_data.txt");
@@ -237,65 +222,5 @@
public void bindExtraContainerItems(FixedContainerItems item) {
mRecommendedWidgets = item;
}
-
- @Override
- public int getPageToBindSynchronously() {
- return 0;
- }
-
- @Override
- public void clearPendingBinds() { }
-
- @Override
- public void startBinding() { }
-
- @Override
- public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
-
- @Override
- public void bindScreens(IntArray orderedScreenIds) { }
-
- @Override
- public void finishFirstPageBind(ViewOnDrawExecutor executor) { }
-
- @Override
- public void finishBindingItems(int pageBoundFirst) { }
-
- @Override
- public void preAddApps() { }
-
- @Override
- public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
- ArrayList<ItemInfo> addAnimated) { }
-
- @Override
- public void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
-
- @Override
- public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
-
- @Override
- public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
-
- @Override
- public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
-
- @Override
- public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
-
- @Override
- public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
-
- @Override
- public void onPageBoundSynchronously(int page) { }
-
- @Override
- public void executeOnNextDraw(ViewOnDrawExecutor executor) { }
-
- @Override
- public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
-
- @Override
- public void bindAllApplications(AppInfo[] apps, int flags) { }
}
}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 32e2f1a..5250d18 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.LauncherState.NO_OFFSET;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
+import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
@@ -30,12 +31,15 @@
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.Handler;
import android.os.IBinder;
+import android.util.Log;
import android.view.View;
import android.window.SplashScreen;
@@ -56,6 +60,8 @@
import com.android.launcher3.taskbar.TaskbarStateHandler;
import com.android.launcher3.uioverrides.RecentsViewStateController;
import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ObjectWrapper;
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.RecentsModel;
@@ -85,6 +91,11 @@
public abstract class BaseQuickstepLauncher extends Launcher
implements NavigationModeChangeListener {
+ private static final long BACKOFF_MILLIS = 1000;
+
+ // Max backoff caps at 5 mins
+ private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
+
private DepthController mDepthController = new DepthController(this);
private QuickstepTransitionManager mAppTransitionManager;
@@ -104,12 +115,24 @@
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager();
mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
+ Log.d(TAG, "TIS service connected");
+ resetServiceBindRetryState();
}
@Override
public void onServiceDisconnected(ComponentName componentName) { }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ Log.w(TAG, "TIS binding died");
+ internalBindToTIS();
+ }
};
+
+ private final Runnable mConnectionRunnable = this::internalBindToTIS;
+ private short mConnectionAttempts;
private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this);
+ private final Handler mHandler = new Handler();
// Will be updated when dragging from taskbar.
private @Nullable DragOptions mNextWorkspaceDragOptions = null;
@@ -128,11 +151,11 @@
SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
-
unbindService(mTisBinderConnection);
if (mTaskbarManager != null) {
- mTaskbarManager.setLauncher(null);
+ mTaskbarManager.clearLauncher(this);
}
+ resetServiceBindRetryState();
super.onDestroy();
}
@@ -248,20 +271,43 @@
SysUINavigationMode.INSTANCE.get(this).updateMode();
mActionsView = findViewById(R.id.overview_actions_view);
- mSplitPlaceholderView = findViewById(R.id.split_placeholder);
RecentsView overviewPanel = (RecentsView) getOverviewPanel();
- mSplitPlaceholderView.init(
- new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this))
- );
- overviewPanel.init(mActionsView, mSplitPlaceholderView);
+ SplitSelectStateController controller =
+ new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+ overviewPanel.init(mActionsView, controller);
mActionsView.setDp(getDeviceProfile());
mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
mAppTransitionManager = new QuickstepTransitionManager(this);
mAppTransitionManager.registerRemoteAnimations();
- bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0);
+ internalBindToTIS();
+ }
+ /**
+ * Binds {@link #mTisBinderConnection} to {@link TouchInteractionService}. If the binding fails,
+ * attempts to retry via {@link #mConnectionRunnable}
+ */
+ private void internalBindToTIS() {
+ boolean bound = bindService(new Intent(this, TouchInteractionService.class),
+ mTisBinderConnection, 0);
+ if (bound) {
+ resetServiceBindRetryState();
+ return;
+ }
+
+ Log.w(TAG, "Retrying TIS Binder connection attempt: " + mConnectionAttempts);
+ final long timeoutMs = (long) Math.min(
+ Math.scalb(BACKOFF_MILLIS, mConnectionAttempts), MAX_BACKOFF_MILLIS);
+ mHandler.postDelayed(mConnectionRunnable, timeoutMs);
+ mConnectionAttempts++;
+ }
+
+ private void resetServiceBindRetryState() {
+ if (mHandler.hasCallbacks(mConnectionRunnable)) {
+ mHandler.removeCallbacks(mConnectionRunnable);
+ }
+ mConnectionAttempts = 0;
}
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
@@ -351,14 +397,6 @@
}
@Override
- public float getNormalTaskbarScale() {
- if (mTaskbarUIController != null) {
- return mTaskbarUIController.getTaskbarScaleOnHome();
- }
- return super.getNormalTaskbarScale();
- }
-
- @Override
public void onDragLayerHierarchyChanged() {
onLauncherStateOrFocusChanged();
}
@@ -412,8 +450,8 @@
}
@Override
- public void finishBindingItems(int pageBoundFirst) {
- super.finishBindingItems(pageBoundFirst);
+ public void finishBindingItems(IntSet pagesBoundFirst) {
+ super.finishBindingItems(pagesBoundFirst);
// Instantiate and initialize WellbeingModel now that its loading won't interfere with
// populating workspace.
// TODO: Find a better place for this
@@ -481,4 +519,15 @@
public void setHintUserWillBeActive() {
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
}
+
+ @Override
+ public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ super.onDisplayInfoChanged(context, info, flags);
+ // When changing screens with live tile active, finish the recents animation to close
+ // overview as it should be an interim state
+ if ((flags & CHANGE_ACTIVE_SCREEN) != 0 && ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ RecentsView recentsView = getOverviewPanel();
+ recentsView.finishRecentsAnimation(/* toRecents= */ true, null);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 5bb76d6..2da8a45 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -40,7 +40,6 @@
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
import static com.android.launcher3.statehandlers.DepthController.DEPTH;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
@@ -1047,7 +1046,7 @@
mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition(
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
false /* startAtFrontOfQueue */));
- mLauncherOpenTransition.addHomeOpenCheck();
+ mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName());
SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
}
}
@@ -1088,7 +1087,15 @@
}
private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
- return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == mode && target.taskInfo != null
+ // Compare component name instead of task-id because transitions will promote
+ // the target up to the root task while getTaskId returns the leaf.
+ && target.taskInfo.topActivity.equals(mLauncher.getComponentName())) {
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 14b0c5d..c7c2567 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -85,7 +85,7 @@
mSampleHotseat = findViewById(R.id.sample_prediction);
DeviceProfile grid = mActivityContext.getDeviceProfile();
- Rect padding = grid.getHotseatLayoutPadding();
+ Rect padding = grid.getHotseatLayoutPadding(getContext());
mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
mSampleHotseat.setGridSize(grid.numShownHotseatIcons, 1);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 85e5ab0..b40a1d5 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -262,10 +262,6 @@
} else {
removeOutlineDrawings();
}
-
- if (mLauncher.getTaskbarUIController() != null) {
- mLauncher.getTaskbarUIController().onHotseatUpdated();
- }
}
private void removeOutlineDrawings() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
deleted file mode 100644
index 540f748..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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;
-
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
-import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
-
-import android.annotation.DrawableRes;
-import android.view.View;
-import android.widget.ImageView;
-
-import com.android.launcher3.R;
-import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
-
-/**
- * Creates Buttons for Taskbar for 3 button nav.
- * Can add animations and state management for buttons in this class as things progress.
- */
-public class ButtonProvider {
-
- private final int mMarginLeftRight;
- private final TaskbarActivityContext mContext;
-
- public ButtonProvider(TaskbarActivityContext context) {
- mContext = context;
- mMarginLeftRight = context.getResources()
- .getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
- }
-
- public View getBack() {
- // Back button
- return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
- }
-
- public View getDown() {
- // Ime down button
- return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK);
- }
-
- public View getHome() {
- // Home button
- return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME);
- }
-
- public View getRecents() {
- // Recents button
- return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS);
- }
-
- public View getImeSwitcher() {
- // IME Switcher Button
- return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
- }
-
- private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
- ImageView buttonView = new ImageView(mContext);
- buttonView.setImageResource(drawableId);
- buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
- buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0);
- buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType));
- return buttonView;
- }
-
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
deleted file mode 100644
index 287caab..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.RelativeLayout;
-
-import com.android.launcher3.views.ActivityContext;
-
-public class ImeBarView extends RelativeLayout {
-
- private ButtonProvider mButtonProvider;
- private View mImeView;
-
- public ImeBarView(Context context) {
- this(context, null);
- }
-
- public ImeBarView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ImeBarView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public void init(ButtonProvider buttonProvider) {
- mButtonProvider = buttonProvider;
-
- ActivityContext context = getActivityContext();
- RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
- context.getDeviceProfile().iconSizePx,
- context.getDeviceProfile().iconSizePx
- );
- RelativeLayout.LayoutParams downParams = new RelativeLayout.LayoutParams(imeParams);
-
- imeParams.addRule(ALIGN_PARENT_END);
- imeParams.setMarginEnd(context.getDeviceProfile().iconSizePx);
- downParams.setMarginStart(context.getDeviceProfile().iconSizePx);
- downParams.addRule(ALIGN_PARENT_START);
-
- // Down Arrow
- View downView = mButtonProvider.getDown();
- downView.setLayoutParams(downParams);
- downView.setRotation(-90);
- addView(downView);
-
- // IME switcher button
- mImeView = mButtonProvider.getImeSwitcher();
- mImeView.setLayoutParams(imeParams);
- addView(mImeView);
- }
-
- public void setImeSwitcherVisibility(boolean show) {
- mImeView.setVisibility(show ? VISIBLE : GONE);
- }
-
- private <T extends Context & ActivityContext> T getActivityContext() {
- return ActivityContext.lookupContext(getContext());
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index c2d107c..b5c834d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -15,42 +15,58 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.view.MotionEvent;
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.recents.model.ThumbnailData;
/**
* A data source which integrates with a Launcher instance
- * TODO: Rename to have Launcher prefix
*/
-
public class LauncherTaskbarUIController extends TaskbarUIController {
private final BaseQuickstepLauncher mLauncher;
private final TaskbarStateHandler mTaskbarStateHandler;
- private final TaskbarAnimationController mTaskbarAnimationController;
- private final TaskbarHotseatController mHotseatController;
private final TaskbarActivityContext mContext;
- final TaskbarDragLayer mTaskbarDragLayer;
- final TaskbarView mTaskbarView;
+ private final TaskbarDragLayer mTaskbarDragLayer;
+ private final TaskbarView mTaskbarView;
- private @Nullable Animator mAnimator;
- private boolean mIsAnimatingToLauncher;
+ private final AnimatedFloat mIconAlignmentForResumedState =
+ new AnimatedFloat(this::onIconAlignmentRatioChanged);
+ private final AnimatedFloat mIconAlignmentForGestureState =
+ new AnimatedFloat(this::onIconAlignmentRatioChanged);
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+ private AnimatedFloat mTaskbarBackgroundAlpha;
+ private AlphaProperty mIconAlphaForHome;
+ private boolean mIsAnimatingToLauncherViaResume;
+ private boolean mIsAnimatingToLauncherViaGesture;
+ private TaskbarKeyguardController mKeyguardController;
+
+ private LauncherState mTargetStateOverride = null;
public LauncherTaskbarUIController(
BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
@@ -60,157 +76,161 @@
mLauncher = launcher;
mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
- mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
- createTaskbarAnimationControllerCallbacks());
- mHotseatController = new TaskbarHotseatController(
- mLauncher, mTaskbarView::updateHotseatItems);
}
@Override
- protected void onCreate() {
- mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
- mTaskbarAnimationController.init();
- mHotseatController.init();
- setTaskbarViewVisible(!mLauncher.hasBeenResumed());
- alignRealHotseatWithTaskbar();
+ protected void init(TaskbarControllers taskbarControllers) {
+ mControllers = taskbarControllers;
+
+ mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
+ .getTaskbarBackgroundAlpha();
+
+ MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
+ mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
+
mLauncher.setTaskbarUIController(this);
+ mKeyguardController = taskbarControllers.taskbarKeyguardController;
+
+ onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
+ mIconAlignmentForResumedState.finishAnimation();
+ onIconAlignmentRatioChanged();
}
@Override
protected void onDestroy() {
- if (mAnimator != null) {
- // End this first, in case it relies on properties that are about to be cleaned up.
- mAnimator.end();
- }
- mTaskbarStateHandler.setAnimationController(null);
- mTaskbarAnimationController.cleanup();
- mHotseatController.cleanup();
- setTaskbarViewVisible(true);
+ onLauncherResumedOrPaused(false);
+ mIconAlignmentForResumedState.finishAnimation();
+ mIconAlignmentForGestureState.finishAnimation();
+
mLauncher.getHotseat().setIconsAlpha(1f);
mLauncher.setTaskbarUIController(null);
}
@Override
protected boolean isTaskbarTouchable() {
- return !mIsAnimatingToLauncher;
+ return !isAnimatingToLauncher() && !mControllers.taskbarStashController.isStashed();
}
- private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
- return new TaskbarAnimationControllerCallbacks() {
- @Override
- public void updateTaskbarBackgroundAlpha(float alpha) {
- mTaskbarDragLayer.setTaskbarBackgroundAlpha(alpha);
- }
+ private boolean isAnimatingToLauncher() {
+ return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
+ }
- @Override
- public void updateTaskbarVisibilityAlpha(float alpha) {
- mTaskbarView.setAlpha(alpha);
- }
-
- @Override
- public void updateImeBarVisibilityAlpha(float alpha) {
- mTaskbarDragLayer.updateImeBarVisibilityAlpha(alpha);
- }
-
- @Override
- public void updateTaskbarScale(float scale) {
- mTaskbarView.setScaleX(scale);
- mTaskbarView.setScaleY(scale);
- }
-
- @Override
- public void updateTaskbarTranslationY(float translationY) {
- if (translationY < 0) {
- // Resize to accommodate the max translation we'll reach.
- mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize
- + mLauncher.getHotseat().getTaskbarOffsetY());
- } else {
- mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize);
- }
- mTaskbarView.setTranslationY(translationY);
- }
- };
+ @Override
+ protected void updateContentInsets(Rect outContentInsets) {
+ int contentHeight = mControllers.taskbarStashController.getContentHeight();
+ outContentInsets.top = mTaskbarDragLayer.getHeight() - contentHeight;
}
/**
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
*/
public void onLauncherResumedOrPaused(boolean isResumed) {
+ if (mKeyguardController.isScreenOff()) {
+ if (!isResumed) {
+ return;
+ } else {
+ // Resuming implicitly means device unlocked
+ mKeyguardController.setScreenOn();
+ }
+ }
+
long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION;
- if (mAnimator != null) {
- mAnimator.cancel();
+ ObjectAnimator anim = mIconAlignmentForResumedState.animateToValue(
+ getCurrentIconAlignmentRatio(), isResumed ? 1 : 0)
+ .setDuration(duration);
+
+ anim.addListener(AnimatorListeners.forEndCallback(
+ () -> mIsAnimatingToLauncherViaResume = false));
+ anim.start();
+ mIsAnimatingToLauncherViaResume = isResumed;
+
+ if (!isResumed) {
+ TaskbarStashController stashController = mControllers.taskbarStashController;
+ stashController.animateToIsStashed(stashController.isStashedInApp(), duration);
}
- if (isResumed) {
- mAnimator = createAnimToLauncher(null, duration);
- } else {
- mAnimator = createAnimToApp(duration);
- }
- mAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimator = null;
- }
- });
- mAnimator.start();
+ SystemUiProxy.INSTANCE.get(mContext).notifyTaskbarStatus(!isResumed,
+ mControllers.taskbarStashController.isStashedInApp());
}
/**
- * Create Taskbar animation when going from an app to Launcher.
+ * Create Taskbar animation when going from an app to Launcher as part of recents transition.
* @param toState If known, the state we will end up in when reaching Launcher.
+ * @param callbacks callbacks to track the recents animation lifecycle. The state change is
+ * automatically reset once the recents animation finishes
*/
- public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
- PendingAnimation anim = new PendingAnimation(duration);
- anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
- if (toState != null) {
- mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
+ public Animator createAnimToLauncher(@NonNull LauncherState toState,
+ @NonNull RecentsAnimationCallbacks callbacks,
+ long duration) {
+ TaskbarStashController stashController = mControllers.taskbarStashController;
+ ObjectAnimator animator = mIconAlignmentForGestureState
+ .animateToValue(1)
+ .setDuration(duration);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTargetStateOverride = null;
+ animator.removeListener(this);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mTargetStateOverride = toState;
+ mIsAnimatingToLauncherViaGesture = true;
+ // TODO: base this on launcher state
+ stashController.animateToIsStashed(false, duration);
+ }
+ });
+ callbacks.addListener(new RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+ endGestureStateOverride(true);
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ endGestureStateOverride(!controller.getFinishTargetIsLauncher());
+ }
+
+ private void endGestureStateOverride(boolean finishedToApp) {
+ callbacks.removeListener(this);
+ mIsAnimatingToLauncherViaGesture = false;
+
+ mIconAlignmentForGestureState
+ .animateToValue(0)
+ .start();
+
+ if (finishedToApp) {
+ // We only need this for the exiting live tile case.
+ stashController.animateToIsStashed(stashController.isStashedInApp());
+ }
+ }
+ });
+ return animator;
+ }
+
+ private float getCurrentIconAlignmentRatio() {
+ return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
+ }
+
+ private void onIconAlignmentRatioChanged() {
+ if (mControllers == null) {
+ return;
}
+ float alignment = getCurrentIconAlignmentRatio();
+ mControllers.taskbarViewController.setLauncherIconAlignment(
+ alignment, mLauncher.getDeviceProfile());
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mIsAnimatingToLauncher = true;
- mTaskbarView.setHolesAllowedInLayout(true);
- mTaskbarView.updateHotseatItemsVisibility();
- }
+ mTaskbarBackgroundAlpha.updateValue(1 - alignment);
- @Override
- public void onAnimationEnd(Animator animation) {
- mIsAnimatingToLauncher = false;
- setTaskbarViewVisible(false);
- }
- });
-
- return anim.buildAnim();
- }
-
- private Animator createAnimToApp(long duration) {
- PendingAnimation anim = new PendingAnimation(duration);
- anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mTaskbarView.updateHotseatItemsVisibility();
- setTaskbarViewVisible(true);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mTaskbarView.setHolesAllowedInLayout(false);
- }
- });
- return anim.buildAnim();
- }
-
- @Override
- protected void onImeVisible(TaskbarDragLayer containerView, boolean isVisible) {
- mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1);
- }
-
- /**
- * Should be called when one or more items in the Hotseat have changed.
- */
- public void onHotseatUpdated() {
- mHotseatController.onHotseatUpdated();
+ LauncherState state = mTargetStateOverride != null ? mTargetStateOverride
+ : mLauncher.getStateManager().getState();
+ if ((state.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+ // If the hotseat icons are visible, then switch taskbar in last frame
+ setTaskbarViewVisible(alignment < 1);
+ } else {
+ mLauncher.getHotseat().setIconsAlpha(1);
+ mIconAlphaForHome.setValue(1 - alignment);
+ }
}
/**
@@ -222,54 +242,11 @@
}
public boolean isDraggingItem() {
- return mTaskbarView.isDraggingItem();
- }
-
- /**
- * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
- */
- @Override
- public void alignRealHotseatWithTaskbar() {
- Rect hotseatBounds = new Rect();
- DeviceProfile grid = mLauncher.getDeviceProfile();
- int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
- int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY();
- int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset;
- int hotseatBottomDiff = taskbarOffset;
-
- RectF hotseatBoundsF = mTaskbarView.getHotseatBounds();
- Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(),
- mTaskbarView.getPivotX(), mTaskbarView.getPivotY());
- hotseatBoundsF.round(hotseatBounds);
- mLauncher.getHotseat().setPadding(hotseatBounds.left,
- hotseatBounds.top + hotseatTopDiff,
- mTaskbarView.getWidth() - hotseatBounds.right,
- mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff);
- }
-
- /**
- * Returns the ratio of the taskbar icon size on home vs in an app.
- */
- public float getTaskbarScaleOnHome() {
- DeviceProfile inAppDp = mContext.getDeviceProfile();
- DeviceProfile onHomeDp = mLauncher.getDeviceProfile();
- return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
+ return mContext.getDragController().isDragging();
}
void setTaskbarViewVisible(boolean isVisible) {
- mTaskbarView.setIconsVisibility(isVisible);
+ mIconAlphaForHome.setValue(isVisible ? 1 : 0);
mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f);
}
-
- /**
- * Contains methods that TaskbarAnimationController can call to interface with
- * TaskbarController.
- */
- protected interface TaskbarAnimationControllerCallbacks {
- void updateTaskbarBackgroundAlpha(float alpha);
- void updateTaskbarVisibilityAlpha(float alpha);
- void updateImeBarVisibilityAlpha(float alpha);
- void updateTaskbarScale(float scale);
- void updateTaskbarTranslationY(float translationY);
- }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
new file mode 100644
index 0000000..be3f5d9
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 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;
+
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y_LONG_CLICK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_IME;
+import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+
+import android.animation.ObjectAnimator;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.util.Property;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnHoverListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.taskbar.contextual.RotationButton;
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+
+import java.util.ArrayList;
+import java.util.function.IntPredicate;
+
+/**
+ * Controller for managing nav bar buttons in taskbar
+ */
+public class NavbarButtonsViewController {
+
+ private final Rect mTempRect = new Rect();
+
+ private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0;
+ private static final int FLAG_IME_VISIBLE = 1 << 1;
+ private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
+ private static final int FLAG_A11Y_VISIBLE = 1 << 3;
+ private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
+ private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
+
+ private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
+
+ private View.OnLongClickListener mA11yLongClickListener;
+ private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
+ private final ArrayList<View> mAllButtons = new ArrayList<>();
+ private int mState;
+
+ private final TaskbarActivityContext mContext;
+ private final FrameLayout mNavButtonsView;
+ private final ViewGroup mNavButtonContainer;
+ // Used for IME+A11Y buttons
+ private final ViewGroup mEndContextualContainer;
+ private final ViewGroup mStartContextualContainer;
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+ private View mA11yButton;
+ private int mSysuiStateFlags;
+ private View mBackButton;
+
+ public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
+ mContext = context;
+ mNavButtonsView = navButtonsView;
+ mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
+ mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
+ mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
+ }
+
+ /**
+ * Initializes the controller
+ */
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
+
+ mA11yLongClickListener = view -> {
+ mControllers.navButtonController.onButtonClick(BUTTON_A11Y_LONG_CLICK);
+ return true;
+ };
+
+ mPropertyHolders.add(new StatePropertyHolder(
+ mControllers.taskbarViewController.getTaskbarIconAlpha()
+ .getProperty(ALPHA_INDEX_IME),
+ flags -> (flags & FLAG_IME_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
+
+ // IME switcher
+ View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
+ mEndContextualContainer, mControllers.navButtonController, R.id.ime_switcher);
+ mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
+ flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
+ && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)
+ && ((flags & FLAG_A11Y_VISIBLE) == 0)));
+
+ View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+ mStartContextualContainer, mControllers.navButtonController, R.id.back);
+ imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
+ // Rotate when Ime visible
+ mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
+ flags -> (flags & FLAG_IME_VISIBLE) != 0));
+
+ mPropertyHolders.add(new StatePropertyHolder(
+ mControllers.taskbarViewController.getTaskbarIconAlpha()
+ .getProperty(ALPHA_INDEX_KEYGUARD),
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
+
+ mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
+ .getKeyguardBgTaskbar(),
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
+
+ if (mContext.isThreeButtonNav()) {
+ initButtons(mNavButtonContainer, mEndContextualContainer,
+ mControllers.navButtonController);
+
+ // Animate taskbar background when IME shows
+ mPropertyHolders.add(new StatePropertyHolder(
+ mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
+ flags -> (flags & FLAG_IME_VISIBLE) != 0 ||
+ (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0,
+ AnimatedFloat.VALUE, 1, 0));
+
+ // Rotation button
+ RotationButton rotationButton = new RotationButtonImpl(
+ addButton(mEndContextualContainer, R.id.rotate_suggestion));
+ rotationButton.hide();
+ mControllers.rotationButtonController.setRotationButton(rotationButton);
+ } else {
+ mControllers.rotationButtonController.setRotationButton(new RotationButton() {});
+ }
+
+ applyState();
+ mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
+ }
+
+ private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
+ TaskbarNavButtonController navButtonController) {
+
+ // Hide when keyguard is showing, show when bouncer is showing
+ mPropertyHolders.add(new StatePropertyHolder(mBackButton,
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
+ (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
+
+ mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
+ mNavButtonContainer, mControllers.navButtonController, R.id.back);
+ mPropertyHolders.add(new StatePropertyHolder(mBackButton,
+ flags -> (flags & FLAG_IME_VISIBLE) == 0));
+
+ // home and recents buttons
+ View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
+ navButtonController, R.id.home);
+ mPropertyHolders.add(new StatePropertyHolder(homeButton,
+ flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
+ (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+ View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
+ navContainer, navButtonController, R.id.recent_apps);
+ mPropertyHolders.add(new StatePropertyHolder(recentsButton,
+ flags -> (flags & FLAG_IME_VISIBLE) == 0 &&
+ (flags & FLAG_KEYGUARD_VISIBLE) == 0));
+
+ // A11y button
+ mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
+ endContainer, navButtonController, R.id.accessibility_button);
+ mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
+ flags -> (flags & FLAG_A11Y_VISIBLE) != 0
+ && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
+ mA11yButton.setOnLongClickListener(mA11yLongClickListener);
+ }
+
+ public void updateStateForSysuiFlags(int systemUiStateFlags, boolean forceUpdate) {
+ boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+ boolean isImeSwitcherShowing = (systemUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
+ boolean a11yVisible = (systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ boolean a11yLongClickable =
+ (systemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+
+ if (!forceUpdate && systemUiStateFlags == mSysuiStateFlags) {
+ return;
+ }
+ mSysuiStateFlags = systemUiStateFlags;
+
+ updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
+ updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing);
+ updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
+ if (mA11yButton != null) {
+ // Only used in 3 button
+ mA11yButton.setLongClickable(a11yLongClickable);
+ }
+ applyState();
+ }
+
+ /**
+ * Should be called when we need to show back button for bouncer
+ */
+ public void setBackForBouncer(boolean isBouncerVisible) {
+ updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
+ applyState();
+ }
+
+ /**
+ * Slightly misnamed, but should be called when keyguard OR AOD is showing
+ */
+ public void setKeyguardVisible(boolean isKeyguardVisible) {
+ updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible);
+ applyState();
+ }
+
+ /**
+ * Returns true if IME bar is visible
+ */
+ public boolean isImeVisible() {
+ return (mState & FLAG_IME_VISIBLE) != 0;
+ }
+
+ /**
+ * Adds the bounds corresponding to all visible buttons to provided region
+ */
+ public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) {
+ int count = mAllButtons.size();
+ for (int i = 0; i < count; i++) {
+ View button = mAllButtons.get(i);
+ if (button.getVisibility() == View.VISIBLE) {
+ parent.getDescendantRectRelativeToSelf(button, mTempRect);
+ outRegion.op(mTempRect, Op.UNION);
+ }
+ }
+ }
+
+ /**
+ * Does not call {@link #applyState()}. Don't forget to!
+ */
+ private void updateStateForFlag(int flag, boolean enabled) {
+ if (enabled) {
+ mState |= flag;
+ } else {
+ mState &= ~flag;
+ }
+ }
+
+ private void applyState() {
+ int count = mPropertyHolders.size();
+ for (int i = 0; i < count; i++) {
+ mPropertyHolders.get(i).setState(mState);
+ }
+ }
+
+ private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
+ ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
+ ImageView buttonView = addButton(parent, id);
+ buttonView.setImageResource(drawableId);
+ buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
+ return buttonView;
+ }
+
+ private ImageView addButton(ViewGroup parent, int id) {
+ ImageView buttonView = (ImageView) mContext.getLayoutInflater()
+ .inflate(R.layout.taskbar_nav_button, parent, false);
+ buttonView.setId(id);
+ parent.addView(buttonView);
+ mAllButtons.add(buttonView);
+ return buttonView;
+ }
+
+ private class RotationButtonImpl implements RotationButton {
+
+ private final ImageView mButton;
+ private AnimatedVectorDrawable mImageDrawable;
+
+ RotationButtonImpl(ImageView button) {
+ mButton = button;
+ }
+
+ @Override
+ public void setRotationButtonController(RotationButtonController rotationButtonController) {
+ // TODO(b/187754252) UI polish, different icons based on light/dark context, etc
+ mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
+ .getDrawable(rotationButtonController.getIconResId());
+ mButton.setImageDrawable(mImageDrawable);
+ mImageDrawable.setCallback(mButton);
+ }
+
+ @Override
+ public View getCurrentView() {
+ return mButton;
+ }
+
+ @Override
+ public void show() {
+ mButton.setVisibility(View.VISIBLE);
+ mState |= FLAG_ROTATION_BUTTON_VISIBLE;
+ applyState();
+ }
+
+ @Override
+ public void hide() {
+ mButton.setVisibility(View.GONE);
+ mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
+ applyState();
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mButton.getVisibility() == View.VISIBLE;
+ }
+
+ @Override
+ public void updateIcon(int lightIconColor, int darkIconColor) {
+ // TODO(b/187754252): UI Polish
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener onClickListener) {
+ mButton.setOnClickListener(onClickListener);
+ }
+
+ @Override
+ public void setOnHoverListener(OnHoverListener onHoverListener) {
+ mButton.setOnHoverListener(onHoverListener);
+ }
+
+ @Override
+ public AnimatedVectorDrawable getImageDrawable() {
+ return mImageDrawable;
+ }
+
+ @Override
+ public void setDarkIntensity(float darkIntensity) {
+ // TODO(b/187754252) UI polish
+ }
+
+ @Override
+ public boolean acceptRotationProposal() {
+ return mButton.isAttachedToWindow();
+ }
+ }
+
+ private static class StatePropertyHolder {
+
+ private final float mEnabledValue, mDisabledValue;
+ private final ObjectAnimator mAnimator;
+ private final IntPredicate mEnableCondition;
+
+ private boolean mIsEnabled = true;
+
+ StatePropertyHolder(View view, IntPredicate enableCondition) {
+ this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
+ mAnimator.addListener(new AlphaUpdateListener(view));
+ }
+
+ <T> StatePropertyHolder(T target, IntPredicate enabledCondition,
+ Property<T, Float> property, float enabledValue, float disabledValue) {
+ mEnableCondition = enabledCondition;
+ mEnabledValue = enabledValue;
+ mDisabledValue = disabledValue;
+ mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
+ }
+
+ public void setState(int flags) {
+ boolean isEnabled = mEnableCondition.test(flags);
+ if (mIsEnabled != isEnabled) {
+ mIsEnabled = isEnabled;
+ mAnimator.cancel();
+ mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
+ mAnimator.start();
+ }
+ }
+
+ public void endAnimation() {
+ if (mAnimator.isRunning()) {
+ mAnimator.end();
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
new file mode 100644
index 0000000..df37261
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 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;
+
+import android.animation.Animator;
+import android.content.res.Resources;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.anim.RevealOutlineAnimation;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.quickstep.AnimatedFloat;
+
+/**
+ * Handles properties/data collection, then passes the results to our stashed handle View to render.
+ */
+public class StashedHandleViewController {
+
+ private final TaskbarActivityContext mActivity;
+ private final View mStashedHandleView;
+ private final int mStashedHandleWidth;
+ private final int mStashedHandleHeight;
+ private final AnimatedFloat mTaskbarStashedHandleAlpha = new AnimatedFloat(
+ this::updateStashedHandleAlpha);
+ private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
+ this::updateStashedHandleHintScale);
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+
+ // The bounds we want to clip to in the settled state when showing the stashed handle.
+ private final Rect mStashedHandleBounds = new Rect();
+ private float mStashedHandleRadius;
+
+ private boolean mIsAtStashedRevealBounds = true;
+
+ public StashedHandleViewController(TaskbarActivityContext activity, View stashedHandleView) {
+ mActivity = activity;
+ mStashedHandleView = stashedHandleView;
+ final Resources resources = mActivity.getResources();
+ mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
+ mStashedHandleHeight = resources.getDimensionPixelSize(
+ R.dimen.taskbar_stashed_handle_height);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+ updateStashedHandleAlpha();
+ mTaskbarStashedHandleHintScale.updateValue(1f);
+
+ final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
+ mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final int stashedCenterX = view.getWidth() / 2;
+ final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+ mStashedHandleBounds.set(
+ stashedCenterX - mStashedHandleWidth / 2,
+ stashedCenterY - mStashedHandleHeight / 2,
+ stashedCenterX + mStashedHandleWidth / 2,
+ stashedCenterY + mStashedHandleHeight / 2);
+ mStashedHandleRadius = view.getHeight() / 2f;
+ outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
+ }
+ });
+
+ mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
+ final int stashedCenterX = view.getWidth() / 2;
+ final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
+
+ view.setPivotX(stashedCenterX);
+ view.setPivotY(stashedCenterY);
+ });
+ }
+
+ public AnimatedFloat getStashedHandleAlpha() {
+ return mTaskbarStashedHandleAlpha;
+ }
+
+ public AnimatedFloat getStashedHandleHintScale() {
+ return mTaskbarStashedHandleHintScale;
+ }
+
+ /**
+ * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
+ * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
+ * morphs into the size of where the taskbar icons will be.
+ */
+ public @Nullable Animator createRevealAnimToIsStashed(boolean isStashed) {
+ if (mIsAtStashedRevealBounds == isStashed) {
+ return null;
+ }
+ mIsAtStashedRevealBounds = isStashed;
+ final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
+ mStashedHandleRadius, mStashedHandleRadius,
+ mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
+ return handleRevealProvider.createRevealAnimator(mStashedHandleView, !isStashed);
+ }
+
+ protected void updateStashedHandleAlpha() {
+ mStashedHandleView.setAlpha(mTaskbarStashedHandleAlpha.value);
+ }
+
+ protected void updateStashedHandleHintScale() {
+ mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value);
+ mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 4ba0ee0..dbe528f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -17,7 +17,7 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
@@ -28,9 +28,7 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.graphics.PixelFormat;
-import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.os.Process;
import android.os.SystemProperties;
import android.util.Log;
@@ -40,31 +38,25 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import android.widget.FrameLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.Folder;
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.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;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -88,45 +80,55 @@
private final DeviceProfile mDeviceProfile;
private final LayoutInflater mLayoutInflater;
private final TaskbarDragLayer mDragLayer;
- private final TaskbarIconController mIconController;
- private final MyDragController mDragController;
+ private final TaskbarControllers mControllers;
private final WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowLayoutParams;
+ private boolean mIsFullscreen;
+ // The size we should return to when we call setTaskbarWindowFullscreen(false)
+ private int mLastRequestedNonFullscreenHeight;
private final SysUINavigationMode.Mode mNavMode;
- private final TaskbarNavButtonController mNavButtonController;
+ private final ViewCache mViewCache = new ViewCache();
private final boolean mIsSafeModeEnabled;
-
- @NonNull
- private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
- private final View.OnClickListener mOnTaskbarIconClickListener;
- private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
+ private boolean mIsDestroyed = false;
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
TaskbarNavButtonController buttonController) {
super(windowContext, Themes.getActivityThemeRes(windowContext));
mDeviceProfile = dp;
- mNavButtonController = buttonController;
+
mNavMode = SysUINavigationMode.getMode(windowContext);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
- mOnTaskbarIconLongClickListener =
- new TaskbarDragController(this)::startSystemDragOnLongClick;
- mOnTaskbarIconClickListener = this::onTaskbarIconClicked;
-
float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
+ mDeviceProfile.updateIconSize(1, getResources());
float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
mDeviceProfile.updateIconSize(iconScale, getResources());
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
- mDragLayer = (TaskbarDragLayer) mLayoutInflater
- .inflate(R.layout.taskbar, null, false);
- mIconController = new TaskbarIconController(this, mDragLayer);
- mDragController = new MyDragController(this);
+
+ // Inflate views.
+ mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
+ R.layout.taskbar, null, false);
+ TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
+ FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
+ View stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
+
+ // Construct controllers.
+ mControllers = new TaskbarControllers(this,
+ new TaskbarDragController(this),
+ buttonController,
+ new NavbarButtonsViewController(this, navButtonsView),
+ new RotationButtonController(this, R.color.popup_color_primary_light,
+ R.color.popup_color_primary_light),
+ new TaskbarDragLayerController(this, mDragLayer),
+ new TaskbarViewController(this, taskbarView),
+ new TaskbarKeyguardController(this),
+ new StashedHandleViewController(this, stashedHandleView),
+ new TaskbarStashController(this));
Display display = windowContext.getDisplay();
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
@@ -136,10 +138,11 @@
}
public void init() {
+ mLastRequestedNonFullscreenHeight = mDeviceProfile.taskbarSize;
mWindowLayoutParams = new WindowManager.LayoutParams(
MATCH_PARENT,
- mDeviceProfile.taskbarSize,
- TYPE_APPLICATION_OVERLAY,
+ mLastRequestedNonFullscreenHeight,
+ TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
mWindowLayoutParams.setTitle(WINDOW_TITLE);
@@ -148,7 +151,6 @@
mWindowLayoutParams.setFitInsetsTypes(0);
mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mWindowLayoutParams.setSystemApplicationOverlay(true);
WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance();
wmWrapper.setProvidesInsetsTypes(
@@ -156,23 +158,14 @@
new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
);
- mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
+ // Initialize controllers after all are constructed.
+ mControllers.init();
+
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
}
- /**
- * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
- */
- public void setTaskbarWindowHeight(int height) {
- if (mWindowLayoutParams.height == height) {
- return;
- }
- mWindowLayoutParams.height = height;
- mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
- }
-
- public boolean canShowNavButtons() {
- return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS;
+ public boolean isThreeButtonNav() {
+ return mNavMode == Mode.THREE_BUTTONS;
}
@Override
@@ -192,59 +185,89 @@
@Override
public Rect getFolderBoundingBox() {
- return mDragLayer.getFolderBoundingBox();
+ return mControllers.taskbarDragLayerController.getFolderBoundingBox();
}
@Override
- public DragController getDragController() {
- return mDragController;
+ public TaskbarDragController getDragController() {
+ return mControllers.taskbarDragController;
+ }
+
+ @Override
+ public ViewCache getViewCache() {
+ return mViewCache;
}
/**
* Sets a new data-source for this taskbar instance
*/
public void setUIController(@NonNull TaskbarUIController uiController) {
- mUIController.onDestroy();
- mUIController = uiController;
- mIconController.setUIController(mUIController);
- mUIController.onCreate();
+ mControllers.uiController.onDestroy();
+ mControllers.uiController = uiController;
+ mControllers.uiController.init(mControllers);
}
/**
* Called when this instance of taskbar is no longer needed
*/
public void onDestroy() {
+ mIsDestroyed = true;
setUIController(TaskbarUIController.DEFAULT);
- mIconController.onDestroy();
+ mControllers.onDestroy();
mWindowManager.removeViewImmediate(mDragLayer);
}
- void onNavigationButtonClick(@TaskbarButton int buttonType) {
- mNavButtonController.onButtonClick(buttonType);
+ public void updateSysuiStateFlags(int systemUiStateFlags, boolean forceUpdate) {
+ mControllers.navbarButtonsViewController.updateStateForSysuiFlags(
+ systemUiStateFlags, forceUpdate);
+ mControllers.taskbarViewController.setImeIsVisible(
+ mControllers.navbarButtonsViewController.isImeVisible());
+ mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags);
}
- /**
- * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
- */
- public void setImeIsVisible(boolean isImeVisible) {
- mIconController.setImeIsVisible(isImeVisible);
+ public void onRotationProposal(int rotation, boolean isValid) {
+ mControllers.rotationButtonController.onRotationProposal(rotation, isValid);
}
- /**
- * 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);
+ public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
+ if (displayId != getDisplayId()) {
+ return;
+ }
+ mControllers.rotationButtonController.onDisable2FlagChanged(state2);
+ mControllers.taskbarKeyguardController.disableNavbarElements(state1, state2);
+ }
+
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior);
}
/**
* Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
*/
- protected void setTaskbarWindowFullscreen(boolean fullscreen) {
- setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize);
+ public void setTaskbarWindowFullscreen(boolean fullscreen) {
+ mIsFullscreen = fullscreen;
+ setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenHeight);
+ }
+
+ /**
+ * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset).
+ */
+ public void setTaskbarWindowHeight(int height) {
+ if (mWindowLayoutParams.height == height || mIsDestroyed) {
+ return;
+ }
+ if (height != MATCH_PARENT) {
+ mLastRequestedNonFullscreenHeight = height;
+ if (mIsFullscreen) {
+ // We still need to be fullscreen, so defer any change to our height until we call
+ // setTaskbarWindowFullscreen(false). For example, this could happen when dragging
+ // from the gesture region, as the drag will cancel the gesture and reset launcher's
+ // state, which in turn normally would reset the taskbar window height as well.
+ return;
+ }
+ }
+ mWindowLayoutParams.height = height;
+ mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
}
protected void onTaskbarIconClicked(View view) {
@@ -262,8 +285,8 @@
folder.animateOpen();
folder.iterateOverItems((itemInfo, itemView) -> {
- itemView.setOnClickListener(mOnTaskbarIconClickListener);
- itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener);
+ mControllers.taskbarViewController
+ .setClickAndLongClickListenersForIcon(itemView);
// To play haptic when dragging, like other Taskbar items do.
itemView.setHapticFeedbackEnabled(true);
return false;
@@ -271,7 +294,9 @@
});
} else if (tag instanceof WorkspaceItemInfo) {
WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
- if (!(info.isDisabled() && ItemClickHandler.handleDisabledItemClicked(info, this))) {
+ if (info.isDisabled()) {
+ ItemClickHandler.handleDisabledItemClicked(info, this);
+ } else {
Intent intent = new Intent(info.getIntent())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -308,26 +333,19 @@
AbstractFloatingView.closeAllOpenViews(this);
}
- private static class MyDragController extends DragController<TaskbarActivityContext> {
- MyDragController(TaskbarActivityContext activity) {
- super(activity);
- }
+ /**
+ * Called when we detect a long press in the nav region before passing the gesture slop.
+ * @return Whether taskbar handled the long press, and thus should cancel the gesture.
+ */
+ public boolean onLongPressToUnstashTaskbar() {
+ return mControllers.taskbarStashController.onLongPressToUnstashTaskbar();
+ }
- @Override
- protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
- DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
- ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
- float dragViewScaleOnDrop, DragOptions options) {
- return null;
- }
-
- @Override
- protected void exitDrag() {
- }
-
- @Override
- protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
- return null;
- }
+ /**
+ * Called when we detect a motion down or up/cancel in the nav region while stashed.
+ * @param animateForward Whether to animate towards the unstashed hint state or back to stashed.
+ */
+ public void startTaskbarUnstashHint(boolean animateForward) {
+ mControllers.taskbarStashController.startUnstashHint(animateForward);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
deleted file mode 100644
index e20ddf8..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 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;
-
-import static com.android.launcher3.LauncherState.TASKBAR;
-
-import android.animation.Animator;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks;
-import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.shared.system.QuickStepContract;
-
-/**
- * Works with TaskbarController to update the TaskbarView's visual properties based on factors such
- * as LauncherState, whether Launcher is in the foreground, etc.
- */
-public class TaskbarAnimationController {
-
- private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
-
- private final BaseQuickstepLauncher mLauncher;
- private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
-
- // Background alpha.
- private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
- this::onTaskbarBackgroundAlphaChanged);
-
- // Overall visibility.
- private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
- this::updateVisibilityAlpha);
- private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
- this::updateVisibilityAlphaForIme);
-
- // Scale.
- private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
- this::updateScale);
-
- // TranslationY.
- private final AnimatedFloat mTaskbarTranslationYForLauncherState = new AnimatedFloat(
- this::updateTranslationY);
-
- public TaskbarAnimationController(BaseQuickstepLauncher launcher,
- TaskbarAnimationControllerCallbacks taskbarCallbacks) {
- mLauncher = launcher;
- mTaskbarCallbacks = taskbarCallbacks;
- }
-
- protected void init() {
- mTaskbarBackgroundAlpha.updateValue(mLauncher.hasBeenResumed() ? 0f : 1f);
- boolean isVisibleForLauncherState = (mLauncher.getStateManager().getState()
- .getVisibleElements(mLauncher) & TASKBAR) != 0;
- mTaskbarVisibilityAlphaForLauncherState.updateValue(isVisibleForLauncherState ? 1f : 0f);
- boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
- & QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
- mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
-
- onTaskbarBackgroundAlphaChanged();
- updateVisibilityAlpha();
- }
-
- protected void cleanup() {
- setNavBarButtonAlpha(1f);
- }
-
- protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
- return mTaskbarVisibilityAlphaForLauncherState;
- }
-
- protected AnimatedFloat getTaskbarScaleForLauncherState() {
- return mTaskbarScaleForLauncherState;
- }
-
- protected AnimatedFloat getTaskbarTranslationYForLauncherState() {
- return mTaskbarTranslationYForLauncherState;
- }
-
- protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
- return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
- .setDuration(duration);
- }
-
- protected void animateToVisibilityForIme(float toAlpha) {
- mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForIme.value, toAlpha)
- .setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
- }
-
- private void onTaskbarBackgroundAlphaChanged() {
- mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
- updateVisibilityAlpha();
- updateScale();
- updateTranslationY();
- }
-
- private void updateVisibilityAlpha() {
- // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
- // assumption being that Taskbar should always be visible regardless of the current
- // LauncherState if Launcher is paused.
- float alphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
- float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
- mTaskbarVisibilityAlphaForLauncherState.value);
- float taskbarAlpha = alphaDueToLauncher * alphaDueToIme;
- mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
-
- // Make the nav bar invisible if taskbar is visible.
- setNavBarButtonAlpha(1f - taskbarAlpha);
- }
-
- private void updateVisibilityAlphaForIme() {
- updateVisibilityAlpha();
- float taskbarAlphaDueToIme = mTaskbarVisibilityAlphaForIme.value;
- mTaskbarCallbacks.updateImeBarVisibilityAlpha(1f - taskbarAlphaDueToIme);
- }
-
- private void updateScale() {
- // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
- // assumption being that Taskbar should always be at scale 1f regardless of the current
- // LauncherState if Launcher is paused.
- float scale = mTaskbarScaleForLauncherState.value;
- scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
- mTaskbarCallbacks.updateTaskbarScale(scale);
- }
-
- private void updateTranslationY() {
- // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
- // assumption being that Taskbar should always be at translationY 0f regardless of the
- // current LauncherState if Launcher is paused.
- float translationY = mTaskbarTranslationYForLauncherState.value;
- translationY = Utilities.mapRange(mTaskbarBackgroundAlpha.value, translationY, 0f);
- mTaskbarCallbacks.updateTaskbarTranslationY(translationY);
- }
-
- private void setNavBarButtonAlpha(float navBarAlpha) {
- SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
new file mode 100644
index 0000000..be26913
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 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;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
+
+/**
+ * Hosts various taskbar controllers to facilitate passing between one another.
+ */
+public class TaskbarControllers {
+
+ public final TaskbarActivityContext taskbarActivityContext;
+ public final TaskbarDragController taskbarDragController;
+ public final TaskbarNavButtonController navButtonController;
+ public final NavbarButtonsViewController navbarButtonsViewController;
+ public final RotationButtonController rotationButtonController;
+ public final TaskbarDragLayerController taskbarDragLayerController;
+ public final TaskbarViewController taskbarViewController;
+ public final TaskbarKeyguardController taskbarKeyguardController;
+ public final StashedHandleViewController stashedHandleViewController;
+ public final TaskbarStashController taskbarStashController;
+
+ /** Do not store this controller, as it may change at runtime. */
+ @NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
+
+ public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
+ TaskbarDragController taskbarDragController,
+ TaskbarNavButtonController navButtonController,
+ NavbarButtonsViewController navbarButtonsViewController,
+ RotationButtonController rotationButtonController,
+ TaskbarDragLayerController taskbarDragLayerController,
+ TaskbarViewController taskbarViewController,
+ TaskbarKeyguardController taskbarKeyguardController,
+ StashedHandleViewController stashedHandleViewController,
+ TaskbarStashController taskbarStashController) {
+ this.taskbarActivityContext = taskbarActivityContext;
+ this.taskbarDragController = taskbarDragController;
+ this.navButtonController = navButtonController;
+ this.navbarButtonsViewController = navbarButtonsViewController;
+ this.rotationButtonController = rotationButtonController;
+ this.taskbarDragLayerController = taskbarDragLayerController;
+ this.taskbarViewController = taskbarViewController;
+ this.taskbarKeyguardController = taskbarKeyguardController;
+ this.stashedHandleViewController = stashedHandleViewController;
+ this.taskbarStashController = taskbarStashController;
+ }
+
+ /**
+ * Initializes all controllers. Note that controllers can now reference each other through this
+ * TaskbarControllers instance, but should be careful to only access things that were created
+ * in constructors for now, as some controllers may still be waiting for init().
+ */
+ public void init() {
+ navbarButtonsViewController.init(this);
+ if (taskbarActivityContext.isThreeButtonNav()) {
+ rotationButtonController.init();
+ }
+ taskbarDragLayerController.init(this);
+ taskbarViewController.init(this);
+ taskbarKeyguardController.init(navbarButtonsViewController);
+ stashedHandleViewController.init(this);
+ taskbarStashController.init(this);
+ }
+
+ /**
+ * Cleans up all controllers.
+ */
+ public void onDestroy() {
+ uiController.onDestroy();
+ rotationButtonController.onDestroy();
+ taskbarDragLayerController.onDestroy();
+ taskbarKeyguardController.onDestroy();
+ taskbarViewController.onDestroy();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index ee44927..67ebc02 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -20,19 +20,35 @@
import android.content.ClipData;
import android.content.ClipDescription;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.view.DragEvent;
+import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragDriver;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ClipDescriptionCompat;
@@ -41,14 +57,20 @@
/**
* Handles long click on Taskbar items to start a system drag and drop operation.
*/
-public class TaskbarDragController {
+public class TaskbarDragController extends DragController<TaskbarActivityContext> {
- private final Context mContext;
private final int mDragIconSize;
+ private final int[] mTempXY = new int[2];
- public TaskbarDragController(Context context) {
- mContext = context;
- Resources resources = mContext.getResources();
+ // Where the initial touch was relative to the dragged icon.
+ private int mRegistrationX;
+ private int mRegistrationY;
+
+ private boolean mIsSystemDragInProgress;
+
+ public TaskbarDragController(TaskbarActivityContext activity) {
+ super(activity);
+ Resources resources = mActivity.getResources();
mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
}
@@ -57,18 +79,146 @@
* generate the ClipDescription and Intent.
* @return Whether {@link View#startDragAndDrop} started successfully.
*/
- protected boolean startSystemDragOnLongClick(View view) {
+ protected boolean startDragOnLongClick(View view) {
if (!(view instanceof BubbleTextView)) {
return false;
}
BubbleTextView btv = (BubbleTextView) view;
- View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+
+ mActivity.setTaskbarWindowFullscreen(true);
+ view.post(() -> {
+ startInternalDrag(btv);
+ btv.setVisibility(INVISIBLE);
+ });
+ return true;
+ }
+
+ private void startInternalDrag(BubbleTextView btv) {
+ float iconScale = 1f;
+ Drawable icon = btv.getIcon();
+ if (icon instanceof FastBitmapDrawable) {
+ iconScale = ((FastBitmapDrawable) icon).getAnimatedScale();
+ }
+
+ // Clear the pressed state if necessary
+ btv.clearFocus();
+ btv.setPressed(false);
+ btv.clearPressedBackground();
+
+ final DragPreviewProvider previewProvider = new DragPreviewProvider(btv);
+ final Drawable drawable = previewProvider.createDrawable();
+ final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
+ int dragLayerX = mTempXY[0];
+ int dragLayerY = mTempXY[1];
+
+ Rect dragRect = new Rect();
+ btv.getSourceVisualDragBounds(dragRect);
+ dragLayerY += dragRect.top;
+
+ DragOptions dragOptions = new DragOptions();
+ // TODO: open popup/pre-drag
+ // PopupContainerWithArrow popupContainer = PopupContainerWithArrow.showForIcon(view);
+ // if (popupContainer != null) {
+ // dragOptions.preDragCondition = popupContainer.createPreDragCondition();
+ // }
+
+ startDrag(
+ drawable,
+ /* view = */ null,
+ /* originalView = */ btv,
+ dragLayerX,
+ dragLayerY,
+ (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
+ (WorkspaceItemInfo) btv.getTag(),
+ /* dragVisualizeOffset = */ null,
+ dragRect,
+ scale * iconScale,
+ scale,
+ dragOptions);
+ }
+
+ @Override
+ protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view,
+ DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source,
+ ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale,
+ float dragViewScaleOnDrop, DragOptions options) {
+ mOptions = options;
+
+ mRegistrationX = mMotionDown.x - dragLayerX;
+ mRegistrationY = mMotionDown.y - dragLayerY;
+
+ final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
+ final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
+
+ mLastDropTarget = null;
+
+ mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext());
+ mDragObject.originalView = originalView;
+
+ mIsInPreDrag = mOptions.preDragCondition != null
+ && !mOptions.preDragCondition.shouldStartDrag(0);
+
+ float scalePx = mDragIconSize - dragRegion.width();
+ final DragView dragView = mDragObject.dragView = new TaskbarDragView(
+ mActivity,
+ drawable,
+ mRegistrationX,
+ mRegistrationY,
+ initialDragViewScale,
+ dragViewScaleOnDrop,
+ scalePx);
+ dragView.setItemInfo(dragInfo);
+ mDragObject.dragComplete = false;
+
+ mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
+ mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
+
+ mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
+ if (!mOptions.isAccessibleDrag) {
+ mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
+ }
+
+ mDragObject.dragSource = source;
+ mDragObject.dragInfo = dragInfo;
+ mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
+
+ if (dragRegion != null) {
+ dragView.setDragRegion(new Rect(dragRegion));
+ }
+
+ dragView.show(mLastTouch.x, mLastTouch.y);
+ mDistanceSinceScroll = 0;
+
+ if (!mIsInPreDrag) {
+ callOnDragStart();
+ } else if (mOptions.preDragCondition != null) {
+ mOptions.preDragCondition.onPreDragStart(mDragObject);
+ }
+
+ handleMoveEvent(mLastTouch.x, mLastTouch.y);
+
+ return dragView;
+ }
+
+ @Override
+ protected void callOnDragStart() {
+ super.callOnDragStart();
+ // Pre-drag has ended, start the global system drag.
+ AbstractFloatingView.closeAllOpenViews(mActivity);
+ startSystemDrag((BubbleTextView) mDragObject.originalView);
+ }
+
+ private void startSystemDrag(BubbleTextView btv) {
+ View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) {
+
@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
shadowSize.set(mDragIconSize, mDragIconSize);
- // TODO: should be based on last touch point on the icon.
- shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
+ // The registration point was taken before the icon scaled to mDragIconSize, so
+ // offset the registration to where the touch is on the new size.
+ int offset = (mDragIconSize - btv.getIconSize()) / 2;
+ shadowTouchPoint.set(mRegistrationX + offset, mRegistrationY + offset);
}
@Override
@@ -81,12 +231,12 @@
}
};
- Object tag = view.getTag();
+ Object tag = btv.getTag();
ClipDescription clipDescription = null;
Intent intent = null;
if (tag instanceof WorkspaceItemInfo) {
WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
- LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class);
clipDescription = new ClipDescription(item.title,
new String[] {
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
@@ -116,28 +266,82 @@
if (clipDescription != null && intent != null) {
ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
- view.setOnDragListener(getDraggedViewDragListener());
- return view.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
- View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE);
+ if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
+ View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) {
+ onSystemDragStarted();
+ }
}
- return false;
}
- /**
- * Hide the original Taskbar item while it is being dragged.
- */
- private View.OnDragListener getDraggedViewDragListener() {
- return (view, dragEvent) -> {
+ private void onSystemDragStarted() {
+ mIsSystemDragInProgress = true;
+ mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> {
switch (dragEvent.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
- view.setVisibility(INVISIBLE);
+ // Return true to tell system we are interested in events, so we get DRAG_ENDED.
return true;
case DragEvent.ACTION_DRAG_ENDED:
- view.setVisibility(VISIBLE);
- view.setOnDragListener(null);
+ mIsSystemDragInProgress = false;
+ maybeOnDragEnd();
return true;
}
return false;
- };
+ });
+ }
+
+ @Override
+ public boolean isDragging() {
+ return super.isDragging() || mIsSystemDragInProgress;
+ }
+
+ private void maybeOnDragEnd() {
+ if (!isDragging()) {
+ ((View) mDragObject.originalView).setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ protected void callOnDragEnd() {
+ super.callOnDragEnd();
+ maybeOnDragEnd();
+ }
+
+ @Override
+ protected float getX(MotionEvent ev) {
+ // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
+ // we start at the correct position even though touch down is on the smaller DragLayer size.
+ return ev.getRawX();
+ }
+
+ @Override
+ protected float getY(MotionEvent ev) {
+ // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
+ // we start at the correct position even though touch down is on the smaller DragLayer size.
+ return ev.getRawY();
+ }
+
+ @Override
+ protected Point getClampedDragLayerPos(float x, float y) {
+ // No need to clamp, as we will take up the entire screen.
+ mTmpPoint.set(Math.round(x), Math.round(y));
+ return mTmpPoint;
+ }
+
+ @Override
+ protected void exitDrag() {
+ if (mDragObject != null) {
+ mActivity.getDragLayer().removeView(mDragObject.dragView);
+ }
+ }
+
+ @Override
+ public void addDropTarget(DropTarget target) {
+ // No-op as Taskbar currently doesn't support any drop targets internally.
+ // Note: if we do add internal DropTargets, we'll still need to ignore Folder.
+ }
+
+ @Override
+ protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
+ return null;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 45ec911..cd1baf7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,14 +18,16 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.systemui.shared.system.ViewTreeObserverWrapper;
@@ -37,14 +39,13 @@
*/
public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
- private final int mFolderMargin;
private final Paint mTaskbarBackgroundPaint;
-
- private TaskbarIconController.Callbacks mControllerCallbacks;
- private TaskbarView mTaskbarView;
-
private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
+ private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
+
+ private float mTaskbarBackgroundOffset;
+
public TaskbarDragLayer(@NonNull Context context) {
this(context, null);
}
@@ -61,25 +62,25 @@
public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, 1 /* alphaChannelCount */);
- mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin);
mTaskbarBackgroundPaint = new Paint();
mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
+ mTaskbarBackgroundPaint.setAlpha(0);
+ }
+
+ public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
+ mControllerCallbacks = callbacks;
recreateControllers();
}
@Override
public void recreateControllers() {
- mControllers = new TouchController[0];
- }
-
- public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) {
- mControllerCallbacks = callbacks;
- mTaskbarView = taskbarView;
+ mControllers = new TouchController[] {mActivity.getDragController()};
}
private void onComputeTaskbarInsets(InsetsInfo insetsInfo) {
if (mControllerCallbacks != null) {
mControllerCallbacks.updateInsetsTouchability(insetsInfo);
+ mControllerCallbacks.updateContentInsets(insetsInfo.contentInsets);
}
}
@@ -108,12 +109,6 @@
return true;
}
- public void updateImeBarVisibilityAlpha(float alpha) {
- if (mControllerCallbacks != null) {
- mControllerCallbacks.updateImeBarVisibilityAlpha(alpha);
- }
- }
-
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
@@ -124,22 +119,14 @@
@Override
protected void dispatchDraw(Canvas canvas) {
- canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(),
+ float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
+ * (1f - mTaskbarBackgroundOffset);
+ canvas.drawRect(0, canvas.getHeight() - backgroundHeight, canvas.getWidth(),
canvas.getHeight(), mTaskbarBackgroundPaint);
super.dispatchDraw(canvas);
}
/**
- * @return Bounds (in our coordinates) where an opened Folder can display.
- */
- protected Rect getFolderBoundingBox() {
- Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight());
- boundingBox.inset(mFolderMargin, mFolderMargin);
- return boundingBox;
- }
-
-
- /**
* Sets the alpha of the background color behind all the Taskbar contents.
* @param alpha 0 is fully transparent, 1 is fully opaque.
*/
@@ -147,4 +134,19 @@
mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
invalidate();
}
+
+ /**
+ * Sets the translation of the background color behind all the Taskbar contents.
+ * @param offset 0 is fully onscreen, 1 is fully offscreen.
+ */
+ protected void setTaskbarBackgroundOffset(float offset) {
+ mTaskbarBackgroundOffset = offset;
+ invalidate();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
+ return super.dispatchTouchEvent(ev);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
new file mode 100644
index 0000000..df89285
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 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;
+
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
+
+import android.content.res.Resources;
+import android.graphics.Rect;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
+
+/**
+ * Handles properties/data collection, then passes the results to TaskbarDragLayer to render.
+ */
+public class TaskbarDragLayerController {
+
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarDragLayer mTaskbarDragLayer;
+ private final int mFolderMargin;
+ // Alpha properties for taskbar background.
+ private final AnimatedFloat mBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ private final AnimatedFloat mBgNavbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ private final AnimatedFloat mKeyguardBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
+ // Translation property for taskbar background.
+ private final AnimatedFloat mBgOffset = new AnimatedFloat(this::updateBackgroundOffset);
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+
+ public TaskbarDragLayerController(TaskbarActivityContext activity,
+ TaskbarDragLayer taskbarDragLayer) {
+ mActivity = activity;
+ mTaskbarDragLayer = taskbarDragLayer;
+ final Resources resources = mTaskbarDragLayer.getResources();
+ mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
+ mKeyguardBgTaskbar.value = 1;
+ }
+
+ public void onDestroy() {
+ mTaskbarDragLayer.onDestroy();
+ }
+
+ /**
+ * @return Bounds (in TaskbarDragLayer coordinates) where an opened Folder can display.
+ */
+ public Rect getFolderBoundingBox() {
+ Rect boundingBox = new Rect(0, 0, mTaskbarDragLayer.getWidth(),
+ mTaskbarDragLayer.getHeight() - mActivity.getDeviceProfile().taskbarSize);
+ boundingBox.inset(mFolderMargin, mFolderMargin);
+ return boundingBox;
+ }
+
+ public AnimatedFloat getTaskbarBackgroundAlpha() {
+ return mBgTaskbar;
+ }
+
+ public AnimatedFloat getNavbarBackgroundAlpha() {
+ return mBgNavbar;
+ }
+
+ public AnimatedFloat getKeyguardBgTaskbar() {
+ return mKeyguardBgTaskbar;
+ }
+
+ public AnimatedFloat getTaskbarBackgroundOffset() {
+ return mBgOffset;
+ }
+
+ private void updateBackgroundAlpha() {
+ mTaskbarDragLayer.setTaskbarBackgroundAlpha(
+ Math.max(mBgNavbar.value, mBgTaskbar.value * mKeyguardBgTaskbar.value)
+ );
+ }
+
+ private void updateBackgroundOffset() {
+ mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
+ }
+
+ /**
+ * Callbacks for {@link TaskbarDragLayer} to interact with its controller.
+ */
+ public class TaskbarDragLayerCallbacks {
+
+ /**
+ * Called to update the touchable insets.
+ * @see InsetsInfo#setTouchableInsets(int)
+ */
+ public void updateInsetsTouchability(InsetsInfo insetsInfo) {
+ insetsInfo.touchableRegion.setEmpty();
+ if (mActivity.isThreeButtonNav()) {
+ // Always have nav buttons be touchable
+ mControllers.navbarButtonsViewController.addVisibleButtonsRegion(
+ mTaskbarDragLayer, insetsInfo.touchableRegion);
+ }
+
+ if (mTaskbarDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ } else if (mControllers.navbarButtonsViewController.isImeVisible()) {
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ } else if (!mControllers.uiController.isTaskbarTouchable()) {
+ // Let touches pass through us.
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ } else if (mControllers.taskbarViewController.areIconsVisible()) {
+ // Buttons are visible, take over the full taskbar area
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
+ } else {
+ insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ }
+ }
+
+ /**
+ * Called to update the {@link InsetsInfo#contentInsets}.
+ */
+ public void updateContentInsets(Rect outContentInsets) {
+ mControllers.uiController.updateContentInsets(outContentInsets);
+ }
+
+ /**
+ * Called when a child is removed from TaskbarDragLayer.
+ */
+ public void onDragLayerViewRemoved() {
+ if (AbstractFloatingView.getOpenView(mActivity, TYPE_ALL) == null) {
+ mActivity.setTaskbarWindowFullscreen(false);
+ }
+ }
+
+ /**
+ * Returns how tall the background should be drawn at the bottom of the screen.
+ */
+ public int getTaskbarBackgroundHeight() {
+ return mActivity.getDeviceProfile().taskbarSize;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
new file mode 100644
index 0000000..cf28eff
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 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;
+
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragView;
+
+/**
+ * A DragView drawn/used by the Taskbar. Note that this is only for the internal drag-and-drop,
+ * while the pre-drag is still in progress (i.e. when the long press popup is still open). After
+ * that ends, we switch to a system drag and drop view instead.
+ */
+public class TaskbarDragView extends DragView<TaskbarActivityContext> {
+ public TaskbarDragView(TaskbarActivityContext launcher, Drawable drawable, int registrationX,
+ int registrationY, float initialScale, float scaleOnDrop, float finalScaleDps) {
+ super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
+ finalScaleDps);
+ }
+
+ @Override
+ public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+ Runnable onAnimationEnd = () -> {
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ mActivity.getDragLayer().removeView(this);
+ };
+
+ duration = Math.max(duration,
+ getResources().getInteger(R.integer.config_dropAnimMinDuration));
+
+ animate()
+ .translationX(toTouchX - mRegistrationX)
+ .translationY(toTouchY - mRegistrationY)
+ .scaleX(mScaleOnDrop)
+ .scaleY(mScaleOnDrop)
+ .withEndAction(onAnimationEnd)
+ .setDuration(duration)
+ .start();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
deleted file mode 100644
index 91cf7ef..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 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;
-
-import android.view.View;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.Hotseat;
-import com.android.launcher3.ShortcutAndWidgetContainer;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.ItemInfo;
-
-import java.util.function.Consumer;
-
-/**
- * Works with TaskbarController to update the TaskbarView's Hotseat items.
- */
-public class TaskbarHotseatController {
-
- private final BaseQuickstepLauncher mLauncher;
- private final Hotseat mHotseat;
- private final Consumer<ItemInfo[]> mTaskbarCallbacks;
- private final int mNumHotseatIcons;
-
- private final DragController.DragListener mDragListener = new DragController.DragListener() {
- @Override
- public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
-
- @Override
- public void onDragEnd() {
- onHotseatUpdated();
- }
- };
-
- public TaskbarHotseatController(
- BaseQuickstepLauncher launcher, Consumer<ItemInfo[]> taskbarCallbacks) {
- mLauncher = launcher;
- mHotseat = mLauncher.getHotseat();
- mTaskbarCallbacks = taskbarCallbacks;
- mNumHotseatIcons = mLauncher.getDeviceProfile().numShownHotseatIcons;
- }
-
- protected void init() {
- mLauncher.getDragController().addDragListener(mDragListener);
- onHotseatUpdated();
- }
-
- protected void cleanup() {
- mLauncher.getDragController().removeDragListener(mDragListener);
- }
-
- /**
- * Called when any Hotseat item changes, and reports the new list of items to TaskbarController.
- */
- protected void onHotseatUpdated() {
- ShortcutAndWidgetContainer shortcutsAndWidgets = mHotseat.getShortcutsAndWidgets();
- ItemInfo[] hotseatItemInfos = new ItemInfo[mNumHotseatIcons];
- for (int i = 0; i < shortcutsAndWidgets.getChildCount(); i++) {
- View child = shortcutsAndWidgets.getChildAt(i);
- Object tag = shortcutsAndWidgets.getChildAt(i).getTag();
- if (tag instanceof ItemInfo) {
- ItemInfo itemInfo = (ItemInfo) tag;
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- // Since the hotseat might be laid out vertically or horizontally, use whichever
- // index is higher.
- int index = Math.max(lp.cellX, lp.cellY);
- if (0 <= index && index < hotseatItemInfos.length) {
- hotseatItemInfos[index] = itemInfo;
- }
- }
- }
-
- mTaskbarCallbacks.accept(hotseatItemInfos);
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
deleted file mode 100644
index 683a5b9..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 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;
-
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME;
-import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION;
-
-import android.graphics.Rect;
-import android.inputmethodservice.InputMethodService;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AlphaUpdateListener;
-import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
-
-/**
- * Controller for taskbar icon UI
- */
-public class TaskbarIconController {
-
- private final Rect mTempRect = new Rect();
-
- private final TaskbarActivityContext mActivity;
- private final TaskbarDragLayer mDragLayer;
-
- private final TaskbarView mTaskbarView;
- private final ImeBarView mImeBarView;
-
- @NonNull
- private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
-
- TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
- mActivity = activity;
- mDragLayer = dragLayer;
- mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
- mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
- }
-
- public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
- mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
- mUIController.alignRealHotseatWithTaskbar());
-
- ButtonProvider buttonProvider = new ButtonProvider(mActivity);
- mImeBarView.init(buttonProvider);
- mTaskbarView.construct(clickListener, longClickListener, buttonProvider);
- mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
-
- mDragLayer.init(new Callbacks(), mTaskbarView);
- }
-
- public void onDestroy() {
- mDragLayer.onDestroy();
- }
-
- public void setUIController(@NonNull TaskbarUIController uiController) {
- mUIController = uiController;
- }
-
- /**
- * 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) {
- if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) {
- return;
- }
-
- mImeBarView.setImeSwitcherVisibility(showImeSwitcher);
- setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0);
- }
-
- /**
- * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
- */
- public void setImeIsVisible(boolean isImeVisible) {
- mTaskbarView.setTouchesEnabled(!isImeVisible);
- mUIController.onImeVisible(mDragLayer, isImeVisible);
- }
-
- /**
- * Callbacks for {@link TaskbarDragLayer} to interact with the icon controller
- */
- public class Callbacks {
-
- /**
- * Called to update the touchable insets
- */
- public void updateInsetsTouchability(InsetsInfo insetsInfo) {
- insetsInfo.touchableRegion.setEmpty();
- if (mDragLayer.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (mImeBarView.getVisibility() == VISIBLE) {
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
- } else if (!mUIController.isTaskbarTouchable()) {
- // Let touches pass through us.
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- } else if (mTaskbarView.areIconsVisible()) {
- // Buttons are visible, take over the full taskbar area
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME);
- } else {
- if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) {
- mDragLayer.getDescendantRectRelativeToSelf(
- mTaskbarView.mSystemButtonContainer, mTempRect);
- insetsInfo.touchableRegion.set(mTempRect);
- }
- insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
- }
-
- // TaskbarContainerView provides insets to other apps based on contentInsets. These
- // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g.
- // to show a floating view like Folder. Thus, we set the contentInsets to be where
- // mTaskbarView is, since its position never changes and insets rather than overlays.
- insetsInfo.contentInsets.left = mTaskbarView.getLeft();
- insetsInfo.contentInsets.top = mTaskbarView.getTop();
- insetsInfo.contentInsets.right = mDragLayer.getWidth() - mTaskbarView.getRight();
- insetsInfo.contentInsets.bottom = mDragLayer.getHeight() - mTaskbarView.getBottom();
- }
-
- public void onDragLayerViewRemoved() {
- int count = mDragLayer.getChildCount();
- // Ensure no other children present (like Folders, etc)
- for (int i = 0; i < count; i++) {
- View v = mDragLayer.getChildAt(i);
- if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) {
- return;
- }
- }
- mActivity.setTaskbarWindowFullscreen(false);
- }
-
- public void updateImeBarVisibilityAlpha(float alpha) {
- if (!mActivity.canShowNavButtons()) {
- // TODO Remove sysui IME bar for gesture nav as well
- return;
- }
- mImeBarView.setAlpha(alpha);
- mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE);
- }
- }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
new file mode 100644
index 0000000..a2039b6
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
@@ -0,0 +1,100 @@
+package com.android.launcher3.taskbar;
+
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
+
+import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.view.View;
+
+/**
+ * Controller for managing keyguard state for taskbar
+ */
+public class TaskbarKeyguardController {
+
+ private static final int KEYGUARD_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING |
+ SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING;
+
+ private final TaskbarActivityContext mContext;
+ private int mDisabledNavIcons;
+ private int mKeyguardSysuiFlags;
+ private boolean mBouncerShowing;
+ private NavbarButtonsViewController mNavbarButtonsViewController;
+ private final KeyguardManager mKeyguardManager;
+ private boolean mIsScreenOff;
+
+ private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIsScreenOff = true;
+ }
+ };
+
+ public TaskbarKeyguardController(TaskbarActivityContext context) {
+ mContext = context;
+ mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+ }
+
+ public void init(NavbarButtonsViewController navbarButtonUIController) {
+ mNavbarButtonsViewController = navbarButtonUIController;
+ mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+
+ public void updateStateForSysuiFlags(int systemUiStateFlags) {
+ boolean bouncerShowing = (systemUiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0;
+ boolean keyguardShowing = (systemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING)
+ != 0;
+ boolean dozing = (systemUiStateFlags & SYSUI_STATE_DEVICE_DOZING) != 0;
+
+ int interestingKeyguardFlags = systemUiStateFlags & KEYGUARD_SYSUI_FLAGS;
+ if (interestingKeyguardFlags == mKeyguardSysuiFlags) {
+ return;
+ }
+ mKeyguardSysuiFlags = interestingKeyguardFlags;
+
+ mBouncerShowing = bouncerShowing;
+
+ mNavbarButtonsViewController.setKeyguardVisible(keyguardShowing || dozing);
+ updateIconsForBouncer();
+ }
+
+ public boolean isScreenOff() {
+ return mIsScreenOff;
+ }
+
+ public void setScreenOn() {
+ mIsScreenOff = false;
+ }
+
+ public void disableNavbarElements(int state1, int state2) {
+ if (mDisabledNavIcons == state1) {
+ // no change
+ return;
+ }
+ mDisabledNavIcons = state1;
+ updateIconsForBouncer();
+ }
+
+ /**
+ * Hides/shows taskbar when keyguard is up
+ */
+ private void updateIconsForBouncer() {
+ boolean disableBack = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_BACK) != 0;
+ boolean disableRecent = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_RECENT) != 0;
+ boolean disableHome = (mDisabledNavIcons & View.STATUS_BAR_DISABLE_HOME) != 0;
+ boolean onlyBackEnabled = !disableBack && disableRecent && disableHome;
+
+ boolean showBackForBouncer = onlyBackEnabled &&
+ mKeyguardManager.isDeviceSecure() &&
+ mBouncerShowing;
+ mNavbarButtonsViewController.setBackForBouncer(showBackForBouncer);
+ }
+
+ public void onDestroy() {
+ mContext.unregisterReceiver(mScreenOffReceiver);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index d026bfb..45eabed 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -21,13 +21,12 @@
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import android.content.Context;
import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
import android.view.Display;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseQuickstepLauncher;
@@ -38,10 +37,11 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
/**
- * Class to manager taskbar lifecycle
+ * Class to manage taskbar lifecycle
*/
public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
SysUINavigationMode.NavigationModeChangeListener {
@@ -53,6 +53,11 @@
private TaskbarActivityContext mTaskbarActivityContext;
private BaseQuickstepLauncher mLauncher;
+ /**
+ * Cache a copy here so we can initialize state whenever taskbar is recreated, since
+ * this class does not get re-initialized w/ new taskbars.
+ */
+ private int mSysuiStateFlags;
private static final int CHANGE_FLAGS =
CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
@@ -100,29 +105,43 @@
}
/**
- * Sets or clears a launcher to act as taskbar callback
+ * Sets a launcher to act as taskbar callback
*/
- public void setLauncher(@Nullable BaseQuickstepLauncher launcher) {
+ public void setLauncher(@NonNull BaseQuickstepLauncher launcher) {
mLauncher = launcher;
if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setUIController(mLauncher == null
- ? TaskbarUIController.DEFAULT
- : new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
+ mTaskbarActivityContext.setUIController(
+ new LauncherTaskbarUIController(launcher, mTaskbarActivityContext));
+ }
+ }
+
+ /**
+ * Clears a previously set Launcher
+ */
+ public void clearLauncher(@NonNull BaseQuickstepLauncher launcher) {
+ if (mLauncher == launcher) {
+ mLauncher = null;
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
+ }
}
}
private void recreateTaskbar() {
destroyExistingTaskbar();
- if (!FeatureFlags.ENABLE_TASKBAR.get()) {
+
+ DeviceProfile dp =
+ mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
+
+ boolean isTaskBarEnabled =
+ FeatureFlags.ENABLE_TASKBAR.get() && dp != null && dp.isTaskbarPresent;
+
+ if (!isTaskBarEnabled) {
+ SystemUiProxy.INSTANCE.get(mContext)
+ .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
return;
}
- if (!mUserUnlocked) {
- return;
- }
- DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
- if (!dp.isTaskbarPresent) {
- return;
- }
+
mTaskbarActivityContext = new TaskbarActivityContext(
mContext, dp.copy(mContext), mNavButtonController);
mTaskbarActivityContext.init();
@@ -130,31 +149,35 @@
mTaskbarActivityContext.setUIController(
new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext));
}
+ onSysuiFlagsChangedInternal(mSysuiStateFlags, true /* forceUpdate */);
}
- /**
- * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
- * @param systemUiStateFlags The latest SystemUiStateFlags
- */
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
- boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
+ onSysuiFlagsChangedInternal(systemUiStateFlags, false /* forceUpdate */);
+ }
+
+ private void onSysuiFlagsChangedInternal(int systemUiStateFlags, boolean forceUpdate) {
+ mSysuiStateFlags = systemUiStateFlags;
if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.setImeIsVisible(isImeVisible);
+ mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, forceUpdate);
}
}
- /**
- * 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
- * @param backDisposition Used to determine back button behavior for software keyboard
- * See BACK_DISPOSITION_* constants in {@link InputMethodService}
- */
- public void updateImeStatus(int displayId, int vis, int backDisposition,
- boolean showImeSwitcher) {
+ public void onRotationProposal(int rotation, boolean isValid) {
if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher);
+ mTaskbarActivityContext.onRotationProposal(rotation, isValid);
+ }
+ }
+
+ public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate);
+ }
+ }
+
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior);
}
}
@@ -166,4 +189,8 @@
mDisplayController.removeChangeListener(this);
mSysUINavigationMode.removeModeChangeListener(this);
}
+
+ public @Nullable TaskbarActivityContext getCurrentActivityContext() {
+ return mTaskbarActivityContext;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
new file mode 100644
index 0000000..fc5abd0
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 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;
+
+import android.util.SparseArray;
+import android.view.View;
+
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Launcher model Callbacks for rendering taskbar.
+ */
+public class TaskbarModelCallbacks implements
+ BgDataModel.Callbacks, LauncherBindableItemsContainer {
+
+ private final SparseArray<ItemInfo> mHotseatItems = new SparseArray<>();
+ private List<ItemInfo> mPredictedItems = Collections.emptyList();
+
+ private final TaskbarActivityContext mContext;
+ private final TaskbarView mContainer;
+
+ private boolean mBindInProgress = false;
+
+ public TaskbarModelCallbacks(
+ TaskbarActivityContext context, TaskbarView container) {
+ mContext = context;
+ mContainer = container;
+ }
+
+ @Override
+ public void startBinding() {
+ mBindInProgress = true;
+ mHotseatItems.clear();
+ mPredictedItems = Collections.emptyList();
+ }
+
+ @Override
+ public void finishBindingItems(IntSet pagesBoundFirst) {
+ mBindInProgress = false;
+ commitItemsToUI();
+ }
+
+ @Override
+ public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
+ ArrayList<ItemInfo> addAnimated) {
+ boolean add1 = handleItemsAdded(addNotAnimated);
+ boolean add2 = handleItemsAdded(addAnimated);
+ if (add1 || add2) {
+ commitItemsToUI();
+ }
+ }
+
+ @Override
+ public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) {
+ if (handleItemsAdded(shortcuts)) {
+ commitItemsToUI();
+ }
+ }
+
+ private boolean handleItemsAdded(List<ItemInfo> items) {
+ boolean modified = false;
+ for (ItemInfo item : items) {
+ if (item.container == Favorites.CONTAINER_HOTSEAT) {
+ mHotseatItems.put(item.screenId, item);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+
+ @Override
+ public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
+ updateWorkspaceItems(updated, mContext);
+ }
+
+ @Override
+ public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
+ updateRestoreItems(updates, mContext);
+ }
+
+ @Override
+ public void mapOverItems(ItemOperator op) {
+ final int itemCount = mContainer.getChildCount();
+ for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
+ View item = mContainer.getChildAt(itemIdx);
+ if (op.evaluate((ItemInfo) item.getTag(), item)) {
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) {
+ if (handleItemsRemoved(matcher)) {
+ commitItemsToUI();
+ }
+ }
+
+ private boolean handleItemsRemoved(ItemInfoMatcher matcher) {
+ boolean modified = false;
+ for (int i = mHotseatItems.size() - 1; i >= 0; i--) {
+ if (matcher.matchesInfo(mHotseatItems.valueAt(i))) {
+ modified = true;
+ mHotseatItems.removeAt(i);
+ }
+ }
+ return modified;
+ }
+
+ @Override
+ public void bindItemsModified(List<ItemInfo> items) {
+ boolean removed = handleItemsRemoved(ItemInfoMatcher.ofItems(items));
+ boolean added = handleItemsAdded(items);
+ if (removed || added) {
+ commitItemsToUI();
+ }
+ }
+
+ @Override
+ public void bindExtraContainerItems(FixedContainerItems item) {
+ if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ mPredictedItems = item.items;
+ commitItemsToUI();
+ }
+ }
+
+ private void commitItemsToUI() {
+ if (mBindInProgress) {
+ return;
+ }
+
+ ItemInfo[] hotseatItemInfos =
+ new ItemInfo[mContext.getDeviceProfile().numShownHotseatIcons];
+ int predictionSize = mPredictedItems.size();
+ int predictionNextIndex = 0;
+
+ for (int i = 0; i < hotseatItemInfos.length; i++) {
+ hotseatItemInfos[i] = mHotseatItems.get(i);
+ if (hotseatItemInfos[i] == null && predictionNextIndex < predictionSize) {
+ hotseatItemInfos[i] = mPredictedItems.get(predictionNextIndex);
+ hotseatItemInfos[i].screenId = i;
+ predictionNextIndex++;
+ }
+ }
+ mContainer.updateHotseatItems(hotseatItemInfos);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index 3b5afad..dd7c403 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -23,6 +23,8 @@
import androidx.annotation.IntDef;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TouchInteractionService;
@@ -44,7 +46,9 @@
BUTTON_BACK,
BUTTON_HOME,
BUTTON_RECENTS,
- BUTTON_IME_SWITCH
+ BUTTON_IME_SWITCH,
+ BUTTON_A11Y,
+ BUTTON_A11Y_LONG_CLICK
})
public @interface TaskbarButton {}
@@ -53,6 +57,8 @@
static final int BUTTON_HOME = BUTTON_BACK << 1;
static final int BUTTON_RECENTS = BUTTON_HOME << 1;
static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
+ static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
+ static final int BUTTON_A11Y_LONG_CLICK = BUTTON_A11Y << 1;
private final TouchInteractionService mService;
@@ -74,6 +80,12 @@
case BUTTON_IME_SWITCH:
showIMESwitcher();
break;
+ case BUTTON_A11Y:
+ notifyImeClick(false /* longClick */);
+ break;
+ case BUTTON_A11Y_LONG_CLICK:
+ notifyImeClick(true /* longClick */);
+ break;
}
}
@@ -84,8 +96,8 @@
}
private void navigateToOverview() {
- mService.getOverviewCommandHelper()
- .addCommand(OverviewCommandHelper.TYPE_SHOW);
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
+ mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
}
private void executeBack() {
@@ -97,4 +109,13 @@
.showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
DEFAULT_DISPLAY);
}
+
+ private void notifyImeClick(boolean longClick) {
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+ if (longClick) {
+ systemUiProxy.notifyAccessibilityButtonLongClicked();
+ } else {
+ systemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
new file mode 100644
index 0000000..0efec53
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 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;
+
+import static android.view.HapticFeedbackConstants.LONG_PRESS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.annotation.Nullable;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
+
+/**
+ * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
+ * create a cohesive animation between stashed/unstashed states.
+ */
+public class TaskbarStashController {
+
+ /**
+ * How long to stash/unstash when manually invoked via long press.
+ */
+ private static final long TASKBAR_STASH_DURATION = 300;
+
+ /**
+ * The scale TaskbarView animates to when being stashed.
+ */
+ private static final float STASHED_TASKBAR_SCALE = 0.5f;
+
+ /**
+ * How long the hint animation plays, starting on motion down.
+ */
+ private static final long TASKBAR_HINT_STASH_DURATION =
+ ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
+
+ /**
+ * The scale that TaskbarView animates to when hinting towards the stashed state.
+ */
+ private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
+
+ /**
+ * The scale that the stashed handle animates to when hinting towards the unstashed state.
+ */
+ private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
+
+ /**
+ * The SharedPreferences key for whether user has manually stashed the taskbar.
+ */
+ private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
+
+ /**
+ * Whether taskbar should be stashed out of the box.
+ */
+ private static final boolean DEFAULT_STASHED_PREF = false;
+
+ private final TaskbarActivityContext mActivity;
+ private final SharedPreferences mPrefs;
+ private final int mStashedHeight;
+ private final int mUnstashedHeight;
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+ // Taskbar background properties.
+ private AnimatedFloat mTaskbarBackgroundOffset;
+ // TaskbarView icon properties.
+ private AlphaProperty mIconAlphaForStash;
+ private AnimatedFloat mIconScaleForStash;
+ private AnimatedFloat mIconTranslationYForStash;
+ // Stashed handle properties.
+ private AnimatedFloat mTaskbarStashedHandleAlpha;
+ private AnimatedFloat mTaskbarStashedHandleHintScale;
+
+ /** Whether the user has manually invoked taskbar stashing, which we persist. */
+ private boolean mIsStashedInApp;
+ /** Whether we are currently visually stashed (might change based on launcher state). */
+ private boolean mIsStashed = false;
+
+ private @Nullable AnimatorSet mAnimator;
+
+ public TaskbarStashController(TaskbarActivityContext activity) {
+ mActivity = activity;
+ mPrefs = Utilities.getPrefs(mActivity);
+ final Resources resources = mActivity.getResources();
+ mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
+ mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+
+ TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
+ mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
+
+ TaskbarViewController taskbarViewController = controllers.taskbarViewController;
+ mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
+ TaskbarViewController.ALPHA_INDEX_STASH);
+ mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
+ mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
+
+ StashedHandleViewController stashedHandleController =
+ controllers.stashedHandleViewController;
+ mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha();
+ mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
+
+ mIsStashedInApp = supportsStashing()
+ && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
+
+ SystemUiProxy.INSTANCE.get(mActivity)
+ .notifyTaskbarStatus(/* visible */ true, /* stashed */ mIsStashedInApp);
+ }
+
+ /**
+ * Returns whether the user can manually stash the taskbar based on the current device state.
+ */
+ private boolean supportsStashing() {
+ return !mActivity.isThreeButtonNav()
+ && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || supportsStashingForTests());
+ }
+
+ private boolean supportsStashingForTests() {
+ // TODO: enable this for tests that specifically check stash/unstash behavior.
+ return false;
+ }
+
+ /**
+ * Returns whether the taskbar is currently visually stashed.
+ */
+ public boolean isStashed() {
+ return mIsStashed;
+ }
+
+ /**
+ * Returns whether the user has manually stashed the taskbar in apps.
+ */
+ public boolean isStashedInApp() {
+ return mIsStashedInApp;
+ }
+
+ public int getContentHeight() {
+ return isStashed() ? mStashedHeight : mUnstashedHeight;
+ }
+
+ public int getStashedHeight() {
+ return mStashedHeight;
+ }
+
+ /**
+ * Should be called when long pressing the nav region when taskbar is present.
+ * @return Whether taskbar was stashed and now is unstashed.
+ */
+ public boolean onLongPressToUnstashTaskbar() {
+ if (!isStashed()) {
+ // We only listen for long press on the nav region to unstash the taskbar. To stash the
+ // taskbar, we use an OnLongClickListener on TaskbarView instead.
+ return false;
+ }
+ if (updateAndAnimateIsStashedInApp(false)) {
+ mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates whether we should stash the taskbar when in apps, and animates to the changed state.
+ * @return Whether we started an animation to either be newly stashed or unstashed.
+ */
+ public boolean updateAndAnimateIsStashedInApp(boolean isStashedInApp) {
+ if (!supportsStashing()) {
+ return false;
+ }
+ if (mIsStashedInApp != isStashedInApp) {
+ boolean wasStashed = mIsStashedInApp;
+ mIsStashedInApp = isStashedInApp;
+ mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, mIsStashedInApp).apply();
+ boolean isStashed = mIsStashedInApp;
+ if (wasStashed != isStashed) {
+ SystemUiProxy.INSTANCE.get(mActivity)
+ .notifyTaskbarStatus(/* visible */ true, /* stashed */ isStashed);
+ createAnimToIsStashed(isStashed, TASKBAR_STASH_DURATION).start();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Starts an animation to the new stashed state with a default duration.
+ */
+ public void animateToIsStashed(boolean isStashed) {
+ animateToIsStashed(isStashed, TASKBAR_STASH_DURATION);
+ }
+
+ /**
+ * Starts an animation to the new stashed state with the specified duration.
+ */
+ public void animateToIsStashed(boolean isStashed, long duration) {
+ createAnimToIsStashed(isStashed, duration).start();
+ }
+
+ private Animator createAnimToIsStashed(boolean isStashed, long duration) {
+ AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
+ // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
+ AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
+ AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
+
+ final float firstHalfDurationScale;
+ final float secondHalfDurationScale;
+
+ if (isStashed) {
+ firstHalfDurationScale = 0.75f;
+ secondHalfDurationScale = 0.5f;
+ final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
+
+ fullLengthAnimatorSet.playTogether(
+ mTaskbarBackgroundOffset.animateToValue(1),
+ mIconTranslationYForStash.animateToValue(stashTranslation)
+ );
+ firstHalfAnimatorSet.playTogether(
+ mIconAlphaForStash.animateToValue(0),
+ mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
+ );
+ secondHalfAnimatorSet.playTogether(
+ mTaskbarStashedHandleAlpha.animateToValue(1)
+ );
+ } else {
+ firstHalfDurationScale = 0.5f;
+ secondHalfDurationScale = 0.75f;
+
+ fullLengthAnimatorSet.playTogether(
+ mTaskbarBackgroundOffset.animateToValue(0),
+ mIconScaleForStash.animateToValue(1),
+ mIconTranslationYForStash.animateToValue(0)
+ );
+ firstHalfAnimatorSet.playTogether(
+ mTaskbarStashedHandleAlpha.animateToValue(0)
+ );
+ secondHalfAnimatorSet.playTogether(
+ mIconAlphaForStash.animateToValue(1)
+ );
+ }
+
+ Animator stashedHandleRevealAnim = mControllers.stashedHandleViewController
+ .createRevealAnimToIsStashed(isStashed);
+ if (stashedHandleRevealAnim != null) {
+ fullLengthAnimatorSet.play(stashedHandleRevealAnim);
+ }
+ // Return the stashed handle to its default scale in case it was changed as part of the
+ // feedforward hint. Note that the reveal animation above also visually scales it.
+ fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
+
+ fullLengthAnimatorSet.setDuration(duration);
+ firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
+ secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
+ secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
+
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = new AnimatorSet();
+ mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
+ secondHalfAnimatorSet);
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mIsStashed = isStashed;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimator = null;
+ }
+ });
+ return mAnimator;
+ }
+
+ /**
+ * Creates and starts a partial stash animation, hinting at the new state that will trigger when
+ * long press is detected.
+ * @param animateForward Whether we are going towards the new stashed state or returning to the
+ * unstashed state.
+ */
+ public void startStashHint(boolean animateForward) {
+ if (isStashed() || !supportsStashing()) {
+ // Already stashed, no need to hint in that direction.
+ return;
+ }
+ mIconScaleForStash.animateToValue(
+ animateForward ? STASHED_TASKBAR_HINT_SCALE : 1)
+ .setDuration(TASKBAR_HINT_STASH_DURATION).start();
+ }
+
+ /**
+ * Creates and starts a partial unstash animation, hinting at the new state that will trigger
+ * when long press is detected.
+ * @param animateForward Whether we are going towards the new unstashed state or returning to
+ * the stashed state.
+ */
+ public void startUnstashHint(boolean animateForward) {
+ if (!isStashed()) {
+ // Already unstashed, no need to hint in that direction.
+ return;
+ }
+ mTaskbarStashedHandleHintScale.animateToValue(
+ animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
+ .setDuration(TASKBAR_HINT_STASH_DURATION).start();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index a701aae..edd2a22 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -16,11 +16,8 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherState.TASKBAR;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
@@ -28,29 +25,26 @@
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.quickstep.AnimatedFloat;
+import com.android.quickstep.SystemUiProxy;
/**
- * StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar
- * isn't present (i.e. {@link #setAnimationController} is never called).
+ * StateHandler to animate Taskbar according to Launcher's state machine.
*/
public class TaskbarStateHandler implements StateManager.StateHandler<LauncherState> {
private final BaseQuickstepLauncher mLauncher;
- // Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything.
- private @Nullable TaskbarAnimationController mAnimationController = null;
+ private AnimatedFloat mNavbarButtonAlpha = new AnimatedFloat(this::updateNavbarButtonAlpha);
public TaskbarStateHandler(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
}
- public void setAnimationController(TaskbarAnimationController callbacks) {
- mAnimationController = callbacks;
- }
-
@Override
public void setState(LauncherState state) {
setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER);
+ // Force update the alpha in case it was not initialized properly
+ updateNavbarButtonAlpha();
}
@Override
@@ -59,17 +53,19 @@
setState(toState, animation);
}
- private void setState(LauncherState toState, PropertySetter setter) {
- if (mAnimationController == null) {
- return;
- }
-
+ /**
+ * Sets the provided state
+ */
+ public void setState(LauncherState toState, PropertySetter setter) {
boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
- setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(),
- AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
- setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(),
- AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR);
- setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(),
- AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL);
+ // Make the nav bar visible in states that taskbar isn't visible.
+ // TODO: We should draw our own handle instead of showing the nav bar.
+ float navbarButtonAlpha = isTaskbarVisible ? 0f : 1f;
+ setter.setFloat(mNavbarButtonAlpha, AnimatedFloat.VALUE, navbarButtonAlpha, LINEAR);
+ }
+
+
+ private void updateNavbarButtonAlpha() {
+ SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(mNavbarButtonAlpha.value, false);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 50adead..260cedc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
+import android.graphics.Rect;
+
/**
* Base class for providing different taskbar UI
*/
@@ -22,12 +24,7 @@
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
- /**
- * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat.
- */
- public void alignRealHotseatWithTaskbar() { }
-
- protected void onCreate() { }
+ protected void init(TaskbarControllers taskbarControllers) { }
protected void onDestroy() { }
@@ -35,7 +32,5 @@
return true;
}
- protected void onImeVisible(TaskbarDragLayer container, boolean isVisible) {
- container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0);
- }
+ protected void updateContentInsets(Rect outContentInsets) { }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c6573a6..a4a92f7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,76 +15,54 @@
*/
package com.android.launcher3.taskbar;
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.util.AttributeSet;
-import android.view.DragEvent;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
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.uioverrides.ApiWrapper;
import com.android.launcher3.views.ActivityContext;
/**
* Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
*/
-public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
+public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable {
- private final int mIconTouchSize;
- private final boolean mIsRtl;
- private final int mTouchSlop;
- private final RectF mTempDelegateBounds = new RectF();
- private final RectF mDelegateSlopBounds = new RectF();
private final int[] mTempOutLocation = new int[2];
+ private final Rect mIconLayoutBounds = new Rect();
+ private final int mIconTouchSize;
private final int mItemMarginLeftRight;
+ private final int mItemPadding;
private final TaskbarActivityContext mActivityContext;
- // Initialized in TaskbarController constructor.
+ // Initialized in init.
+ private TaskbarViewController.TaskbarViewCallbacks mControllerCallbacks;
private View.OnClickListener mIconClickListener;
private View.OnLongClickListener mIconLongClickListener;
- LinearLayout mSystemButtonContainer;
- LinearLayout mHotseatIconsContainer;
-
- // Delegate touches to the closest view if within mIconTouchSize.
- private boolean mDelegateTargeted;
- private View mDelegateView;
// Prevents dispatching touches to children if true
private boolean mTouchEnabled = true;
- private boolean mIsDraggingItem;
// Only non-null when the corresponding Folder is open.
private @Nullable FolderIcon mLeaveBehindFolderIcon;
- /** Provider of buttons added to taskbar in 3 button nav */
- private ButtonProvider mButtonProvider;
-
- private boolean mDisableRelayout;
- private boolean mAreHolesAllowed;
-
public TaskbarView(@NonNull Context context) {
this(context, null);
}
@@ -105,85 +83,76 @@
Resources resources = getResources();
mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
- mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
- mIsRtl = Utilities.isRtl(resources);
- mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+ int actualIconSize = mActivityContext.getDeviceProfile().iconSizePx;
+
+ // We layout the icons to be of mIconTouchSize in width and height
+ mItemMarginLeftRight = actualMargin - (mIconTouchSize - actualIconSize) / 2;
+ mItemPadding = (mIconTouchSize - actualIconSize) / 2;
}
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSystemButtonContainer = findViewById(R.id.system_button_layout);
- mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
+ protected void init(TaskbarViewController.TaskbarViewCallbacks callbacks) {
+ mControllerCallbacks = callbacks;
+ mIconClickListener = mControllerCallbacks.getIconOnClickListener();
+ mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
+
+ setOnLongClickListener(mControllerCallbacks.getBackgroundOnLongClickListener());
}
- protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener,
- ButtonProvider buttonProvider) {
- mIconClickListener = clickListener;
- mIconLongClickListener = longClickListener;
- mButtonProvider = buttonProvider;
-
- if (mActivityContext.canShowNavButtons()) {
- createNavButtons();
- } else {
- mSystemButtonContainer.setVisibility(GONE);
+ private void removeAndRecycle(View view) {
+ removeView(view);
+ view.setOnClickListener(null);
+ view.setOnLongClickListener(null);
+ if (!(view.getTag() instanceof FolderInfo)) {
+ mActivityContext.getViewCache().recycleView(view.getSourceLayoutResId(), view);
}
-
- int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
- updateHotseatItems(new ItemInfo[numHotseatIcons]);
- }
-
- /**
- * Enables/disables empty icons in taskbar so that the layout matches with Launcher
- */
- public void setHolesAllowedInLayout(boolean areHolesAllowed) {
- if (mAreHolesAllowed != areHolesAllowed) {
- mAreHolesAllowed = areHolesAllowed;
- updateHotseatItemsVisibility();
- // TODO: Add animation
- }
- }
-
- private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) {
- if (mAreHolesAllowed != areHolesAllowed) {
- mAreHolesAllowed = areHolesAllowed;
- updateHotseatItemsVisibility();
- onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
- makeMeasureSpec(getMeasuredHeight(), EXACTLY));
- onLayout(false, getLeft(), getTop(), getRight(), getBottom());
- }
+ view.setTag(null);
}
/**
* Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
*/
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+ int nextViewIndex = 0;
+
for (int i = 0; i < hotseatItemInfos.length; i++) {
- ItemInfo hotseatItemInfo = hotseatItemInfos[
- !mIsRtl ? i : hotseatItemInfos.length - i - 1];
- View hotseatView = mHotseatIconsContainer.getChildAt(i);
+ ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+ if (hotseatItemInfo == null) {
+ continue;
+ }
// Replace any Hotseat views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isFolder = false;
- boolean needsReinflate = false;
- if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) {
+ if (hotseatItemInfo.isPredictedItem()) {
expectedLayoutResId = R.layout.taskbar_predicted_app_icon;
} else if (hotseatItemInfo instanceof FolderInfo) {
expectedLayoutResId = R.layout.folder_icon;
isFolder = true;
- // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so
- // if the info changes we need to reinflate. This should only happen if a new folder
- // is dragged to the position that another folder previously existed.
- needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo;
} else {
expectedLayoutResId = R.layout.taskbar_app_icon;
}
- if (hotseatView == null
- || hotseatView.getSourceLayoutResId() != expectedLayoutResId
- || needsReinflate) {
- mHotseatIconsContainer.removeView(hotseatView);
+
+ View hotseatView = null;
+ while (nextViewIndex < getChildCount()) {
+ hotseatView = getChildAt(nextViewIndex);
+
+ // see if the view can be reused
+ if ((hotseatView.getSourceLayoutResId() != expectedLayoutResId)
+ || (isFolder && (hotseatView.getTag() != hotseatItemInfo))) {
+ // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation,
+ // so if the info changes we need to reinflate. This should only happen if a new
+ // folder is dragged to the position that another folder previously existed.
+ removeAndRecycle(hotseatView);
+ hotseatView = null;
+ } else {
+ // View found
+ break;
+ }
+ }
+
+ if (hotseatView == null) {
if (isFolder) {
FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
@@ -193,10 +162,9 @@
} else {
hotseatView = inflate(expectedLayoutResId);
}
- int iconSize = mActivityContext.getDeviceProfile().iconSizePx;
- LayoutParams lp = new LayoutParams(iconSize, iconSize);
- lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
- mHotseatIconsContainer.addView(hotseatView, i, lp);
+ LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
+ hotseatView.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+ addView(hotseatView, nextViewIndex, lp);
}
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
@@ -204,29 +172,52 @@
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
((BubbleTextView) hotseatView).applyFromWorkspaceItem(
(WorkspaceItemInfo) hotseatItemInfo);
- hotseatView.setOnClickListener(mIconClickListener);
- hotseatView.setOnLongClickListener(mIconLongClickListener);
- } else if (isFolder) {
- hotseatView.setOnClickListener(mIconClickListener);
- hotseatView.setOnLongClickListener(mIconLongClickListener);
- } else {
- hotseatView.setOnClickListener(null);
- hotseatView.setOnLongClickListener(null);
- hotseatView.setTag(null);
}
- updateHotseatItemVisibility(hotseatView);
+ setClickAndLongClickListenersForIcon(hotseatView);
+ nextViewIndex++;
+ }
+ // Remove remaining views
+ while (nextViewIndex < getChildCount()) {
+ removeAndRecycle(getChildAt(nextViewIndex));
}
}
- protected void updateHotseatItemsVisibility() {
- for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) {
- updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i));
- }
+ /**
+ * Sets OnClickListener and OnLongClickListener for the given view.
+ */
+ public void setClickAndLongClickListenersForIcon(View icon) {
+ icon.setOnClickListener(mIconClickListener);
+ icon.setOnLongClickListener(mIconLongClickListener);
}
- private void updateHotseatItemVisibility(View hotseatView) {
- hotseatView.setVisibility(
- hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE));
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ int count = getChildCount();
+ int spaceNeeded = count * (mItemMarginLeftRight * 2 + mIconTouchSize);
+ int navSpaceNeeded = ApiWrapper.getHotseatEndOffset(getContext());
+ boolean layoutRtl = isLayoutRtl();
+ int iconEnd = right - (right - left - spaceNeeded) / 2;
+ boolean needMoreSpaceForNav = layoutRtl ?
+ navSpaceNeeded > (iconEnd - spaceNeeded) :
+ iconEnd > (right - navSpaceNeeded);
+ if (needMoreSpaceForNav) {
+ int offset = layoutRtl ?
+ navSpaceNeeded - (iconEnd - spaceNeeded) :
+ (right - navSpaceNeeded) - iconEnd;
+ iconEnd += offset;
+ }
+ // Layout the children
+ mIconLayoutBounds.right = iconEnd;
+ mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
+ mIconLayoutBounds.bottom = mIconLayoutBounds.top + mIconTouchSize;
+ for (int i = count; i > 0; i--) {
+ View child = getChildAt(i - 1);
+ iconEnd -= mItemMarginLeftRight;
+ int iconStart = iconEnd - mIconTouchSize;
+ child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
+ iconEnd = iconStart - mItemMarginLeftRight;
+ }
+ mIconLayoutBounds.left = iconEnd;
}
@Override
@@ -239,8 +230,11 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
- boolean handled = delegateTouchIfNecessary(event);
- return super.onTouchEvent(event) || handled;
+ if (!mTouchEnabled) {
+ return true;
+ }
+ mControllerCallbacks.onTouchEvent(event);
+ return super.onTouchEvent(event);
}
public void setTouchesEnabled(boolean touchEnabled) {
@@ -248,149 +242,18 @@
}
/**
- * User touched the Taskbar background. Determine whether the touch is close enough to a view
- * that we should forward the touches to it.
- * @return Whether a delegate view was chosen and it handled the touch event.
- */
- private boolean delegateTouchIfNecessary(MotionEvent event) {
- final float x = event.getX();
- final float y = event.getY();
- if (mDelegateView == null && event.getAction() == MotionEvent.ACTION_DOWN) {
- View delegateView = findDelegateView(x, y);
- if (delegateView != null) {
- mDelegateTargeted = true;
- mDelegateView = delegateView;
- mDelegateSlopBounds.set(mTempDelegateBounds);
- mDelegateSlopBounds.inset(-mTouchSlop, -mTouchSlop);
- }
- }
-
- boolean sendToDelegate = mDelegateTargeted;
- boolean inBounds = true;
- switch (event.getAction()) {
- case MotionEvent.ACTION_MOVE:
- inBounds = mDelegateSlopBounds.contains(x, y);
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mDelegateTargeted = false;
- break;
- }
-
- boolean handled = false;
- if (sendToDelegate) {
- if (inBounds) {
- // Offset event coordinates to be inside the target view
- event.setLocation(mDelegateView.getWidth() / 2f, mDelegateView.getHeight() / 2f);
- } else {
- // Offset event coordinates to be outside the target view (in case it does
- // something like tracking pressed state)
- event.setLocation(-mTouchSlop * 2, -mTouchSlop * 2);
- }
- handled = mDelegateView.dispatchTouchEvent(event);
- // Cleanup if this was the last event to send to the delegate.
- if (!mDelegateTargeted) {
- mDelegateView = null;
- }
- }
- return handled;
- }
-
- /**
- * Return an item whose touch bounds contain the given coordinates,
- * or null if no such item exists.
- *
- * Also sets {@link #mTempDelegateBounds} to be the touch bounds of the chosen delegate view.
- */
- private @Nullable View findDelegateView(float x, float y) {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (!child.isShown() || !child.isClickable()) {
- continue;
- }
- int childCenterX = child.getLeft() + child.getWidth() / 2;
- int childCenterY = child.getTop() + child.getHeight() / 2;
- mTempDelegateBounds.set(
- childCenterX - mIconTouchSize / 2f,
- childCenterY - mIconTouchSize / 2f,
- childCenterX + mIconTouchSize / 2f,
- childCenterY + mIconTouchSize / 2f);
- if (mTempDelegateBounds.contains(x, y)) {
- return child;
- }
- }
- return null;
- }
-
- /**
* Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
* touch bounds.
*/
public boolean isEventOverAnyItem(MotionEvent ev) {
getLocationOnScreen(mTempOutLocation);
- float xInOurCoordinates = ev.getX() - mTempOutLocation[0];
- float yInOurCoorindates = ev.getY() - mTempOutLocation[1];
- return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null;
+ int xInOurCoordinates = (int) ev.getX() - mTempOutLocation[0];
+ int yInOurCoorindates = (int) ev.getY() - mTempOutLocation[1];
+ return isShown() && mIconLayoutBounds.contains(xInOurCoordinates, yInOurCoorindates);
}
- /**
- * Add back/home/recents buttons into a single ViewGroup that will be inserted at
- * {@param navButtonStartIndex}
- */
- private void createNavButtons() {
- LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams(
- mActivityContext.getDeviceProfile().iconSizePx,
- mActivityContext.getDeviceProfile().iconSizePx
- );
- buttonParams.gravity = Gravity.CENTER;
-
- mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams);
- mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams);
- mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams);
- }
-
- @Override
- public boolean onDragEvent(DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- mIsDraggingItem = true;
- AbstractFloatingView.closeAllOpenViews(mActivityContext);
- return true;
- case DragEvent.ACTION_DRAG_ENDED:
- mIsDraggingItem = false;
- break;
- }
- return super.onDragEvent(event);
- }
-
- public boolean isDraggingItem() {
- return mIsDraggingItem;
- }
-
- /**
- * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
- */
- protected RectF getHotseatBounds() {
- RectF result;
- mDisableRelayout = true;
- boolean wereHolesAllowed = mAreHolesAllowed;
- setHolesAllowedInLayoutNoAnimation(true);
- result = new RectF(
- mHotseatIconsContainer.getLeft(),
- mHotseatIconsContainer.getTop(),
- mHotseatIconsContainer.getRight(),
- mHotseatIconsContainer.getBottom());
- setHolesAllowedInLayoutNoAnimation(wereHolesAllowed);
- mDisableRelayout = false;
-
- return result;
- }
-
- @Override
- public void requestLayout() {
- if (!mDisableRelayout) {
- super.requestLayout();
- }
+ public Rect getIconLayoutBounds() {
+ return mIconLayoutBounds;
}
// FolderIconParent implemented methods.
@@ -421,7 +284,7 @@
}
private View inflate(@LayoutRes int layoutResId) {
- return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+ return mActivityContext.getViewCache().getView(layoutResId, mActivityContext, this);
}
@Override
@@ -429,11 +292,8 @@
// Ignore, we just implement Insettable to draw behind system insets.
}
- public void setIconsVisibility(boolean isVisible) {
- mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
- }
-
public boolean areIconsVisible() {
- return mHotseatIconsContainer.getVisibility() == VISIBLE;
+ // Consider the overall visibility
+ return getVisibility() == VISIBLE;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
new file mode 100644
index 0000000..6b95f08
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 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;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.AnimatedFloat.VALUE;
+
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.quickstep.AnimatedFloat;
+
+/**
+ * Handles properties/data collection, then passes the results to TaskbarView to render.
+ */
+public class TaskbarViewController {
+ private static final Runnable NO_OP = () -> { };
+
+ public static final int ALPHA_INDEX_HOME = 0;
+ public static final int ALPHA_INDEX_IME = 1;
+ public static final int ALPHA_INDEX_KEYGUARD = 2;
+ public static final int ALPHA_INDEX_STASH = 3;
+
+ private final TaskbarActivityContext mActivity;
+ private final TaskbarView mTaskbarView;
+ private final MultiValueAlpha mTaskbarIconAlpha;
+ private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
+ private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat(
+ this::updateTranslationY);
+ private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat(
+ this::updateTranslationY);
+
+ private final TaskbarModelCallbacks mModelCallbacks;
+
+ // Initialized in init.
+ private TaskbarControllers mControllers;
+
+ // Animation to align icons with Launcher, created lazily. This allows the controller to be
+ // active only during the animation and does not need to worry about layout changes.
+ private AnimatorPlaybackController mIconAlignControllerLazy = null;
+ private Runnable mOnControllerPreCreateCallback = NO_OP;
+
+ public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
+ mActivity = activity;
+ mTaskbarView = taskbarView;
+ mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, 4);
+ mTaskbarIconAlpha.setUpdateVisibility(true);
+ mModelCallbacks = new TaskbarModelCallbacks(activity, mTaskbarView);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
+ mTaskbarView.init(new TaskbarViewCallbacks());
+ mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
+
+ mTaskbarIconScaleForStash.updateValue(1f);
+ LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
+ }
+
+ public void onDestroy() {
+ LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
+ }
+
+ public boolean areIconsVisible() {
+ return mTaskbarView.areIconsVisible();
+ }
+
+ public MultiValueAlpha getTaskbarIconAlpha() {
+ return mTaskbarIconAlpha;
+ }
+
+ /**
+ * Should be called when the IME visibility changes, so we can make Taskbar not steal touches.
+ */
+ public void setImeIsVisible(boolean isImeVisible) {
+ mTaskbarView.setTouchesEnabled(!isImeVisible);
+ }
+
+ /**
+ * Sets OnClickListener and OnLongClickListener for the given view.
+ */
+ public void setClickAndLongClickListenersForIcon(View icon) {
+ mTaskbarView.setClickAndLongClickListenersForIcon(icon);
+ }
+
+ public Rect getIconLayoutBounds() {
+ return mTaskbarView.getIconLayoutBounds();
+ }
+
+ public AnimatedFloat getTaskbarIconScaleForStash() {
+ return mTaskbarIconScaleForStash;
+ }
+
+ public AnimatedFloat getTaskbarIconTranslationYForStash() {
+ return mTaskbarIconTranslationYForStash;
+ }
+
+ /**
+ * Applies scale properties for the entire TaskbarView (rather than individual icons).
+ */
+ private void updateScale() {
+ float scale = mTaskbarIconScaleForStash.value;
+ mTaskbarView.setScaleX(scale);
+ mTaskbarView.setScaleY(scale);
+ }
+
+ private void updateTranslationY() {
+ mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value
+ + mTaskbarIconTranslationYForStash.value);
+ }
+
+ /**
+ * Sets the taskbar icon alignment relative to Launcher hotseat icons
+ * @param alignmentRatio [0, 1]
+ * 0 => not aligned
+ * 1 => fully aligned
+ */
+ public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
+ if (mIconAlignControllerLazy == null) {
+ mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
+ }
+ mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
+ if (alignmentRatio <= 0 || alignmentRatio >= 1) {
+ // Cleanup lazy controller so that it is created again in next animation
+ mIconAlignControllerLazy = null;
+ }
+ }
+
+ /**
+ * Creates an animation for aligning the taskbar icons with the provided Launcher device profile
+ */
+ private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
+ mOnControllerPreCreateCallback.run();
+ PendingAnimation setter = new PendingAnimation(100);
+ Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
+ float scaleUp = ((float) launcherDp.iconSizePx) / mActivity.getDeviceProfile().iconSizePx;
+ int hotseatCellSize =
+ (launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right)
+ / launcherDp.numShownHotseatIcons;
+
+ int offsetY = launcherDp.getTaskbarOffsetY();
+ setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, LINEAR);
+
+ int collapsedHeight = mActivity.getDeviceProfile().taskbarSize;
+ int expandedHeight = collapsedHeight + offsetY;
+ setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight(
+ anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight));
+
+ int count = mTaskbarView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = mTaskbarView.getChildAt(i);
+ ItemInfo info = (ItemInfo) child.getTag();
+ setter.setFloat(child, SCALE_PROPERTY, scaleUp, LINEAR);
+
+ float childCenter = (child.getLeft() + child.getRight()) / 2;
+ float hotseatIconCenter = hotseatPadding.left + hotseatCellSize * info.screenId
+ + hotseatCellSize / 2;
+ setter.setFloat(child, VIEW_TRANSLATE_X, hotseatIconCenter - childCenter, LINEAR);
+ }
+
+ AnimatorPlaybackController controller = setter.createPlaybackController();
+ mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0);
+ return controller;
+ }
+
+ /**
+ * Callbacks for {@link TaskbarView} to interact with its controller.
+ */
+ public class TaskbarViewCallbacks {
+ private final float mSquaredTouchSlop = Utilities.squaredTouchSlop(mActivity);
+
+ private float mDownX, mDownY;
+ private boolean mCanceledStashHint;
+
+ public View.OnClickListener getIconOnClickListener() {
+ return mActivity::onTaskbarIconClicked;
+ }
+
+ public View.OnLongClickListener getIconOnLongClickListener() {
+ return mControllers.taskbarDragController::startDragOnLongClick;
+ }
+
+ public View.OnLongClickListener getBackgroundOnLongClickListener() {
+ return view -> mControllers.taskbarStashController.updateAndAnimateIsStashedInApp(true);
+ }
+
+ public void onTouchEvent(MotionEvent motionEvent) {
+ final float x = motionEvent.getRawX();
+ final float y = motionEvent.getRawY();
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownX = x;
+ mDownY = y;
+ mControllers.taskbarStashController.startStashHint(/* animateForward = */ true);
+ mCanceledStashHint = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (!mCanceledStashHint
+ && squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
+ mControllers.taskbarStashController.startStashHint(
+ /* animateForward= */ false);
+ mCanceledStashHint = true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (!mCanceledStashHint) {
+ mControllers.taskbarStashController.startStashHint(
+ /* animateForward= */ false);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java
new file mode 100644
index 0000000..4093097
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java
@@ -0,0 +1,54 @@
+/*
+ * 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 {
+ default void setRotationButtonController(RotationButtonController rotationButtonController) { }
+
+ default View getCurrentView() {
+ return null;
+ }
+ default void show() { }
+ default void hide() { }
+ default boolean isVisible() {
+ return false;
+ }
+
+ default void updateIcon(int lightIconColor, int darkIconColor) { }
+ default void setOnClickListener(View.OnClickListener onClickListener) { }
+ default void setOnHoverListener(View.OnHoverListener onHoverListener) { }
+ default AnimatedVectorDrawable getImageDrawable() {
+ return null;
+ }
+ default void setDarkIntensity(float darkIntensity) { }
+ default boolean acceptRotationProposal() {
+ return getCurrentView() != null;
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
new file mode 100644
index 0000000..99dc282
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
@@ -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) {
+ // 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.get(context);
+ }
+
+ 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 onDestroy() {
+ 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;
+ }
+ }
+}
+
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 76a5782..85943b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,10 +17,15 @@
package com.android.launcher3.uioverrides;
import android.app.Person;
+import android.content.Context;
import android.content.pm.ShortcutInfo;
+import android.content.res.Resources;
import android.view.Display;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
public class ApiWrapper {
@@ -37,4 +42,18 @@
public static boolean isInternalDisplay(Display display) {
return display.getType() == Display.TYPE_INTERNAL;
}
+
+ /**
+ * Returns the minimum space that should be left empty at the end of hotseat
+ */
+ public static int getHotseatEndOffset(Context context) {
+ if (SysUINavigationMode.INSTANCE.get(context).getMode() == Mode.THREE_BUTTONS) {
+ Resources res = context.getResources();
+ return 2 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_spacing)
+ + 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size);
+ } else {
+ return 0;
+ }
+
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 1d52315..d74b6c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,6 +17,8 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
@@ -112,8 +114,9 @@
mRecentsView, getTaskModalnessProperty(),
toState.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
- setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
- toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
+ boolean showAsGrid = toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile());
+ setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
+ showAsGrid ? INSTANT : FINAL_FRAME);
}
abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
index c115bbb..c46809a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/DeviceFlag.java
@@ -17,21 +17,17 @@
package com.android.launcher3.uioverrides;
import android.annotation.TargetApi;
-import android.content.Context;
import android.os.Build;
import android.provider.DeviceConfig;
import com.android.launcher3.config.FeatureFlags.DebugFlag;
-import java.util.ArrayList;
-
@TargetApi(Build.VERSION_CODES.P)
public class DeviceFlag extends DebugFlag {
public static final String NAMESPACE_LAUNCHER = "launcher";
private final boolean mDefaultValueInCode;
- ArrayList<Runnable> mListeners;
public DeviceFlag(String key, boolean defaultValue, String description) {
super(key, getDeviceValue(key, defaultValue), description);
@@ -44,53 +40,11 @@
}
@Override
- public void initialize(Context context) {
- super.initialize(context);
- if (mListeners == null) {
- mListeners = new ArrayList<>();
- registerDeviceConfigChangedListener(context);
- }
- }
-
- @Override
- public void addChangeListener(Context context, Runnable r) {
- if (mListeners == null) {
- initialize(context);
- }
- mListeners.add(r);
- }
-
- @Override
- public void removeChangeListener(Runnable r) {
- if (mListeners == null) {
- return;
- }
- mListeners.remove(r);
- }
-
- @Override
public boolean get() {
// Override this method in order to let Robolectric ShadowDeviceFlag to stub it.
return super.get();
}
- private void registerDeviceConfigChangedListener(Context context) {
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE_LAUNCHER,
- context.getMainExecutor(),
- properties -> {
- if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())
- || !properties.getKeyset().contains(key)) {
- return;
- }
- defaultValue = getDeviceValue(key, mDefaultValueInCode);
- initialize(context);
- for (Runnable r: mListeners) {
- r.run();
- }
- });
- }
-
protected static boolean getDeviceValue(String key, boolean defaultValue) {
return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValue);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index ec9893c..2009cd7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -17,7 +17,6 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
@@ -55,7 +54,6 @@
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
@@ -84,7 +82,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@@ -231,15 +228,6 @@
}
@Override
- public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
- super.bindWorkspaceItemsChanged(updated);
- if (getTaskbarUIController() != null && updated.stream()
- .filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) {
- getTaskbarUIController().onHotseatUpdated();
- }
- }
-
- @Override
public void onDestroy() {
super.onDestroy();
mHotseatPredictionController.destroy();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 996d36a..1f744e1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -18,13 +18,11 @@
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
-import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
import android.annotation.TargetApi;
@@ -106,16 +104,10 @@
float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
- float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS)
- && mRecentsView.shouldShowOverviewActionsForState(state) ? 1 : 0;
+ float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
-
- float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
- 0.85f : 0;
- propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
- splitPlaceholderAlpha, LINEAR);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 8c128c8..90e17c0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -83,16 +83,6 @@
}
@Override
- public float getTaskbarScale(Launcher launcher) {
- return 1f;
- }
-
- @Override
- public float getTaskbarTranslationY(Launcher launcher) {
- return 0f;
- }
-
- @Override
public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
return new PageAlphaProvider(DEACCEL_2) {
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 6968494..1882a0c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -43,7 +43,7 @@
@Override
public float getSplitSelectTranslation(Launcher launcher) {
RecentsView recentsView = launcher.getOverviewPanel();
- int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
+ int splitPosition = recentsView.getSplitPlaceholder()
.getActiveSplitPositionOption().mStagePosition;
if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) {
return 0f;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 180af0b..0603ba5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -34,7 +34,6 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.SingleAxisSwipeDetector;
@@ -158,26 +157,21 @@
mTaskBeingDragged = view;
int upDirection = mRecentsView.getPagedOrientationHandler()
.getUpDirection(mIsRtl);
- if (!SysUINavigationMode.getMode(mActivity).hasGestures || (
- mActivity.getDeviceProfile().isTablet
- && FeatureFlags.ENABLE_OVERVIEW_GRID.get())) {
- // Don't allow swipe down to open if we don't support swipe up
- // to enter overview, or when grid layout is enabled.
- directionsToDetectScroll = upDirection;
- mAllowGoingUp = true;
- mAllowGoingDown = false;
- } else {
- // The task can be dragged up to dismiss it,
- // and down to open if it's the current page.
- mAllowGoingUp = true;
- if (i == mRecentsView.getCurrentPage()) {
- mAllowGoingDown = true;
- directionsToDetectScroll = DIRECTION_BOTH;
- } else {
- mAllowGoingDown = false;
- directionsToDetectScroll = upDirection;
- }
- }
+
+ // The task can be dragged up to dismiss it
+ mAllowGoingUp = true;
+
+ // The task can be dragged down to open it if:
+ // - It's the current page
+ // - We support gestures to enter overview
+ // - It's the focused task if in grid view
+ // - The task is snapped
+ mAllowGoingDown = i == mRecentsView.getCurrentPage()
+ && SysUINavigationMode.getMode(mActivity).hasGestures
+ && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isFocusedTask())
+ && mRecentsView.isTaskSnapped(i);
+
+ directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
break;
}
}
@@ -233,7 +227,8 @@
if (goingUp) {
currentInterpolator = Interpolators.LINEAR;
pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
- true /* animateTaskView */, true /* removeTask */, maxDuration);
+ true /* animateTaskView */, true /* removeTask */, maxDuration,
+ false /* dismissingForSplitSelection*/);
mEndDisplacement = -secondaryTaskDimension;
} else {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index ac1772c..88fcce1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -212,6 +212,8 @@
public static final long RECENTS_ATTACH_DURATION = 300;
+ private static final float MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS = 0.07f;
+
/**
* Used as the page index for logging when we return to the last task at the end of the gesture.
*/
@@ -252,6 +254,9 @@
private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
protected boolean mIsSwipingPipToHome;
+ // Interpolate RecentsView scale from start of quick switch scroll until this scroll threshold
+ private final float mQuickSwitchScaleScrollThreshold;
+
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
@@ -267,6 +272,8 @@
mTaskAnimationManager = taskAnimationManager;
mTouchTimeMs = touchTimeMs;
mContinuingLastGesture = continuingLastGesture;
+ mQuickSwitchScaleScrollThreshold = context.getResources().getDimension(
+ R.dimen.quick_switch_scaling_scroll_threshold);
initAfterSubclassConstructor();
initStateCallbacks();
@@ -539,7 +546,7 @@
@Override
public void onMotionPauseDetected() {
mHasMotionEverBeenPaused = true;
- maybeUpdateRecentsAttachedState();
+ maybeUpdateRecentsAttachedState(true/* animate */, true/* moveFocusedTask */);
performHapticFeedback();
}
@@ -550,18 +557,24 @@
};
}
- public void maybeUpdateRecentsAttachedState() {
+ private void maybeUpdateRecentsAttachedState() {
maybeUpdateRecentsAttachedState(true /* animate */);
}
+ private void maybeUpdateRecentsAttachedState(boolean animate) {
+ maybeUpdateRecentsAttachedState(animate, false /* moveFocusedTask */);
+ }
+
/**
* Determines whether to show or hide RecentsView. The window is always
* synchronized with its corresponding TaskView in RecentsView, so if
* RecentsView is shown, it will appear to be attached to the window.
*
* Note this method has no effect unless the navigation mode is NO_BUTTON.
+ * @param animate whether to animate when attaching RecentsView
+ * @param moveFocusedTask whether to move focused task to front when attaching
*/
- private void maybeUpdateRecentsAttachedState(boolean animate) {
+ private void maybeUpdateRecentsAttachedState(boolean animate, boolean moveFocusedTask) {
if (!mDeviceState.isFullyGesturalNavMode() || mRecentsView == null) {
return;
}
@@ -580,6 +593,12 @@
} else {
recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
}
+ if (moveFocusedTask && !mAnimationFactory.hasRecentsEverAttachedToAppWindow()
+ && recentsAttachedToAppWindow) {
+ // Only move focused task if RecentsView has never been attached before, to avoid
+ // TaskView jumping to new position as we move the tasks.
+ mRecentsView.moveFocusedTaskToFront();
+ }
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
// Reapply window transform throughout the attach animation, as the animation affects how
@@ -669,7 +688,8 @@
|| !canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
}
- mLauncherTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
+ mLauncherTransitionController.setProgress(
+ Math.max(mCurrentShift.value, getScaleProgressDueToScroll()), mDragLengthFactor);
}
/**
@@ -742,6 +762,8 @@
mRecentsAnimationStartCallbacks.clear();
}
+ TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, false);
+
// Only add the callback to enable the input consumer after we actually have the controller
mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
mRecentsAnimationController::enableInputConsumer);
@@ -756,6 +778,8 @@
mActivityInitListener.unregister();
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+ TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
+
// Defer clearing the controller and the targets until after we've updated the state
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
@@ -884,6 +908,7 @@
break;
case LAST_TASK:
mStateCallback.setState(STATE_RESUME_LAST_TASK);
+ TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
break;
}
ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
@@ -1020,9 +1045,6 @@
if (mRecentsView != null) {
int nearestPage = mRecentsView.getDestinationPage();
boolean isScrolling = false;
- // Update page scroll before snapping to page to make sure we snapped to the
- // position calculated with target gesture in mind.
- mRecentsView.updateScrollSynchronously();
if (mRecentsView.getNextPage() != nearestPage) {
// We shouldn't really scroll to the next page when swiping up to recents.
// Only allow settling on the next page if it's nearest to the center.
@@ -1128,7 +1150,8 @@
mActivityRestartListener);
mParallelRunningAnim = mActivityInterface.getParallelAnimationToLauncher(
- mGestureState.getEndTarget(), duration);
+ mGestureState.getEndTarget(), duration,
+ mTaskAnimationManager.getCurrentCallbacks());
if (mParallelRunningAnim != null) {
mParallelRunningAnim.start();
}
@@ -1180,7 +1203,8 @@
mLauncherTransitionController = null;
if (mRecentsView != null) {
- mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget());
+ mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
+ mTaskViewSimulator);
}
} else {
AnimatorSet animatorSet = new AnimatorSet();
@@ -1222,7 +1246,7 @@
animatorSet.play(windowAnim);
if (mRecentsView != null) {
mRecentsView.onPrepareGestureEndAnimation(
- animatorSet, mGestureState.getEndTarget());
+ animatorSet, mGestureState.getEndTarget(), mTaskViewSimulator);
}
animatorSet.setDuration(duration).setInterpolator(interpolator);
animatorSet.start();
@@ -1230,12 +1254,23 @@
}
}
+ private int calculateWindowRotation(RemoteAnimationTargetCompat runningTaskTarget,
+ RecentsOrientedState orientationState) {
+ if (runningTaskTarget.rotationChange != 0
+ && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
+ ? ROTATION_270 : ROTATION_90;
+ } else {
+ return orientationState.getDisplayRotation();
+ }
+ }
+
private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
// Directly animate the app to PiP (picture-in-picture) mode
final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
- final int windowRotation = orientationState.getDisplayRotation();
+ final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
final int homeRotation = orientationState.getRecentsActivityRotation();
final Matrix homeToWindowPositionMap = new Matrix();
@@ -1725,6 +1760,9 @@
@Override
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+ if (!controller.getFinishTargetIsLauncher()) {
+ TaskViewUtils.setDividerBarShown(mRecentsAnimationTargets.nonApps, true);
+ }
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
if (mRecentsView != null) {
@@ -1777,7 +1815,9 @@
*/
protected void applyWindowTransform() {
if (mWindowTransitionController != null) {
- mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
+ mWindowTransitionController.setProgress(
+ Math.max(mCurrentShift.value, getScaleProgressDueToScroll()),
+ mDragLengthFactor);
}
// No need to apply any transform if there is ongoing swipe-pip-to-home animator since
// that animator handles the leash solely.
@@ -1790,6 +1830,35 @@
ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
}
+ // Scaling of RecentsView during quick switch based on amount of recents scroll
+ private float getScaleProgressDueToScroll() {
+ if (mActivity == null || !mActivity.getDeviceProfile().isTablet || mRecentsView == null
+ || !mRecentsViewScrollLinked) {
+ return 0;
+ }
+
+ float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
+ int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
+ mRecentsView.getLastComputedTaskSize().width(),
+ mRecentsView.getLastComputedTaskSize().height());
+ maxScrollOffset += mRecentsView.getPageSpacing();
+
+ float maxScaleProgress =
+ MAX_QUICK_SWITCH_RECENTS_SCALE_PROGRESS * mRecentsView.getMaxScaleForFullScreen();
+ float scaleProgress = maxScaleProgress;
+
+ if (scrollOffset < mQuickSwitchScaleScrollThreshold) {
+ scaleProgress = Utilities.mapToRange(scrollOffset, 0, mQuickSwitchScaleScrollThreshold,
+ 0, maxScaleProgress, ACCEL_DEACCEL);
+ } else if (scrollOffset > (maxScrollOffset - mQuickSwitchScaleScrollThreshold)) {
+ scaleProgress = Utilities.mapToRange(scrollOffset,
+ (maxScrollOffset - mQuickSwitchScaleScrollThreshold), maxScrollOffset,
+ maxScaleProgress, 0, ACCEL_DEACCEL);
+ }
+
+ return scaleProgress;
+ }
+
/**
* Used for winscope tracing, see launcher_trace.proto
* @see com.android.systemui.shared.tracing.ProtoTraceable#writeToProto
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
index f7e8781..95c8710 100644
--- a/quickstep/src/com/android/quickstep/AnimatedFloat.java
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -53,6 +53,16 @@
mUpdateCallback = updateCallback;
}
+ /**
+ * Returns an animation from the current value to the given value.
+ */
+ public ObjectAnimator animateToValue(float end) {
+ return animateToValue(value, end);
+ }
+
+ /**
+ * Returns an animation from the given start value to the given end value.
+ */
public ObjectAnimator animateToValue(float start, float end) {
cancelAnimation();
mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, start, end);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index fac4d52..11fb1d0 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -211,17 +211,18 @@
Rect gridRect = new Rect();
calculateGridSize(context, dp, gridRect);
- int verticalMargin = res.getDimensionPixelSize(
- R.dimen.overview_grid_focus_vertical_margin);
- float taskHeight = gridRect.height() - verticalMargin * 2;
+ int verticalMargin = Math.max(
+ res.getDimensionPixelSize(R.dimen.overview_grid_focus_vertical_margin),
+ res.getDimensionPixelSize(R.dimen.overview_actions_height));
+ float taskHeight =
+ gridRect.height() - verticalMargin * 2 - dp.overviewTaskThumbnailTopMarginPx;
PointF taskDimension = getTaskDimension(context, dp);
- float scale = taskHeight / Math.max(taskDimension.x, taskDimension.y);
+ float scale = taskHeight / taskDimension.y;
int outWidth = Math.round(scale * taskDimension.x);
int outHeight = Math.round(scale * taskDimension.y);
- int gravity = Gravity.CENTER_VERTICAL;
- gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
+ int gravity = Gravity.CENTER;
Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
} else {
int taskMargin = dp.overviewTaskMarginPx;
@@ -322,8 +323,7 @@
float rowHeight = (gridRect.height() - rowSpacing) / 2f;
PointF taskDimension = getTaskDimension(context, dp);
- float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / Math.max(
- taskDimension.x, taskDimension.y);
+ float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / taskDimension.y;
int outWidth = Math.round(scale * taskDimension.x);
int outHeight = Math.round(scale * taskDimension.y);
@@ -358,7 +358,8 @@
* an optional additional animation with the same duration.
*/
public @Nullable Animator getParallelAnimationToLauncher(
- GestureState.GestureEndTarget endTarget, long duration) {
+ GestureState.GestureEndTarget endTarget, long duration,
+ RecentsAnimationCallbacks callbacks) {
if (endTarget == RECENTS) {
ACTIVITY_TYPE activity = getCreatedActivity();
if (activity == null) {
@@ -399,6 +400,10 @@
default boolean isRecentsAttachedToAppWindow() {
return false;
}
+
+ default boolean hasRecentsEverAttachedToAppWindow() {
+ return false;
+ }
}
class DefaultAnimationFactory implements AnimationFactory {
@@ -408,6 +413,7 @@
private final Consumer<AnimatorControllerWithResistance> mCallback;
private boolean mIsAttachedToWindow;
+ private boolean mHasEverAttachedToWindow;
DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) {
mCallback = callback;
@@ -461,6 +467,9 @@
}
mIsAttachedToWindow = attached;
RecentsView recentsView = mActivity.getOverviewPanel();
+ if (attached) {
+ mHasEverAttachedToWindow = true;
+ }
Animator fadeAnim = mActivity.getStateManager()
.createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
@@ -490,6 +499,11 @@
return mIsAttachedToWindow;
}
+ @Override
+ public boolean hasRecentsEverAttachedToAppWindow() {
+ return mHasEverAttachedToWindow;
+ }
+
protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
// Scale down recents from being full screen to being in overview.
RecentsView recentsView = activity.getOverviewPanel();
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 0b2a057..3580ee5 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -39,6 +39,7 @@
int TYPE_OVERSCROLL = 1 << 9;
int TYPE_SYSUI_OVERLAY = 1 << 10;
int TYPE_ONE_HANDED = 1 << 11;
+ int TYPE_TASKBAR_STASH = 1 << 12;
String[] NAMES = new String[] {
"TYPE_NO_OP", // 0
@@ -53,6 +54,7 @@
"TYPE_OVERSCROLL", // 9
"TYPE_SYSUI_OVERLAY", // 10
"TYPE_ONE_HANDED", // 11
+ "TYPE_TASKBAR_STASH", // 12
};
InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index fb1391a..5deb75e 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -299,14 +299,15 @@
@Override
public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
- long duration) {
+ long duration, RecentsAnimationCallbacks callbacks) {
LauncherTaskbarUIController uiController = getTaskbarController();
- Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration);
- if (uiController == null) {
+ Animator superAnimator = super.getParallelAnimationToLauncher(
+ endTarget, duration, callbacks);
+ if (uiController == null || callbacks == null) {
return superAnimator;
}
LauncherState toState = stateFromGestureEndTarget(endTarget);
- Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration);
+ Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration);
if (superAnimator == null) {
return taskbarAnimator;
} else {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 9dfcd12..95be45a 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -122,13 +122,10 @@
mActionsView = findViewById(R.id.overview_actions_view);
SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
- SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
- splitPlaceholderView.init(
- new SplitSelectStateController(mUiHandler, SystemUiProxy.INSTANCE.get(this))
- );
-
+ SplitSelectStateController controller =
+ new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
mDragLayer.recreateControllers();
- mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
+ mFallbackRecentsView.init(mActionsView, controller);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index a21c714..239233b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.util.ArraySet;
+import android.view.RemoteAnimationTarget;
import androidx.annotation.BinderThread;
import androidx.annotation.UiThread;
@@ -39,6 +40,7 @@
com.android.systemui.shared.system.RecentsAnimationListener {
private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
+ private final SystemUiProxy mSystemUiProxy;
private final boolean mAllowMinimizeSplitScreen;
// TODO(141886704): Remove these references when they are no longer needed
@@ -46,7 +48,9 @@
private boolean mCancelled;
- public RecentsAnimationCallbacks(boolean allowMinimizeSplitScreen) {
+ public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
+ boolean allowMinimizeSplitScreen) {
+ mSystemUiProxy = systemUiProxy;
mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
}
@@ -89,8 +93,11 @@
RemoteAnimationTargetCompat[] appTargets,
RemoteAnimationTargetCompat[] wallpaperTargets,
Rect homeContentInsets, Rect minimizedHomeBounds) {
+ RemoteAnimationTarget[] nonAppTargets =
+ mSystemUiProxy.onGoingToRecentsLegacy(mCancelled);
RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
- wallpaperTargets, homeContentInsets, minimizedHomeBounds);
+ wallpaperTargets, RemoteAnimationTargetCompat.wrap(nonAppTargets),
+ homeContentInsets, minimizedHomeBounds);
mController = new RecentsAnimationController(animationController,
mAllowMinimizeSplitScreen, this::onAnimationFinished);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 53b6675..f343485 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -46,6 +46,8 @@
private boolean mUseLauncherSysBarFlags = false;
private boolean mSplitScreenMinimized = false;
private boolean mFinishRequested = false;
+ // Only valid when mFinishRequested == true.
+ private boolean mFinishTargetIsLauncher;
private RunnableList mPendingFinishCallbacks = new RunnableList();
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
@@ -145,6 +147,7 @@
// Finish not yet requested
mFinishRequested = true;
+ mFinishTargetIsLauncher = toRecents;
mOnFinishedListener.accept(this);
mPendingFinishCallbacks.add(callback);
UI_HELPER_EXECUTOR.execute(() -> {
@@ -217,4 +220,12 @@
public RecentsAnimationControllerCompat getController() {
return mController;
}
+
+ /**
+ * RecentsAnimationListeners can check this in onRecentsAnimationFinished() to determine whether
+ * the animation was finished to launcher vs an app.
+ */
+ public boolean getFinishTargetIsLauncher() {
+ return mFinishTargetIsLauncher;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 3861bab..b6d9016 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -31,9 +31,9 @@
public final Rect minimizedHomeBounds;
public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
- RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
- Rect minimizedHomeBounds) {
- super(apps, wallpapers, new RemoteAnimationTargetCompat[0], MODE_CLOSING);
+ RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
+ Rect homeContentInsets, Rect minimizedHomeBounds) {
+ super(apps, wallpapers, nonApps, MODE_CLOSING);
this.homeContentInsets = homeContentInsets;
this.minimizedHomeBounds = minimizedHomeBounds;
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index d040904..11ca4b1 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -33,6 +33,8 @@
import android.os.UserHandle;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import com.android.launcher3.util.MainThreadInitializedObject;
@@ -85,6 +87,7 @@
private float mLastNavButtonAlpha;
private boolean mLastNavButtonAnimate;
private boolean mHasNavButtonAlphaBeenSet = false;
+ private Runnable mPendingSetNavButtonAlpha = null;
// TODO(141886704): Find a way to remove this
private int mLastSystemUiStateFlags;
@@ -157,6 +160,11 @@
setSmartspaceCallback(mPendingSmartspaceCallback);
mPendingSmartspaceCallback = null;
}
+
+ if (mPendingSetNavButtonAlpha != null) {
+ mPendingSetNavButtonAlpha.run();
+ mPendingSetNavButtonAlpha = null;
+ }
}
public void clearProxy() {
@@ -240,14 +248,18 @@
boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
|| animate != mLastNavButtonAnimate
|| !mHasNavButtonAlphaBeenSet;
- if (mSystemUiProxy != null && changed) {
- mLastNavButtonAlpha = alpha;
- mLastNavButtonAnimate = animate;
- mHasNavButtonAlphaBeenSet = true;
- try {
- mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+ if (changed) {
+ if (mSystemUiProxy == null) {
+ mPendingSetNavButtonAlpha = () -> setNavBarButtonAlpha(alpha, animate);
+ } else {
+ mLastNavButtonAlpha = alpha;
+ mLastNavButtonAnimate = animate;
+ mHasNavButtonAlphaBeenSet = true;
+ try {
+ mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+ }
}
}
}
@@ -346,7 +358,7 @@
try {
mSystemUiProxy.setSplitScreenMinimized(minimized);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call stopScreenPinning", e);
+ Log.w(TAG, "Failed call setSplitScreenMinimized", e);
}
}
}
@@ -388,6 +400,18 @@
}
@Override
+ public void notifyTaskbarStatus(boolean visible, boolean stashed) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call notifyTaskbarStatus with arg: " +
+ visible + ", " + stashed, e);
+ }
+ }
+ }
+
+ @Override
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
Insets visibleInsets, Task.TaskKey task) {
if (mSystemUiProxy != null) {
@@ -552,6 +576,22 @@
}
}
+ /**
+ * Start multiple tasks in split-screen simultaneously.
+ */
+ public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
+ Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
+ RemoteAnimationAdapter adapter) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
+ sideOptions, sidePosition, adapter);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call startTasksWithLegacyTransition");
+ }
+ }
+ }
+
public void startShortcut(String packageName, String shortcutId, int stage, int position,
Bundle options, UserHandle user) {
if (mSplitScreen != null) {
@@ -585,6 +625,24 @@
}
}
+ /**
+ * Call this when going to recents so that shell can set-up and provide appropriate leashes
+ * for animation (eg. DividerBar).
+ *
+ * @param cancel true if recents starting is being cancelled.
+ * @return RemoteAnimationTargets of windows that need to animate but only exist in shell.
+ */
+ public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) {
+ if (mSplitScreen != null) {
+ try {
+ return mSplitScreen.onGoingToRecentsLegacy(cancel);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onGoingToRecentsLegacy");
+ }
+ }
+ return null;
+ }
+
//
// One handed
//
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 67bd85f..fe07cbd 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -28,6 +28,7 @@
import android.os.SystemProperties;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
@@ -108,7 +109,8 @@
final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
mLastGestureState = gestureState;
- mCallbacks = new RecentsAnimationCallbacks(activityInterface.allowMinimizeSplitScreen());
+ mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx),
+ activityInterface.allowMinimizeSplitScreen());
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
@@ -262,6 +264,11 @@
mLastAppearedTaskTarget = null;
}
+ @Nullable
+ public RecentsAnimationCallbacks getCurrentCallbacks() {
+ return mCallbacks;
+ }
+
public void dump() {
// TODO
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index e75d751..080533b 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -68,13 +68,14 @@
final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
for (TaskShortcutFactory menuOption : MENU_OPTIONS) {
SystemShortcut shortcut = menuOption.getShortcut(activity, taskView);
- if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
- FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
- addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+ if (shortcut == null) {
continue;
}
- if (shortcut != null) {
+ if (menuOption == TaskShortcutFactory.SPLIT_SCREEN &&
+ FeatureFlags.ENABLE_SPLIT_SELECT.get()) {
+ addSplitOptions(shortcuts, activity, taskView, deviceProfile);
+ } else {
shortcuts.add(shortcut);
}
}
@@ -88,7 +89,6 @@
SystemShortcut screenshotShortcut = TaskShortcutFactory.SCREENSHOT
.getShortcut(activity, taskView);
if (screenshotShortcut != null) {
- screenshotShortcut.setHasFinishRecentsInAction(true);
shortcuts.add(screenshotShortcut);
}
@@ -97,7 +97,6 @@
SystemShortcut modalShortcut = TaskShortcutFactory.MODAL
.getShortcut(activity, taskView);
if (modalShortcut != null) {
- modalShortcut.setHasFinishRecentsInAction(true);
shortcuts.add(modalShortcut);
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 37fda73..f292f1a 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -405,77 +406,39 @@
}
/** Legacy version (until shell transitions are enabled) */
- public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull AnimatorSet anim,
+ public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull TaskView initialView,
@NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
@NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
- @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
- @NonNull StateManager stateManager, @NonNull DepthController depthController,
- int targetStage) {
- PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
- boolean isRunningTask = v.isRunningTask();
- TransformParams params = null;
- TaskViewSimulator tvs = null;
- RecentsView recentsView = v.getRecentsView();
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
- params = recentsView.getLiveTileParams();
- tvs = recentsView.getLiveTileTaskViewSimulator();
+ @NonNull RemoteAnimationTargetCompat[] nonAppTargets,
+ @NonNull Runnable finishCallback) {
+
+ final int[] splitRoots = new int[2];
+ for (int i = 0; i < appTargets.length; ++i) {
+ final int taskId = appTargets[i].taskInfo != null ? appTargets[i].taskInfo.taskId : -1;
+ final int mode = appTargets[i].mode;
+ if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) {
+ if (mode != MODE_OPENING) {
+ throw new IllegalStateException(
+ "Expected task to be opening, but it is " + mode);
+ }
+ splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] = i;
+ }
}
- boolean inLiveTileMode =
- ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
- final RemoteAnimationTargets targets =
- new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
- inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- if (params == null) {
- SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
- targets.addReleaseCheck(applier);
-
- params = new TransformParams()
- .setSyncTransactionApplier(applier)
- .setTargetSet(targets);
+ // This is where we should animate the split roots. For now, though, just make them visible.
+ for (int i = 0; i < 2; ++i) {
+ t.show(appTargets[splitRoots[i]].leash.getSurfaceControl());
+ t.setAlpha(appTargets[splitRoots[i]].leash.getSurfaceControl(), 1.f);
}
- Rect crop = new Rect();
- Context context = v.getContext();
- DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
- if (tvs == null && targets.apps.length > 0) {
- tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
- tvs.setDp(dp);
+ // This contains the initial state (before animation), so apply this at the beginning of
+ // the animation.
+ t.apply();
- // RecentsView never updates the display rotation until swipe-up so the value may
- // be stale. Use the display value instead.
- int displayRotation = DisplayController.INSTANCE.get(recentsView.getContext())
- .getInfo().rotation;
- tvs.getOrientationState().update(displayRotation, displayRotation);
-
- tvs.setPreview(targets.apps[targets.apps.length - 1]);
- tvs.fullScreenProgress.value = 0;
- tvs.recentsViewScale.value = 1;
-// tvs.setScroll(startScroll);
-
- // Fade in the task during the initial 20% of the animation
- out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
- clampToProgress(LINEAR, 0, 0.2f));
- }
-
- TaskViewSimulator topMostSimulator = null;
-
- if (tvs != null) {
- out.setFloat(tvs.fullScreenProgress,
- AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
- out.setFloat(tvs.recentsViewScale,
- AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
- out.setFloat(tvs.recentsViewScroll,
- AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
-
- TaskViewSimulator finalTsv = tvs;
- TransformParams finalParams = params;
- out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
- topMostSimulator = tvs;
- }
-
- anim.play(out.buildAnim());
+ // Once there is an animation, this should be called AFTER the animation completes.
+ finishCallback.run();
}
public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
@@ -490,6 +453,10 @@
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
nonAppTargets, depthController, pa);
+ if (launcherClosing) {
+ // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
+ TaskViewUtils.setDividerBarShown(nonAppTargets, true);
+ }
Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
@@ -542,4 +509,19 @@
stateManager.setCurrentAnimation(anim, childStateAnimation);
anim.addListener(windowAnimEndListener);
}
+
+ static void setDividerBarShown(RemoteAnimationTargetCompat[] nonApps, boolean shown) {
+ // TODO(b/182592057): make this part of the animations instead.
+ if (nonApps != null && nonApps.length > 0) {
+ for (int i = 0; i < nonApps.length; ++i) {
+ final RemoteAnimationTargetCompat targ = nonApps[i];
+ if (targ.windowType == TYPE_DOCK_DIVIDER) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setVisibility(targ.leash.getSurfaceControl(), shown);
+ t.apply();
+ t.close();
+ }
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b6dc833..e55f1d1 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -96,6 +96,7 @@
import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
+import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
import com.android.quickstep.util.ProtoTracer;
@@ -262,16 +263,35 @@
}
@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));
}
@Override
- public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
- int backDisposition, boolean showImeSwitcher) {
- MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus(
- displayId, vis, backDisposition, showImeSwitcher));
+ 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
+ .disableNavBarElements(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() {
@@ -287,7 +307,6 @@
return sConnected;
}
-
public static boolean isInitialized() {
return sIsInitialized;
}
@@ -654,6 +673,14 @@
mDeviceState, event);
}
+ // If Taskbar is present, we listen for long press to unstash it.
+ BaseActivityInterface activityInterface = newGestureState.getActivityInterface();
+ StatefulActivity activity = activityInterface.getCreatedActivity();
+ if (activity != null && activity.getDeviceProfile().isTaskbarPresent) {
+ base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat,
+ mTaskbarManager.getCurrentActivityContext());
+ }
+
if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
OverscrollPlugin plugin = null;
if (FeatureFlags.FORCE_LOCAL_OVERSCROLL_PLUGIN.get()) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index f0364eb..50b69dc 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep.fallback;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
@@ -77,9 +79,7 @@
float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
clearAllButtonAlpha, LINEAR);
- float overviewButtonAlpha =
- state.hasOverviewActions() && mRecentsView.shouldShowOverviewActionsForState(state)
- ? 1 : 0;
+ float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
MultiValueAlpha.VALUE, overviewButtonAlpha, LINEAR);
@@ -94,8 +94,9 @@
setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
- setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
- state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()) ? 1f : 0f, LINEAR);
+ boolean showAsGrid = state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile());
+ setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS, showAsGrid ? 1f : 0f,
+ showAsGrid ? INSTANT : FINAL_FRAME);
setter.setViewBackgroundColor(mActivity.getScrimView(), state.getScrimColor(mActivity),
config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index ac3fb27..de79372 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -26,17 +26,18 @@
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.Log;
+import android.view.MotionEvent;
import androidx.annotation.Nullable;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.testing.TestProtocol;
import com.android.quickstep.FallbackActivityInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.SplitPlaceholderView;
@@ -62,8 +63,8 @@
}
@Override
- public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
- super.init(actionsView, splitPlaceholderView);
+ public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
+ super.init(actionsView, splitController);
setOverviewStateEnabled(true);
setOverlayEnabled(true);
}
@@ -90,12 +91,14 @@
*/
@Override
public void onPrepareGestureEndAnimation(
- @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget) {
- super.onPrepareGestureEndAnimation(animatorSet, endTarget);
+ @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
+ TaskViewSimulator taskViewSimulator) {
+ super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulator);
if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
TaskView tv = getTaskView(mHomeTaskInfo.taskId);
if (tv != null) {
- PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
+ PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
+ false /* dismissingForSplitSelection*/);
pa.addEndListener(e -> setCurrentTask(-1));
AnimatorPlaybackController controller = pa.createPlaybackController();
controller.dispatchOnStart();
@@ -122,6 +125,12 @@
}
}
+ @Nullable
+ @Override
+ protected TaskView getHomeTaskView() {
+ return mHomeTaskInfo != null ? getTaskView(mHomeTaskInfo.taskId) : null;
+ }
+
@Override
protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
if (mHomeTaskInfo != null && runningTaskInfo != null &&
@@ -205,4 +214,11 @@
setDisallowScrollToClearAll(!state.hasClearAllButton());
}
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ boolean result = super.onTouchEvent(ev);
+ // Do not let touch escape to siblings below this view.
+ return result || mActivity.getStateManager().getState().overviewUi();
+ }
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index b6cfdce..111a940 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -40,15 +40,16 @@
private static final int FLAG_SHOW_AS_GRID = BaseState.getFlag(4);
private static final int FLAG_SCRIM = BaseState.getFlag(5);
private static final int FLAG_LIVE_TILE = BaseState.getFlag(6);
+ private static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
public static final RecentsState DEFAULT = new RecentsState(0,
FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID | FLAG_SCRIM
- | FLAG_LIVE_TILE);
+ | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
public static final RecentsState MODAL_TASK = new ModalState(1,
FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
- | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE);
+ | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
- FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+ FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN | FLAG_OVERVIEW_UI);
public static final RecentsState HOME = new RecentsState(3, 0);
public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
@@ -140,6 +141,13 @@
return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
}
+ /**
+ * True if the state has overview panel visible.
+ */
+ public boolean overviewUi() {
+ return hasFlag(FLAG_OVERVIEW_UI);
+ }
+
private static class ModalState extends RecentsState {
public ModalState(int id, int flags) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
new file mode 100644
index 0000000..dbe260a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 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.inputconsumers;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Listens for a long press, and cancels the current gesture if that causes Taskbar to be unstashed.
+ */
+public class TaskbarStashInputConsumer extends DelegateInputConsumer {
+
+ private final TaskbarActivityContext mTaskbarActivityContext;
+ private final GestureDetector mLongPressDetector;
+ private final float mSquaredTouchSlop;
+
+ private float mDownX, mDownY;
+ private boolean mCanceledUnstashHint;
+
+ public TaskbarStashInputConsumer(Context context, InputConsumer delegate,
+ InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext) {
+ super(delegate, inputMonitor);
+ mTaskbarActivityContext = taskbarActivityContext;
+ mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
+
+ mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
+ @Override
+ public void onLongPress(MotionEvent motionEvent) {
+ onLongPressDetected(motionEvent);
+ }
+ });
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_TASKBAR_STASH | mDelegate.getType();
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ mLongPressDetector.onTouchEvent(ev);
+ if (mState != STATE_ACTIVE) {
+ mDelegate.onMotionEvent(ev);
+
+ if (mTaskbarActivityContext != null) {
+ final float x = ev.getRawX();
+ final float y = ev.getRawY();
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownX = x;
+ mDownY = y;
+ mTaskbarActivityContext.startTaskbarUnstashHint(
+ /* animateForward = */ true);
+ mCanceledUnstashHint = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (!mCanceledUnstashHint
+ && squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) {
+ mTaskbarActivityContext.startTaskbarUnstashHint(
+ /* animateForward = */ false);
+ mCanceledUnstashHint = true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (!mCanceledUnstashHint) {
+ mTaskbarActivityContext.startTaskbarUnstashHint(
+ /* animateForward = */ false);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ private void onLongPressDetected(MotionEvent motionEvent) {
+ if (mTaskbarActivityContext != null
+ && mTaskbarActivityContext.onLongPressToUnstashTaskbar()) {
+ setActive(motionEvent);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 7eca360..6bdc284 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -16,7 +16,7 @@
package com.android.quickstep.logging;
-import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
+import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.Utilities.getPrefs;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_2;
@@ -43,6 +43,7 @@
import com.android.launcher3.AutoInstallsLayout;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
@@ -133,7 +134,8 @@
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (LAST_PREDICTION_ENABLED_STATE.equals(key) || KEY_MIGRATION_SRC_HOTSEAT_COUNT.equals(key)
+ if (LAST_PREDICTION_ENABLED_STATE.equals(key)
+ || KEY_MIGRATION_SRC_WORKSPACE_SIZE.equals(key)
|| mLoggablePrefs.containsKey(key)) {
dispatchUserEvent();
}
@@ -153,23 +155,25 @@
SharedPreferences prefs = getPrefs(mContext);
StatsLogManager.LauncherEvent gridSizeChangedEvent = null;
- // TODO(b/184981523): This doesn't work for 2-panel grid, which has 6 hotseat icons
- switch (prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1)) {
- case 5:
- gridSizeChangedEvent = LAUNCHER_GRID_SIZE_5;
- break;
- case 4:
- gridSizeChangedEvent = LAUNCHER_GRID_SIZE_4;
- break;
- case 3:
- gridSizeChangedEvent = LAUNCHER_GRID_SIZE_3;
- break;
- case 2:
- gridSizeChangedEvent = LAUNCHER_GRID_SIZE_2;
- break;
- default:
- // Ignore illegal input.
- break;
+ String workspaceSize = prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, null);
+ if (workspaceSize != null) {
+ switch (Utilities.parsePoint(workspaceSize).x) {
+ case 5:
+ gridSizeChangedEvent = LAUNCHER_GRID_SIZE_5;
+ break;
+ case 4:
+ gridSizeChangedEvent = LAUNCHER_GRID_SIZE_4;
+ break;
+ case 3:
+ gridSizeChangedEvent = LAUNCHER_GRID_SIZE_3;
+ break;
+ case 2:
+ gridSizeChangedEvent = LAUNCHER_GRID_SIZE_2;
+ break;
+ default:
+ // Ignore illegal input.
+ break;
+ }
}
if (gridSizeChangedEvent != null) {
logger.log(gridSizeChangedEvent);
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index 7f94839..f1b4e3d 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -49,6 +49,7 @@
private enum RecentsResistanceParams {
FROM_APP(0.75f, 0.5f, 1f),
+ FROM_APP_TABLET(0.9f, 0.75f, 1f),
FROM_OVERVIEW(1f, 0.75f, 0.5f);
RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
@@ -228,7 +229,7 @@
// These are not required, or can have a default value that is generally correct.
@Nullable public PendingAnimation resistAnim = null;
- public RecentsResistanceParams resistanceParams = RecentsResistanceParams.FROM_APP;
+ public RecentsResistanceParams resistanceParams;
public float startScale = 1f;
public float startTranslation = 0f;
@@ -242,6 +243,11 @@
this.scaleProperty = scaleProperty;
this.translationTarget = translationTarget;
this.translationProperty = translationProperty;
+ if (dp.isTablet) {
+ resistanceParams = RecentsResistanceParams.FROM_APP_TABLET;
+ } else {
+ resistanceParams = RecentsResistanceParams.FROM_APP;
+ }
}
private RecentsParams setResistAnim(PendingAnimation resistAnim) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index a147b68..16c925a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -22,33 +22,26 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import android.animation.AnimatorSet;
-import android.app.ActivityOptions;
+import android.app.ActivityThread;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.util.Pair;
import android.view.Gravity;
+import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import androidx.annotation.Nullable;
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
import com.android.launcher3.R;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskAnimationManager;
import com.android.quickstep.TaskViewUtils;
import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -63,6 +56,7 @@
private final SystemUiProxy mSystemUiProxy;
private TaskView mInitialTaskView;
+ private TaskView mSecondTaskView;
private SplitPositionOption mInitialPosition;
private Rect mInitialBounds;
private final Handler mHandler;
@@ -86,43 +80,29 @@
* To be called after second task selected
*/
public void setSecondTaskId(TaskView taskView) {
- if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
- // Assume initial task is for top/left part of screen
- final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
- ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
- : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+ mSecondTaskView = taskView;
+ // Assume initial task is for top/left part of screen
- RemoteSplitLaunchAnimationRunner animationRunner =
- new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView);
+ final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
+ ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
+ : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+ if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ RemoteSplitLaunchTransitionRunner animationRunner =
+ new RemoteSplitLaunchTransitionRunner(mInitialTaskView, taskView);
mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR));
- return;
+ } else {
+ RemoteSplitLaunchAnimationRunner animationRunner =
+ new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ RemoteAnimationAdapterCompat.wrapRemoteAnimationRunner(animationRunner),
+ 300, 150,
+ ActivityThread.currentActivityThread().getApplicationThread());
+
+ mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], null /* mainOptions */,
+ taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, adapter);
}
- // Assume initial mInitialTaskId is for top/left part of screen
- RemoteAnimationFactory initialSplitRunnerWrapped = new SplitLaunchAnimationRunner(
- mInitialTaskView, 0);
- RemoteAnimationFactory secondarySplitRunnerWrapped = new SplitLaunchAnimationRunner(
- taskView, 1);
- RemoteAnimationRunnerCompat initialSplitRunner = new LauncherAnimationRunner(
- new Handler(Looper.getMainLooper()), initialSplitRunnerWrapped,
- true /* startAtFrontOfQueue */);
- RemoteAnimationRunnerCompat secondarySplitRunner = new LauncherAnimationRunner(
- new Handler(Looper.getMainLooper()), secondarySplitRunnerWrapped,
- true /* startAtFrontOfQueue */);
- ActivityOptions initialOptions = ActivityOptionsCompat.makeRemoteAnimation(
- new RemoteAnimationAdapterCompat(initialSplitRunner, 300, 150));
- ActivityOptions secondaryOptions = ActivityOptionsCompat.makeRemoteAnimation(
- new RemoteAnimationAdapterCompat(secondarySplitRunner, 300, 150));
- mSystemUiProxy.startTask(mInitialTaskView.getTask().key.id, mInitialPosition.mStageType,
- mInitialPosition.mStagePosition,
- /*null*/ initialOptions.toBundle());
- Pair<Integer, Integer> compliment = getComplimentaryStageAndPosition(mInitialPosition);
- mSystemUiProxy.startTask(taskView.getTask().key.id, compliment.first,
- compliment.second,
- /*null*/ secondaryOptions.toBundle());
- // After successful launch, call resetState
- resetState();
}
/**
@@ -153,12 +133,12 @@
/**
* Requires Shell Transitions
*/
- private class RemoteSplitLaunchAnimationRunner implements RemoteTransitionRunner {
+ private class RemoteSplitLaunchTransitionRunner implements RemoteTransitionRunner {
private final TaskView mInitialTaskView;
private final TaskView mTaskView;
- RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView) {
+ RemoteSplitLaunchTransitionRunner(TaskView initialTaskView, TaskView taskView) {
mInitialTaskView = initialTaskView;
mTaskView = taskView;
}
@@ -175,59 +155,50 @@
/**
* LEGACY
- * @return the opposite stage and position from the {@param position} provided as first and
- * second object, respectively
- * Ex. If position is has stage = Main and position = Top/Left, this will return
- * Pair(stage=Side, position=Bottom/Left)
- */
- private Pair<Integer, Integer> getComplimentaryStageAndPosition(SplitPositionOption position) {
- // Right now this is as simple as flipping between 0 and 1
- int complimentStageType = position.mStageType ^ 1;
- int complimentStagePosition = position.mStagePosition ^ 1;
- return new Pair<>(complimentStageType, complimentStagePosition);
- }
-
- /**
- * LEGACY
* Remote animation runner for animation to launch an app.
*/
- private class SplitLaunchAnimationRunner implements RemoteAnimationFactory {
+ private class RemoteSplitLaunchAnimationRunner implements RemoteAnimationRunnerCompat {
- private final TaskView mV;
- private final int mTargetState;
+ private final TaskView mInitialTaskView;
+ private final TaskView mTaskView;
- SplitLaunchAnimationRunner(TaskView v, int targetState) {
- mV = v;
- mTargetState = targetState;
+ RemoteSplitLaunchAnimationRunner(TaskView initialTaskView, TaskView taskView) {
+ mInitialTaskView = initialTaskView;
+ mTaskView = taskView;
}
@Override
- public void onCreateAnimation(int transit,
- RemoteAnimationTargetCompat[] appTargets,
- RemoteAnimationTargetCompat[] wallpaperTargets,
- RemoteAnimationTargetCompat[] nonAppTargets,
- LauncherAnimationRunner.AnimationResult result) {
- AnimatorSet anim = new AnimatorSet();
- BaseQuickstepLauncher activity = BaseActivity.fromContext(mV.getContext());
- TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(anim, mV,
- appTargets, wallpaperTargets, nonAppTargets, true, activity.getStateManager(),
- activity.getDepthController(), mTargetState);
- result.setAnimation(anim, activity);
+ public void onAnimationStart(int transit, RemoteAnimationTargetCompat[] apps,
+ RemoteAnimationTargetCompat[] wallpapers, RemoteAnimationTargetCompat[] nonApps,
+ Runnable finishedCallback) {
+ TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(mInitialTaskView, mTaskView, apps,
+ wallpapers, nonApps, finishedCallback);
+ // After successful launch, call resetState
+ resetState();
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ resetState();
}
}
-
/**
* To be called if split select was cancelled
*/
public void resetState() {
mInitialTaskView = null;
+ mSecondTaskView = null;
mInitialPosition = null;
mInitialBounds = null;
}
+ /**
+ * @return {@code true} if first task has been selected and waiting for the second task to be
+ * chosen
+ */
public boolean isSplitSelectActive() {
- return mInitialTaskView != null;
+ return mInitialTaskView != null && mSecondTaskView == null;
}
public Rect getInitialBounds() {
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index c0f5c14..d4191fe 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -40,6 +40,7 @@
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.Themes;
+import com.android.quickstep.TaskAnimationManager;
import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -278,19 +279,36 @@
private RotatedPosition getRotatedPosition(float progress) {
final float degree, positionX, positionY;
- if (mFromRotation == Surface.ROTATION_90) {
- degree = -90 * progress;
- positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
- + mStartBounds.left;
- positionY = progress * (mDestinationBoundsTransformed.bottom - mStartBounds.top)
- + mStartBounds.top;
+ if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ if (mFromRotation == Surface.ROTATION_90) {
+ degree = -90 * (1 - progress);
+ positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
+ + mStartBounds.left;
+ positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
+ + mStartBounds.top + mStartBounds.bottom * (1 - progress);
+ } else {
+ degree = 90 * (1 - progress);
+ positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
+ + mStartBounds.left + mStartBounds.right * (1 - progress);
+ positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
+ + mStartBounds.top;
+ }
} else {
- degree = 90 * progress;
- positionX = progress * (mDestinationBoundsTransformed.right - mStartBounds.left)
- + mStartBounds.left;
- positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
- + mStartBounds.top;
+ if (mFromRotation == Surface.ROTATION_90) {
+ degree = -90 * progress;
+ positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
+ + mStartBounds.left;
+ positionY = progress * (mDestinationBoundsTransformed.bottom - mStartBounds.top)
+ + mStartBounds.top;
+ } else {
+ degree = 90 * progress;
+ positionX = progress * (mDestinationBoundsTransformed.right - mStartBounds.left)
+ + mStartBounds.left;
+ positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
+ + mStartBounds.top;
+ }
}
+
return new RotatedPosition(degree, positionX, positionY);
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index c97ca32..7eee415 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.util;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.states.RotationHelper.deltaRotation;
import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
@@ -62,7 +61,6 @@
private final boolean mIsRecentsRtl;
private final Rect mTaskRect = new Rect();
- private boolean mDrawsBelowRecents;
private final PointF mPivot = new PointF();
private DeviceProfile mDp;
@@ -163,10 +161,6 @@
recentsViewScroll.value = scroll;
}
- public void setDrawsBelowRecents(boolean drawsBelowRecents) {
- mDrawsBelowRecents = drawsBelowRecents;
- }
-
/**
* Adds animation for all the components corresponding to transition from an app to overview.
*/
@@ -256,8 +250,8 @@
float fullScreenProgress = Utilities.boundToRange(this.fullScreenProgress.value, 0, 1);
mCurrentFullscreenParams.setProgress(
- fullScreenProgress, recentsViewScale.value, mTaskRect.width(), mDp,
- mPositionHelper);
+ fullScreenProgress, recentsViewScale.value, /*taskViewScale=*/1f, mTaskRect.width(),
+ mDp, mPositionHelper);
// Apply thumbnail matrix
RectF insets = mCurrentFullscreenParams.mCurrentDrawnInsets;
@@ -301,12 +295,6 @@
builder.withMatrix(mMatrix)
.withWindowCrop(mTmpCropRect)
.withCornerRadius(getCurrentCornerRadius());
-
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
- // When relativeLayer = 0, it reverts the surfaces back to the original order.
- builder.withRelativeLayerTo(params.getRecentsSurface(),
- mDrawsBelowRecents ? Integer.MIN_VALUE : 0);
- }
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
new file mode 100644
index 0000000..a1befc5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -0,0 +1,241 @@
+package com.android.quickstep.views;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.util.MultiValueUpdateListener;
+
+/**
+ * Create an instance via {@link #getFloatingTaskView(StatefulActivity, TaskView, RectF)} to
+ * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
+ *
+ * Can then animate the taskview using
+ * {@link #addAnimation(PendingAnimation, RectF, Rect, View, boolean)}
+ * giving a starting and ending bounds. Currently this is set to use the split placeholder view,
+ * but it could be generified.
+ *
+ * TODO: Figure out how to copy thumbnail data from existing TaskView to this view.
+ */
+public class FloatingTaskView extends FrameLayout {
+
+ private SplitPlaceholderView mSplitPlaceholderView;
+ private RectF mStartingPosition;
+ private final Launcher mLauncher;
+ private final boolean mIsRtl;
+ private final Rect mOutline = new Rect();
+ private PagedOrientationHandler mOrientationHandler;
+ private ImageView mImageView;
+
+ public FloatingTaskView(Context context) {
+ this(context, null);
+ }
+
+ public FloatingTaskView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FloatingTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ mIsRtl = Utilities.isRtl(getResources());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mImageView = findViewById(R.id.thumbnail);
+ mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ mImageView.setLayerType(LAYER_TYPE_HARDWARE, null);
+ mSplitPlaceholderView = findViewById(R.id.split_placeholder);
+ mSplitPlaceholderView.setAlpha(0);
+ mSplitPlaceholderView.setBackgroundColor(getResources().getColor(android.R.color.white));
+ }
+
+ public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
+ TaskView originalView, RectF positionOut) {
+ final BaseDragLayer dragLayer = launcher.getDragLayer();
+ ViewGroup parent = (ViewGroup) dragLayer.getParent();
+ final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
+ .inflate(R.layout.floating_split_select_view, parent, false);
+
+ floatingView.mStartingPosition = positionOut;
+ floatingView.updateInitialPositionForView(originalView);
+ final InsettableFrameLayout.LayoutParams lp =
+ (InsettableFrameLayout.LayoutParams) floatingView.getLayoutParams();
+
+ floatingView.mSplitPlaceholderView.setLayoutParams(
+ new FrameLayout.LayoutParams(lp.width, lp.height));
+ positionOut.round(floatingView.mOutline);
+ floatingView.setPivotX(0);
+ floatingView.setPivotY(0);
+
+ // Copy bounds of exiting thumbnail into ImageView
+ TaskThumbnailView thumbnail = originalView.getThumbnail();
+ floatingView.mImageView.setImageBitmap(thumbnail.getThumbnail());
+ floatingView.mImageView.setVisibility(VISIBLE);
+
+ floatingView.mOrientationHandler =
+ originalView.getRecentsView().getPagedOrientationHandler();
+ floatingView.mSplitPlaceholderView.setIcon(originalView.getIconView());
+ floatingView.mSplitPlaceholderView.getIcon()
+ .setRotation(floatingView.mOrientationHandler.getDegreesRotated());
+ parent.addView(floatingView);
+ return floatingView;
+ }
+
+ public void updateInitialPositionForView(TaskView originalView) {
+ View thumbnail = originalView.getThumbnail();
+ Rect viewBounds = new Rect(0, 0, thumbnail.getWidth(), thumbnail.getHeight());
+ Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), thumbnail, viewBounds,
+ true /* ignoreTransform */, null /* recycle */,
+ mStartingPosition);
+ mStartingPosition.offset(originalView.getTranslationX(), originalView.getTranslationY());
+ final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
+ Math.round(mStartingPosition.width()),
+ Math.round(mStartingPosition.height()));
+ initPosition(mStartingPosition, lp);
+ setLayoutParams(lp);
+ }
+
+ // TODO(194414938) set correct corner radii
+ public void update(RectF position, float progress, float windowRadius) {
+ MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+
+ float dX = mIsRtl
+ ? position.left - (lp.getMarginStart() - lp.width)
+ : position.left - lp.getMarginStart();
+ float dY = position.top - lp.topMargin;
+
+ setTranslationX(dX);
+ setTranslationY(dY);
+
+ float scaleX = position.width() / lp.width;
+ float scaleY = position.height() / lp.height;
+ setScaleX(scaleX);
+ setScaleY(scaleY);
+ float childScaleX = 1f / scaleX;
+ float childScaleY = 1f / scaleY;
+
+ invalidate();
+ // TODO(194414938) seems like this scale value could be fine tuned, some stretchiness
+ mImageView.setScaleX(1f / scaleX + scaleX * progress);
+ mImageView.setScaleY(1f / scaleY + scaleY * progress);
+ mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIcon(), childScaleX);
+ mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIcon(), childScaleY);
+ }
+
+ protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
+ mStartingPosition.set(pos);
+ lp.ignoreInsets = true;
+ // Position the floating view exactly on top of the original
+ lp.topMargin = Math.round(pos.top);
+ if (mIsRtl) {
+ lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right));
+ } else {
+ lp.setMarginStart(Math.round(pos.left));
+ }
+ // Set the properties here already to make sure they are available when running the first
+ // animation frame.
+ int left = mIsRtl
+ ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+ : lp.leftMargin;
+ layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+ }
+
+ public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
+ View viewToCover, boolean fadeWithThumbnail) {
+ final BaseDragLayer dragLayer = mLauncher.getDragLayer();
+ int[] dragLayerBounds = new int[2];
+ dragLayer.getLocationOnScreen(dragLayerBounds);
+ SplitOverlayProperties prop = new SplitOverlayProperties(endBounds,
+ startingBounds, viewToCover, dragLayerBounds[0],
+ dragLayerBounds[1]);
+
+ ValueAnimator transitionAnimator = ValueAnimator.ofFloat(0, 1);
+ animation.add(transitionAnimator);
+ long animDuration = animation.getDuration();
+ Rect crop = new Rect();
+ RectF floatingTaskViewBounds = new RectF();
+ final float initialWindowRadius = supportsRoundedCornersOnWindows(getResources())
+ ? Math.max(crop.width(), crop.height()) / 2f
+ : 0f;
+
+ if (fadeWithThumbnail) {
+ animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
+ 0, 1, ACCEL);
+ animation.addFloat(mImageView, LauncherAnimUtils.VIEW_ALPHA,
+ 1, 0, DEACCEL_3);
+ }
+
+ MultiValueUpdateListener listener = new MultiValueUpdateListener() {
+ final FloatProp mWindowRadius = new FloatProp(initialWindowRadius,
+ initialWindowRadius, 0, animDuration, LINEAR);
+ final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
+ final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
+ final FloatProp mTaskViewScaleX = new FloatProp(prop.initialTaskViewScaleX,
+ prop.finalTaskViewScaleX, 0, animDuration, LINEAR);
+ final FloatProp mTaskViewScaleY = new FloatProp(prop.initialTaskViewScaleY,
+ prop.finalTaskViewScaleY, 0, animDuration, LINEAR);
+ @Override
+ public void onUpdate(float percent, boolean initOnly) {
+ // Calculate the icon position.
+ floatingTaskViewBounds.set(startingBounds);
+ floatingTaskViewBounds.offset(mDx.value, mDy.value);
+ Utilities.scaleRectFAboutCenter(floatingTaskViewBounds, mTaskViewScaleX.value,
+ mTaskViewScaleY.value);
+
+ update(floatingTaskViewBounds, percent, mWindowRadius.value * 1);
+ }
+ };
+ transitionAnimator.addUpdateListener(listener);
+ }
+
+ private static class SplitOverlayProperties {
+
+ private final float initialTaskViewScaleX;
+ private final float initialTaskViewScaleY;
+ private final float finalTaskViewScaleX;
+ private final float finalTaskViewScaleY;
+ private final float dX;
+ private final float dY;
+
+ SplitOverlayProperties(Rect endBounds, RectF startTaskViewBounds, View view,
+ int dragLayerLeft, int dragLayerTop) {
+ float maxScaleX = endBounds.width() / startTaskViewBounds.width();
+ float maxScaleY = endBounds.height() / startTaskViewBounds.height();
+
+ initialTaskViewScaleX = view.getScaleX();
+ initialTaskViewScaleY = view.getScaleY();
+ finalTaskViewScaleX = maxScaleX;
+ finalTaskViewScaleY = maxScaleY;
+
+ // Animate the app icon to the center of the window bounds in screen coordinates.
+ float centerX = endBounds.centerX() - dragLayerLeft;
+ float centerY = endBounds.centerY() - dragLayerTop;
+
+ dX = centerX - startTaskViewBounds.centerX();
+ dY = centerY - startTaskViewBounds.centerY();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 65956d5..152c2bd 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -38,6 +38,7 @@
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.util.SplitSelectStateController;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.RecentsExtraCard;
@@ -81,7 +82,8 @@
}
@Override
- public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ public void init(OverviewActionsView actionsView,
+ SplitSelectStateController splitPlaceholderView) {
super.init(actionsView, splitPlaceholderView);
setContentAlpha(0);
}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 8c115e5..563bb53 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -180,7 +180,6 @@
} else {
mDisabledFlags &= ~disabledFlags;
}
- //
boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
LayoutUtils.setViewEnabled(this, isEnabled);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 4e1bcc9..6ea4a0d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -32,7 +32,6 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_0_5;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
@@ -129,8 +128,8 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TranslateEdgeEffect;
@@ -348,6 +347,9 @@
private static final int ADDITION_TASK_DURATION = 200;
private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f;
private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
+ private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
+
+ private static final float SIGNIFICANT_MOVE_THRESHOLD_TABLET = 0.15f;
protected final RecentsOrientedState mOrientationState;
protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
@@ -355,6 +357,13 @@
protected SurfaceTransactionApplier mSyncTransactionApplier;
protected int mTaskWidth;
protected int mTaskHeight;
+ // Used to position the top of a task in the top row of the grid
+ private float mTaskGridVerticalDiff;
+ // The vertical space one grid task takes + space between top and bottom row.
+ private float mTopBottomRowHeightDiff;
+ // mTaskGridVerticalDiff and mTopBottomRowHeightDiff summed together provides the top
+ // position for bottom row of grid tasks.
+
protected final TransformParams mLiveTileParams = new TransformParams();
protected final TaskViewSimulator mLiveTileTaskViewSimulator;
protected final Rect mLastComputedTaskSize = new Rect();
@@ -367,6 +376,7 @@
protected final Rect mTempRect = new Rect();
protected final RectF mTempRectF = new RectF();
private final PointF mTempPointF = new PointF();
+ private final Matrix mTempMatrix = new Matrix();
private final float[] mTempFloat = new float[1];
private final List<OnScrollChangedListener> mScrollListeners = new ArrayList<>();
@@ -493,9 +503,8 @@
protected boolean mRunningTaskTileHidden;
private Task mTmpRunningTask;
protected int mFocusedTaskId = -1;
- private float mFocusedTaskRatio;
- private boolean mRunningTaskIconScaledDown = false;
+ private boolean mTaskIconScaledDown = false;
private boolean mRunningTaskShowScreenshot = false;
private boolean mOverviewStateEnabled;
@@ -536,15 +545,18 @@
/**
* Placeholder view indicating where the first split screen selected app will be placed
*/
- private SplitPlaceholderView mSplitPlaceholderView;
+ private SplitSelectStateController mSplitSelectStateController;
/**
* The first task that split screen selection was initiated with. When split select state is
* initialized, we create a
- * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
- * don't actually remove the task since the user might back out. As such, we also ensure this
- * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
+ * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long, boolean)} for this
+ * TaskView but don't actually remove the task since the user might back out. As such, we also
+ * ensure this View doesn't go back into the {@link #mTaskViewPool},
+ * see {@link #onViewRemoved(View)}
*/
private TaskView mSplitHiddenTaskView;
+ private TaskView mSecondSplitHiddenTaskView;
+
/**
* Keeps track of the index of the TaskView that split screen was initialized with so we know
* where to insert it back into list of taskViews in case user backs out of entering split
@@ -554,6 +566,13 @@
* removed from recentsView
*/
private int mSplitHiddenTaskViewIndex;
+ private FloatingTaskView mFirstFloatingTaskView;
+ private FloatingTaskView mSecondFloatingTaskView;
+
+ /**
+ * The task to be removed and immediately re-added. Should not be added to task pool.
+ */
+ private TaskView mMovingTaskView;
// Keeps track of the index where the first TaskView should be
private int mTaskViewStartIndex = 0;
@@ -632,7 +651,6 @@
mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
- mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
mTintingColor = getForegroundScrimDimColor(context);
}
@@ -764,18 +782,18 @@
updateTaskStackListenerState();
}
- public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+ public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
- mSplitPlaceholderView = splitPlaceholderView;
+ mSplitSelectStateController = splitController;
}
- public SplitPlaceholderView getSplitPlaceholder() {
- return mSplitPlaceholderView;
+ public SplitSelectStateController getSplitPlaceholder() {
+ return mSplitSelectStateController;
}
public boolean isSplitSelectionActive() {
- return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
+ return mSplitSelectStateController.isSplitSelectActive();
}
@Override
@@ -818,11 +836,14 @@
public void onViewRemoved(View child) {
super.onViewRemoved(child);
- // Clear the task data for the removed child if it was visible unless it's the initial
- // taskview for entering split screen, we only pretend to dismiss the task
- if (child instanceof TaskView && child != mSplitHiddenTaskView) {
+ // Clear the task data for the removed child if it was visible unless:
+ // - It's the initial taskview for entering split screen, we only pretend to dismiss the
+ // task
+ // - It's the focused task to be moved to the front, we immediately re-add the task
+ if (child instanceof TaskView && child != mSplitHiddenTaskView
+ && child != mMovingTaskView) {
TaskView taskView = (TaskView) child;
- mHasVisibleTaskData.delete(taskView.getTask().key.id);
+ mHasVisibleTaskData.delete(taskView.getTaskId());
mTaskViewPool.recycle(taskView);
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
}
@@ -941,10 +962,20 @@
&& taskEnd <= end);
}
+ /**
+ * Returns true if the task is snapped.
+ *
+ * @param taskIndex the index of the task
+ */
+ public boolean isTaskSnapped(int taskIndex) {
+ return getScrollForPage(taskIndex + mTaskViewStartIndex)
+ == getPagedOrientationHandler().getPrimaryScroll(this);
+ }
+
public TaskView getTaskView(int taskId) {
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = getTaskViewAt(i);
- if (taskView.hasTaskId(taskId)) {
+ if (taskView.getTaskId() == taskId) {
return taskView;
}
}
@@ -959,7 +990,7 @@
// Reset the running task when leaving overview since it can still have a reference to
// its thumbnail
mTmpRunningTask = null;
- if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
+ if (mSplitSelectStateController.isSplitSelectActive()) {
cancelSplitSelect(false);
}
}
@@ -988,6 +1019,7 @@
super.onPageEndTransition();
if (isClearAllHidden()) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
+ } else {
}
if (getNextPage() > 0) {
setSwipeDownShouldLaunchApp(true);
@@ -995,6 +1027,12 @@
}
@Override
+ protected float getSignificantMoveThreshold() {
+ return mActivity.getDeviceProfile().isTablet ? SIGNIFICANT_MOVE_THRESHOLD_TABLET
+ : super.getSignificantMoveThreshold();
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
@@ -1096,6 +1134,42 @@
}
}
+ /**
+ * Moves the focused task to the front of the carousel in tablets, to minimize animation
+ * required to focus the task in grid.
+ */
+ public void moveFocusedTaskToFront() {
+ if (!(mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get())) {
+ return;
+ }
+
+ TaskView focusedTaskView = getFocusedTaskView();
+ if (focusedTaskView == null) {
+ return;
+ }
+
+ if (indexOfChild(focusedTaskView) != mCurrentPage) {
+ return;
+ }
+
+ if (mCurrentPage == mTaskViewStartIndex) {
+ return;
+ }
+
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+ int currentPageScroll = getScrollForPage(mCurrentPage);
+ mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+
+ mMovingTaskView = focusedTaskView;
+ removeView(focusedTaskView);
+ mMovingTaskView = null;
+ focusedTaskView.onRecycle();
+ addView(focusedTaskView, mTaskViewStartIndex);
+ setCurrentPage(mTaskViewStartIndex);
+
+ updateGridProperties();
+ }
+
protected void applyLoadPlan(ArrayList<Task> tasks) {
if (mPendingAnimation != null) {
mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks));
@@ -1143,6 +1217,11 @@
final TaskView taskView = (TaskView) getChildAt(pageIndex);
taskView.bind(task, mOrientationState);
}
+
+ // If the list changed, maybe the focused task doesn't exist anymore
+ if (getFocusedTaskView() == null && getTaskViewCount() > 0) {
+ mFocusedTaskId = getTaskViewAt(0).getTaskId();
+ }
updateTaskSize();
if (mNextPage == INVALID_PAGE) {
@@ -1204,8 +1283,9 @@
public void resetTaskVisuals() {
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView taskView = getTaskViewAt(i);
- if (mIgnoreResetTaskId != taskView.getTask().key.id) {
+ if (mIgnoreResetTaskId != taskView.getTaskId()) {
taskView.resetViewTransforms();
+ taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
taskView.setStableAlpha(mContentAlpha);
taskView.setFullscreenProgress(mFullscreenProgress);
taskView.setModalness(mTaskModalness);
@@ -1220,6 +1300,8 @@
mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
+ mLiveTileParams.setTargetAlpha(1);
+
// Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
// null.
if (!mRunningTaskShowScreenshot) {
@@ -1230,11 +1312,6 @@
setRunningTaskHidden(mRunningTaskTileHidden);
}
- // Force apply the scale.
- if (mIgnoreResetTaskId != mRunningTaskId) {
- applyRunningTaskIconScale();
- }
-
updateCurveProperties();
// Update the set of visible task's data
loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
@@ -1341,6 +1418,11 @@
mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(),
mLastComputedGridTaskSize, mOrientationHandler);
+ mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
+ mTopBottomRowHeightDiff =
+ mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx
+ + mRowSpacing;
+
// Force TaskView to update size from thumbnail
updateTaskSize();
@@ -1371,6 +1453,15 @@
* Updates TaskView scaling and translation required to support variable width.
*/
private void updateTaskSize() {
+ updateTaskSize(false);
+ }
+
+ /**
+ * Updates TaskView scaling and translation required to support variable width.
+ *
+ * @param isTaskDismissal indicates if update was called due to task dismissal
+ */
+ private void updateTaskSize(boolean isTaskDismissal) {
final int taskCount = getTaskViewCount();
if (taskCount == 0) {
return;
@@ -1380,18 +1471,17 @@
for (int i = 0; i < taskCount; i++) {
TaskView taskView = getTaskViewAt(i);
taskView.updateTaskSize();
- taskView.getPrimaryFullscreenTranslationProperty().set(taskView,
- accumulatedTranslationX);
- taskView.getSecondaryFullscreenTranslationProperty().set(taskView, 0f);
+ taskView.getPrimaryNonGridTranslationProperty().set(taskView, accumulatedTranslationX);
+ taskView.getSecondaryNonGridTranslationProperty().set(taskView, 0f);
// Compensate space caused by TaskView scaling.
float widthDiff =
- taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
+ taskView.getLayoutParams().width * (1 - taskView.getNonGridScale());
accumulatedTranslationX += mIsRtl ? widthDiff : -widthDiff;
}
mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX);
- updateGridProperties();
+ updateGridProperties(isTaskDismissal);
}
public void getTaskSize(Rect outRect) {
@@ -1406,19 +1496,7 @@
public Point getSelectedTaskSize() {
mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect,
mOrientationHandler);
- int taskWidth = mTempRect.width();
- int taskHeight = mTempRect.height();
- if (mFocusedTaskId != -1) {
- int boxLength = Math.max(taskWidth, taskHeight);
- if (mFocusedTaskRatio > 1) {
- taskWidth = boxLength;
- taskHeight = (int) (boxLength / mFocusedTaskRatio);
- } else {
- taskWidth = (int) (boxLength * mFocusedTaskRatio);
- taskHeight = boxLength;
- }
- }
- return new Point(taskWidth, taskHeight);
+ return new Point(mTempRect.width(), mTempRect.height());
}
/** Gets the last computed task size */
@@ -1450,13 +1528,7 @@
loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
// After scrolling, update ActionsView's visibility.
- TaskView focusedTaskView = getFocusedTaskView();
- if (focusedTaskView != null) {
- float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
- - mOrientationHandler.getPrimaryScroll(this));
- float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
- mActionsView.getScrollAlpha().setValue(Utilities.boundToRange(delta, 0, 1));
- }
+ updateActionsViewScrollAlpha();
}
// Update the high res thumbnail loader state
@@ -1464,6 +1536,20 @@
return scrolling;
}
+ private void updateActionsViewScrollAlpha() {
+ float scrollAlpha = 1f;
+ if (showAsGrid()) {
+ TaskView focusedTaskView = getFocusedTaskView();
+ if (focusedTaskView != null) {
+ float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView))
+ - mOrientationHandler.getPrimaryScroll(this));
+ float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin;
+ scrollAlpha = Utilities.boundToRange(delta, 0, 1);
+ }
+ }
+ mActionsView.getScrollAlpha().setValue(scrollAlpha);
+ }
+
/**
* Scales and adjusts translation of adjacent pages as if on a curved carousel.
*/
@@ -1505,7 +1591,8 @@
* and unloads the associated task data for tasks that are no longer visible.
*/
public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
- if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
+ boolean hasLeftOverview = !mOverviewStateEnabled && mScroller.isFinished();
+ if (hasLeftOverview || mTaskListChangeId == -1) {
// Skip loading visible task data if we've already left the overview state, or if the
// task list hasn't been loaded yet (the task views will not reflect the task list)
return;
@@ -1606,7 +1693,7 @@
setCurrentTask(-1);
mIgnoreResetTaskId = -1;
mTaskListChangeId = -1;
- mFocusedTaskId = -1;
+ mFocusedTaskId = getTaskViewCount() > 0 ? getTaskViewAt(0).getTaskId() : -1;
if (mRecentsAnimationController != null) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
@@ -1618,7 +1705,6 @@
}
setEnableDrawingLiveTile(false);
mLiveTileParams.setTargetSet(null);
- mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
// These are relatively expensive and don't need to be done this frame (RecentsView isn't
// visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -1648,11 +1734,8 @@
return getTaskView(mFocusedTaskId);
}
- /**
- * Returns the width to height ratio of the focused {@link TaskView}.
- */
- public float getFocusedTaskRatio() {
- return mFocusedTaskRatio;
+ protected @Nullable TaskView getHomeTaskView() {
+ return null;
}
/**
@@ -1691,7 +1774,7 @@
setEnableFreeScroll(false);
setEnableDrawingLiveTile(false);
setRunningTaskHidden(true);
- setRunningTaskIconScaledDown(true);
+ setTaskIconScaledDown(true);
}
/**
@@ -1699,9 +1782,7 @@
* {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}.
*/
public void onSwipeUpAnimationSuccess() {
- if (getRunningTaskView() != null) {
- animateUpRunningTaskIconScale();
- }
+ animateUpTaskIconScale();
setSwipeDownShouldLaunchApp(true);
}
@@ -1748,24 +1829,33 @@
* Called when a gesture from an app has finished, and an end target has been determined.
*/
public void onPrepareGestureEndAnimation(
- @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget) {
+ @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
+ TaskViewSimulator taskViewSimulator) {
+ mCurrentGestureEndTarget = endTarget;
+ if (endTarget == GestureState.GestureEndTarget.RECENTS) {
+ setEnableFreeScroll(true);
+ updateGridProperties();
+ }
+
if (mSizeStrategy.stateFromGestureEndTarget(endTarget)
.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
+ TaskView runningTaskView = getRunningTaskView();
+ float runningTaskPrimaryGridTranslation = 0;
+ if (runningTaskView != null && indexOfChild(runningTaskView) != getNextPage()) {
+ // Apply the gird translation to running task unless it's being snapped to.
+ runningTaskPrimaryGridTranslation = mOrientationHandler.getPrimaryValue(
+ runningTaskView.getGridTranslationX(),
+ runningTaskView.getGridTranslationY());
+ }
if (animatorSet == null) {
setGridProgress(1);
+ taskViewSimulator.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation;
} else {
animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
+ animatorSet.play(taskViewSimulator.taskPrimaryTranslation.animateToValue(
+ runningTaskPrimaryGridTranslation));
}
}
- mCurrentGestureEndTarget = endTarget;
- if (endTarget == GestureState.GestureEndTarget.NEW_TASK
- || endTarget == GestureState.GestureEndTarget.LAST_TASK) {
- // When switching to tasks in quick switch, ensures the snapped page's scroll maintain
- // invariant between quick switch and overview, to ensure a smooth animation transition.
- updateGridProperties();
- } else if (endTarget == GestureState.GestureEndTarget.RECENTS) {
- setEnableFreeScroll(true);
- }
}
/**
@@ -1783,12 +1873,8 @@
setRunningTaskViewShowScreenshot(true);
}
setRunningTaskHidden(false);
- animateUpRunningTaskIconScale();
-
- if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS
- && (!showAsGrid() || getFocusedTaskView() != null)) {
- animateActionsViewIn();
- }
+ animateUpTaskIconScale();
+ animateActionsViewIn();
mCurrentGestureEndTarget = null;
}
@@ -1830,9 +1916,7 @@
boolean runningTaskTileHidden = mRunningTaskTileHidden;
int runningTaskId = runningTaskInfo == null ? -1 : runningTaskInfo.taskId;
setCurrentTask(runningTaskId);
- if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
- setFocusedTask(runningTaskId);
- }
+ mFocusedTaskId = runningTaskId;
setCurrentPage(getRunningTaskIndex());
setRunningTaskViewShowScreenshot(false);
setRunningTaskHidden(runningTaskTileHidden);
@@ -1853,7 +1937,7 @@
if (mRunningTaskId != -1) {
// Reset the state on the old running task view
- setRunningTaskIconScaledDown(false);
+ setTaskIconScaledDown(false);
setRunningTaskViewShowScreenshot(true);
setRunningTaskHidden(false);
}
@@ -1861,15 +1945,6 @@
}
/**
- * Sets the focused task id and store the width to height ratio of the focused task.
- */
- protected void setFocusedTask(int focusedTaskId) {
- mFocusedTaskId = focusedTaskId;
- mFocusedTaskRatio =
- mLastComputedTaskSize.width() / (float) mLastComputedTaskSize.height();
- }
-
- /**
* Hides the tile associated with {@link #mRunningTaskId}
*/
public void setRunningTaskHidden(boolean isHidden) {
@@ -1894,21 +1969,13 @@
}
}
- public void setRunningTaskIconScaledDown(boolean isScaledDown) {
- if (mRunningTaskIconScaledDown != isScaledDown) {
- mRunningTaskIconScaledDown = isScaledDown;
- applyRunningTaskIconScale();
- }
- }
-
- public boolean isTaskIconScaledDown(TaskView taskView) {
- return mRunningTaskIconScaledDown && getRunningTaskView() == taskView;
- }
-
- private void applyRunningTaskIconScale() {
- TaskView firstTask = getRunningTaskView();
- if (firstTask != null) {
- firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1);
+ public void setTaskIconScaledDown(boolean isScaledDown) {
+ if (mTaskIconScaledDown != isScaledDown) {
+ mTaskIconScaledDown = isScaledDown;
+ int taskCount = getTaskViewCount();
+ for (int i = 0; i < taskCount; i++) {
+ getTaskViewAt(i).setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
+ }
}
}
@@ -1919,19 +1986,13 @@
anim.start();
}
- private void animateActionsViewOut() {
- ObjectAnimator anim = ObjectAnimator.ofFloat(
- mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 1, 0);
- anim.setDuration(TaskView.SCALE_ICON_DURATION);
- anim.start();
- }
-
- public void animateUpRunningTaskIconScale() {
- mRunningTaskIconScaledDown = false;
- TaskView firstTask = getRunningTaskView();
- if (firstTask != null) {
- firstTask.setIconScaleAnimStartProgress(0f);
- firstTask.animateIconScaleAndDimIntoView();
+ public void animateUpTaskIconScale() {
+ mTaskIconScaledDown = false;
+ int taskCount = getTaskViewCount();
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ taskView.setIconScaleAnimStartProgress(0f);
+ taskView.animateIconScaleAndDimIntoView();
}
}
@@ -1957,20 +2018,8 @@
return;
}
- final int boxLength = Math.max(mLastComputedGridTaskSize.width(),
- mLastComputedGridTaskSize.height());
int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
- /*
- * taskGridVerticalDiff is used to position the top of a task in the top row of the grid
- * heightOffset is the vertical space one grid task takes + space between top and
- * bottom row
- * Summed together they provide the top position for bottom row of grid tasks
- */
- final float taskGridVerticalDiff =
- mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
- final float heightOffset = (boxLength + taskTopMargin) + mRowSpacing;
-
int topRowWidth = 0;
int bottomRowWidth = 0;
float topAccumulatedTranslationX = 0;
@@ -1990,6 +2039,8 @@
int snappedTaskRowWidth = 0;
int snappedPage = getNextPage();
TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(snappedPage);
+ TaskView homeTaskView = getHomeTaskView();
+ TaskView nextFocusedTaskView = null;
if (!isTaskDismissal) {
mTopRowIdSet.clear();
@@ -2027,15 +2078,20 @@
// calculate the distance focused task need to shift.
focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
}
- int taskId = taskView.getTask().key.id;
+ int taskId = taskView.getTaskId();
boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskId)
: topRowWidth <= bottomRowWidth;
if (isTopRow) {
- topRowWidth += taskWidthAndSpacing;
+ if (homeTaskView != null && nextFocusedTaskView == null) {
+ // TaskView will be focused when swipe up, don't count towards row width.
+ nextFocusedTaskView = taskView;
+ } else {
+ topRowWidth += taskWidthAndSpacing;
+ }
topSet.add(i);
mTopRowIdSet.add(taskId);
- taskView.setGridTranslationY(taskGridVerticalDiff);
+ taskView.setGridTranslationY(mTaskGridVerticalDiff);
// Move horizontally into empty space.
float widthOffset = 0;
@@ -2054,7 +2110,7 @@
bottomSet.add(i);
// Move into bottom row.
- taskView.setGridTranslationY(heightOffset + taskGridVerticalDiff);
+ taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff);
// Move horizontally into empty space.
float widthOffset = 0;
@@ -2078,22 +2134,14 @@
// We need to maintain snapped task's page scroll invariant between quick switch and
// overview, so we sure snapped task's grid translation is 0, and add a non-fullscreen
// translationX that is the same as snapped task's full scroll adjustment.
- float snappedTaskFullscreenScrollAdjustment = 0;
+ float snappedTaskNonGridScrollAdjustment = 0;
float snappedTaskGridTranslationX = 0;
if (snappedTaskView != null) {
- snappedTaskFullscreenScrollAdjustment = snappedTaskView.getScrollAdjustment(
+ snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment(
/*fullscreenEnabled=*/true, /*gridEnabled=*/false);
snappedTaskGridTranslationX = gridTranslations[snappedPage - mTaskViewStartIndex];
}
- for (int i = 0; i < taskCount; i++) {
- TaskView taskView = getTaskViewAt(i);
- taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX);
- taskView.getPrimaryNonFullscreenTranslationProperty().set(taskView,
- snappedTaskFullscreenScrollAdjustment);
- taskView.getSecondaryNonFullscreenTranslationProperty().set(taskView, 0f);
- }
-
// Use the accumulated translation of the row containing the last task.
float clearAllAccumulatedTranslation = topSet.contains(taskCount - 1)
? topAccumulatedTranslationX : bottomAccumulatedTranslationX;
@@ -2126,7 +2174,7 @@
float clearAllTotalTranslationX =
clearAllAccumulatedTranslation + clearAllShorterRowCompensation
- + clearAllShortTotalCompensation + snappedTaskFullscreenScrollAdjustment;
+ + clearAllShortTotalCompensation + snappedTaskNonGridScrollAdjustment;
if (focusedTaskIndex < taskCount) {
// Shift by focused task's width and spacing if a task is focused.
clearAllTotalTranslationX +=
@@ -2136,15 +2184,23 @@
// Make sure there are enough space between snapped page and ClearAllButton, for the case
// of swiping up after quick switch.
if (snappedTaskView != null) {
- int distanceFromClearAll = longRowWidth - snappedTaskRowWidth;
+ int distanceFromClearAll = longRowWidth - snappedTaskRowWidth + mPageSpacing;
+ // ClearAllButton should be off screen when snapped task is in its snapped position.
int minimumDistance =
- mLastComputedGridSize.width() - snappedTaskView.getLayoutParams().width;
+ mTaskWidth - snappedTaskView.getLayoutParams().width
+ + (mLastComputedGridSize.width() - mTaskWidth) / 2;
if (distanceFromClearAll < minimumDistance) {
int distanceDifference = minimumDistance - distanceFromClearAll;
- clearAllTotalTranslationX += mIsRtl ? -distanceDifference : distanceDifference;
+ snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference;
}
}
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX
+ + snappedTaskNonGridScrollAdjustment);
+ }
+
mClearAllButton.setGridTranslationPrimary(
clearAllTotalTranslationX - snappedTaskGridTranslationX);
mClearAllButton.setGridScrollOffset(
@@ -2158,8 +2214,8 @@
if (taskView1 == null || taskView2 == null) {
return false;
}
- int taskId1 = taskView1.getTask().key.id;
- int taskId2 = taskView2.getTask().key.id;
+ int taskId1 = taskView1.getTaskId();
+ int taskId2 = taskView2.getTaskId();
if (taskId1 == mFocusedTaskId || taskId2 == mFocusedTaskId) {
return false;
}
@@ -2236,46 +2292,23 @@
PendingAnimation anim) {
// Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
// alpha is set to 0 so that it can be recycled in the view pool properly
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
+ anim.setFloat(mLiveTileParams, TransformParams.TARGET_ALPHA, 0,
+ clampToProgress(ACCEL, 0, 0.5f));
+ }
anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
- SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
+ FloatProperty<TaskView> secondaryViewTranslate =
+ taskView.getSecondaryDissmissTranslationProperty();
+ int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
+ int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
ResourceProvider rp = DynamicResource.provider(mActivity);
SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
.setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
.setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
- FloatProperty<TaskView> dismissingTaskViewTranslate =
- taskView.getSecondaryDissmissTranslationProperty();
- // TODO(b/186800707) translate entire grid size distance
- int translateDistance = mOrientationHandler.getSecondaryDimension(taskView);
- int positiveNegativeFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
- if (splitController.isSplitSelectActive()) {
- // Have the task translate towards whatever side was just pinned
- int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitController
- .getActiveSplitPositionOption(), mActivity.getDeviceProfile());
- switch (dir) {
- case PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE:
- dismissingTaskViewTranslate = taskView
- .getSecondaryDissmissTranslationProperty();
- positiveNegativeFactor = -1;
- break;
- case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE:
- dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
- positiveNegativeFactor = 1;
- break;
-
- case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_NEGATIVE:
- dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
- positiveNegativeFactor = -1;
- break;
- default:
- throw new IllegalStateException("Invalid split task translation: " + dir);
- }
- }
- // Double translation distance so dismissal drag is the full height, as we only animate
- // the drag for the first half of the progress.
- anim.add(ObjectAnimator.ofFloat(taskView, dismissingTaskViewTranslate,
- positiveNegativeFactor * translateDistance * 2).setDuration(duration), LINEAR, sp);
+ anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
+ verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
&& taskView.isRunningTask()) {
@@ -2289,8 +2322,38 @@
}
}
- public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
- boolean shouldRemoveTask, long duration) {
+ /**
+ * Places an {@link FloatingTaskView} on top of the thumbnail for {@link #mSplitHiddenTaskView}
+ * and then animates it into the split position that was desired
+ */
+ private void createInitialSplitSelectAnimation(PendingAnimation anim) {
+ float placeholderHeight = getResources().getDimension(R.dimen.split_placeholder_size);
+ mOrientationHandler.getInitialSplitPlaceholderBounds((int) placeholderHeight,
+ mActivity.getDeviceProfile(),
+ mSplitSelectStateController.getActiveSplitPositionOption(), mTempRect);
+
+ RectF startingTaskRect = new RectF();
+ mSplitHiddenTaskView.setVisibility(INVISIBLE);
+ mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+ mSplitHiddenTaskView, startingTaskRect);
+ mFirstFloatingTaskView.setAlpha(1);
+ mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
+ mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
+ }
+
+ /**
+ * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
+ * @param dismissedTaskView the {@link TaskView} to be dismissed
+ * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated
+ * @param shouldRemoveTask whether the associated {@link Task} should be removed from
+ * ActivityManager after dismissal
+ * @param duration duration of the animation
+ * @param dismissingForSplitSelection task dismiss animation is used for entering split
+ * selection state from app icon
+ */
+ public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
+ boolean animateTaskView, boolean shouldRemoveTask, long duration,
+ boolean dismissingForSplitSelection) {
if (mPendingAnimation != null) {
mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
}
@@ -2301,30 +2364,69 @@
return anim;
}
+ boolean showAsGrid = showAsGrid();
+ int taskCount = getTaskViewCount();
+ int dismissedIndex = indexOfChild(dismissedTaskView);
+ int dismissedTaskId = dismissedTaskView.getTaskId();
+
+ // Grid specific properties.
+ boolean isFocusedTaskDismissed = false;
+ TaskView nextFocusedTaskView = null;
+ boolean nextFocusedTaskFromTop = false;
+ float dismissedTaskWidth = 0;
+ float nextFocusedTaskWidth = 0;
+
+ // Non-grid specific properties.
int[] oldScroll = new int[count];
int[] newScroll = new int[count];
- getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
- getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
- int taskCount = getTaskViewCount();
int scrollDiffPerPage = 0;
- if (count > 1) {
- scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
- }
- int draggedIndex = indexOfChild(taskView);
-
- boolean isFocusedTaskDismissed = taskView.getTask().key.id == mFocusedTaskId;
- if (isFocusedTaskDismissed && showAsGrid()) {
- anim.setFloat(mActionsView, VIEW_ALPHA, 0, clampToProgress(ACCEL_0_5, 0, 0.5f));
- }
- float dismissedTaskWidth = taskView.getLayoutParams().width + mPageSpacing;
boolean needsCurveUpdates = false;
+
+ if (showAsGrid) {
+ dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing;
+ isFocusedTaskDismissed = dismissedTaskId == mFocusedTaskId;
+ if (isFocusedTaskDismissed) {
+ nextFocusedTaskFromTop =
+ mTopRowIdSet.size() > 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
+ // Pick the next focused task from the preferred row.
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ if (taskView == dismissedTaskView) {
+ continue;
+ }
+ boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskId());
+ if ((nextFocusedTaskFromTop && isTopRow
+ || (!nextFocusedTaskFromTop && !isTopRow))) {
+ nextFocusedTaskView = taskView;
+ break;
+ }
+ }
+ if (nextFocusedTaskView != null) {
+ nextFocusedTaskWidth =
+ nextFocusedTaskView.getLayoutParams().width + mPageSpacing;
+ }
+ }
+ } else {
+ getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
+ getPageScrolls(newScroll, false,
+ v -> v.getVisibility() != GONE && v != dismissedTaskView);
+ if (count > 1) {
+ scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]);
+ }
+ }
+
+ int distanceFromDismissedTask = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
- if (child == taskView) {
+ if (child == dismissedTaskView) {
if (animateTaskView) {
- addDismissedTaskAnimations(taskView, duration, anim);
+ if (dismissingForSplitSelection) {
+ createInitialSplitSelectAnimation(anim);
+ } else {
+ addDismissedTaskAnimations(dismissedTaskView, duration, anim);
+ }
}
- } else if (!showAsGrid()) {
+ } else if (!showAsGrid) {
// Compute scroll offsets from task dismissal for animation.
// If we just take newScroll - oldScroll, everything to the right of dragged task
// translates to the left. We need to offset this in some cases:
@@ -2333,15 +2435,15 @@
// - Current page is rightmost page (leftmost for RTL)
// - Dragging an adjacent page on the left side (right side for RTL)
int offset = mIsRtl ? scrollDiffPerPage : 0;
- if (mCurrentPage == draggedIndex) {
+ if (mCurrentPage == dismissedIndex) {
int lastPage = taskCount - 1;
if (mCurrentPage == lastPage) {
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
} else {
- // Dragging an adjacent page.
+ // Dismissing an adjacent page.
int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
- if (draggedIndex == negativeAdjacent) {
+ if (dismissedIndex == negativeAdjacent) {
offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
}
}
@@ -2354,7 +2456,7 @@
float additionalDismissDuration =
ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
- i - draggedIndex);
+ i - dismissedIndex);
anim.setFloat(child, translationProperty, scrollDiff, clampToProgress(LINEAR,
Utilities.boundToRange(INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ additionalDismissDuration, 0f, 1f), 1));
@@ -2371,21 +2473,46 @@
needsCurveUpdates = true;
}
} else if (child instanceof TaskView) {
+ TaskView taskView = (TaskView) child;
+ if (isFocusedTaskDismissed) {
+ if (!isSameGridRow(taskView, nextFocusedTaskView)) {
+ continue;
+ }
+ } else {
+ if (i < dismissedIndex || !isSameGridRow(taskView, dismissedTaskView)) {
+ continue;
+ }
+ }
// Animate task with index >= dismissed index and in the same row as the
- // dismissed index, or if the dismissed task was the focused task. Offset
- // successive task dismissal durations for a staggered effect.
- if (isFocusedTaskDismissed || (i >= draggedIndex && isSameGridRow((TaskView) child,
- taskView))) {
- FloatProperty translationProperty =
- ((TaskView) child).getPrimaryDismissTranslationProperty();
- float additionalDismissDuration =
- ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs(
- i - draggedIndex);
- anim.setFloat(child, translationProperty,
- !mIsRtl ? -dismissedTaskWidth : dismissedTaskWidth,
- clampToProgress(LINEAR, Utilities.boundToRange(
- INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- + additionalDismissDuration, 0f, 1f), 1));
+ // dismissed index or next focused index. Offset successive task dismissal
+ // durations for a staggered effect.
+ float animationStartProgress = Utilities.boundToRange(
+ INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ * ++distanceFromDismissedTask, 0f, 1f);
+ if (taskView == nextFocusedTaskView) {
+ // Enlarge the task to be focused next, and translate into focus position.
+ float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
+ anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
+ clampToProgress(LINEAR, animationStartProgress, 1f));
+ anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
+ mIsRtl ? dismissedTaskWidth : -dismissedTaskWidth,
+ clampToProgress(LINEAR, animationStartProgress, 1f));
+ float secondaryTranslation = -mTaskGridVerticalDiff;
+ if (!nextFocusedTaskFromTop) {
+ secondaryTranslation -= mTopBottomRowHeightDiff;
+ }
+ anim.setFloat(taskView, taskView.getSecondaryDissmissTranslationProperty(),
+ secondaryTranslation,
+ clampToProgress(LINEAR, animationStartProgress, 1f));
+ anim.setFloat(taskView, TaskView.FOCUS_TRANSITION, 0f,
+ clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
+ } else {
+ float primaryTranslation =
+ isFocusedTaskDismissed ? nextFocusedTaskWidth : dismissedTaskWidth;
+ anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
+ mIsRtl ? primaryTranslation : -primaryTranslation,
+ clampToProgress(LINEAR, animationStartProgress, 1f));
}
}
}
@@ -2396,15 +2523,16 @@
// Add a tiny bit of translation Z, so that it draws on top of other views
if (animateTaskView) {
- taskView.setTranslationZ(0.1f);
+ dismissedTaskView.setTranslationZ(0.1f);
}
mPendingAnimation = anim;
+ final TaskView finalNextFocusedTaskView = nextFocusedTaskView;
mPendingAnimation.addEndListener(new Consumer<Boolean>() {
@Override
public void accept(Boolean success) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
- && taskView.isRunningTask() && success) {
+ && dismissedTaskView.isRunningTask() && success) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
() -> onEnd(success));
} else {
@@ -2416,15 +2544,16 @@
private void onEnd(boolean success) {
if (success) {
if (shouldRemoveTask) {
- if (taskView.getTask() != null) {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
+ if (dismissedTaskView.getTask() != null) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+ && dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> removeTaskInternal(taskView));
+ () -> removeTaskInternal(dismissedTaskId));
} else {
- removeTaskInternal(taskView);
+ removeTaskInternal(dismissedTaskId);
}
mActivity.getStatsLogManager().logger()
- .withItemInfo(taskView.getItemInfo())
+ .withItemInfo(dismissedTaskView.getItemInfo())
.log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
}
}
@@ -2434,32 +2563,34 @@
resetTaskVisuals();
int pageToSnapTo = mCurrentPage;
- // Snap to start if focused task was dismissed, as after quick switch it could
- // be at any page but the focused task always displays at the start.
- if (taskView.getTask().key.id == mFocusedTaskId) {
- pageToSnapTo = mTaskViewStartIndex;
- } else if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount()
- - 1)) {
+ if ((dismissedIndex < pageToSnapTo && !showAsGrid)
+ || pageToSnapTo == taskCount - 1) {
pageToSnapTo -= 1;
}
- removeViewInLayout(taskView);
+ if (showAsGrid) {
+ int primaryScroll = mOrientationHandler.getPrimaryScroll(RecentsView.this);
+ int currentPageScroll = getScrollForPage(pageToSnapTo);
+ mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
+ }
+ removeViewInLayout(dismissedTaskView);
+ mTopRowIdSet.remove(dismissedTaskId);
- if (getTaskViewCount() == 0) {
+ if (taskCount == 1) {
removeViewInLayout(mClearAllButton);
startHome();
} else {
- snapToPageImmediately(pageToSnapTo);
- dispatchScrollChanged();
- // Grid got messed up, reapply.
- updateGridProperties(true);
- if (showAsGrid() && getFocusedTaskView() == null
- && mActionsView.getVisibilityAlpha().getValue() == 1) {
- animateActionsViewOut();
+ // Update focus task and its size.
+ if (finalNextFocusedTaskView != null) {
+ mFocusedTaskId = finalNextFocusedTaskView.getTaskId();
+ mTopRowIdSet.remove(mFocusedTaskId);
+ finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
}
+ updateTaskSize(true);
+ // Update scroll and snap to page.
+ updateScrollSynchronously();
+ setCurrentPage(pageToSnapTo);
+ dispatchScrollChanged();
}
- // Update the layout synchronously so that the position of next view is
- // immediately available.
- onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
}
onDismissAnimationEnds();
mPendingAnimation = null;
@@ -2468,16 +2599,15 @@
return anim;
}
- private void removeTaskInternal(TaskView taskView) {
- UI_HELPER_EXECUTOR.getHandler().postDelayed(() ->
- ActivityManagerWrapper.getInstance().removeTask(
- taskView.getTask().key.id),
+ private void removeTaskInternal(int dismissedTaskId) {
+ UI_HELPER_EXECUTOR.getHandler().postDelayed(
+ () -> ActivityManagerWrapper.getInstance().removeTask(dismissedTaskId),
REMOVE_TASK_WAIT_FOR_APP_STOP_MS);
}
/**
* @return {@code true} if one of the task thumbnails would intersect/overlap with the
- * {@link #mSplitPlaceholderView}
+ * {@link #mFirstFloatingTaskView}
*/
public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition
int stagePosition) {
@@ -2502,7 +2632,8 @@
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
TaskView taskView = getTaskViewAt(i);
- if (taskView == mSplitHiddenTaskView && taskView != getFocusedTaskView()) {
+ if (taskView == mSplitHiddenTaskView
+ && !(showAsGrid() && taskView == getFocusedTaskView())) {
// Case where the hidden task view would have overlapped w/ placeholder,
// but because it's going to hide we don't care
// TODO (b/187312247) edge case for thumbnails that are off screen but scroll on
@@ -2517,6 +2648,7 @@
}
protected void onDismissAnimationEnds() {
+ AccessibilityManagerCompat.sendDismissAnimationEndsEventToTest(getContext());
}
public PendingAnimation createAllTasksDismissAnimation(long duration) {
@@ -2578,7 +2710,7 @@
public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
- DISMISS_TASK_DURATION));
+ DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/));
}
@SuppressWarnings("unused")
@@ -2650,7 +2782,7 @@
mContentAlpha = alpha;
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView child = getTaskViewAt(i);
- if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
+ if (!mRunningTaskTileHidden || child.getTaskId() != mRunningTaskId) {
child.setStableAlpha(alpha);
}
}
@@ -2779,6 +2911,12 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // If we're going to HOME, avoid unnecessary onLayout that cause TaskViews to re-arrange
+ // during animation to HOME.
+ if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.HOME) {
+ return;
+ }
+
super.onLayout(changed, left, top, right, bottom);
updateEmptyStateUi(changed);
@@ -2793,6 +2931,7 @@
mLastComputedTaskStartPushOutDistance = null;
mLastComputedTaskEndPushOutDistance = null;
updatePageOffsets();
+ mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
: IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
@@ -2875,6 +3014,12 @@
outRect.offset(taskView.getPersistentTranslationX(),
taskView.getPersistentTranslationY());
outRect.top += mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+
+ mTempMatrix.reset();
+ float persistentScale = taskView.getPersistentScale();
+ mTempMatrix.postScale(persistentScale, persistentScale,
+ mIsRtl ? outRect.right : outRect.left, outRect.top);
+ mTempMatrix.mapRect(outRect);
}
outRect.offset(mOrientationHandler.getPrimaryValue(-midPointScroll, 0),
mOrientationHandler.getSecondaryValue(-midPointScroll, 0));
@@ -2967,8 +3112,11 @@
protected void setTaskViewsSecondarySplitTranslation(float translation) {
mTaskViewsSecondarySplitTranslation = translation;
for (int i = 0; i < getTaskViewCount(); i++) {
- TaskView task = getTaskViewAt(i);
- task.getSecondarySplitTranslationProperty().set(task, translation);
+ TaskView taskView = getTaskViewAt(i);
+ if (taskView == mSplitHiddenTaskView) {
+ continue;
+ }
+ taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
}
}
@@ -2984,30 +3132,60 @@
public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
mSplitHiddenTaskView = taskView;
- SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
taskView.getBottom());
- splitController.setInitialTaskSelect(taskView, splitPositionOption, initialBounds);
+ mSplitSelectStateController.setInitialTaskSelect(taskView,
+ splitPositionOption, initialBounds);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
- mSplitPlaceholderView.setLayoutParams(
- splitController.getLayoutParamsForActivePosition(getResources(),
- mActivity.getDeviceProfile()));
- mSplitPlaceholderView.setIcon(taskView.getIconView());
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ finishRecentsAnimation(true, null);
+ }
}
public PendingAnimation createSplitSelectInitAnimation() {
int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
- return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
+ return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration,
+ true /* dismissingForSplitSelection*/);
}
public void confirmSplitSelect(TaskView taskView) {
- mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
- resetTaskVisuals();
- setTranslationY(0);
+ RectF secondTaskStartingBounds = new RectF();
+ Rect secondTaskEndingBounds = new Rect();
+ // TODO(194414938) starting bounds seem slightly off, investigate
+ Rect firstTaskStartingBounds = new Rect();
+ Rect firstTaskEndingBounds = mTempRect;
+ int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+ PendingAnimation pendingAnimation = new PendingAnimation(duration);
+
+ int halfDividerSize = getResources()
+ .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+ mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
+ mActivity.getDeviceProfile(),
+ mSplitSelectStateController.getActiveSplitPositionOption(), firstTaskEndingBounds,
+ secondTaskEndingBounds);
+
+ mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
+ mFirstFloatingTaskView.addAnimation(pendingAnimation,
+ new RectF(firstTaskStartingBounds), firstTaskEndingBounds, mFirstFloatingTaskView,
+ false /*fadeWithThumbnail*/);
+
+ mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+ taskView, secondTaskStartingBounds);
+ mSecondFloatingTaskView.setAlpha(1);
+ mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
+ secondTaskEndingBounds, taskView.getThumbnail(),
+ true /*fadeWithThumbnail*/);
+ pendingAnimation.addEndListener(aBoolean -> {
+ mSplitSelectStateController.setSecondTaskId(taskView);
+ resetFromSplitSelectionState();
+ });
+ mSecondSplitHiddenTaskView = taskView;
+ taskView.setVisibility(INVISIBLE);
+ pendingAnimation.buildAnim().start();
}
public PendingAnimation cancelSplitSelect(boolean animate) {
- SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
+ SplitSelectStateController splitController = mSplitSelectStateController;
SplitPositionOption splitOption = splitController.getActiveSplitPositionOption();
Rect initialBounds = splitController.getInitialBounds();
splitController.resetState();
@@ -3120,8 +3298,19 @@
}
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
resetTaskVisuals();
+ mSplitHiddenTaskView.setVisibility(VISIBLE);
mSplitHiddenTaskView = null;
+ mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
+ mSecondSplitHiddenTaskView = null;
mSplitHiddenTaskViewIndex = -1;
+ if (mFirstFloatingTaskView != null) {
+ mActivity.getRootView().removeView(mFirstFloatingTaskView);
+ mFirstFloatingTaskView = null;
+ }
+ if (mSecondFloatingTaskView != null) {
+ mActivity.getRootView().removeView(mSecondFloatingTaskView);
+ mSecondFloatingTaskView = null;
+ }
}
private void updateDeadZoneRects() {
@@ -3480,9 +3669,13 @@
}
/**
- * Updates page scroll synchronously and layout child views.
+ * Updates page scroll synchronously after measure and layout child views.
*/
public void updateScrollSynchronously() {
+ // onMeasure is needed to update child's measured width which is used in scroll calculation,
+ // in case TaskView sizes has changed when being focused/unfocused.
+ onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
+ makeMeasureSpec(getMeasuredHeight(), EXACTLY));
onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom());
updateMinAndMaxScrollX();
}
@@ -3545,18 +3738,28 @@
}
boolean pageScrollChanged = false;
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- float scrollDiff = 0;
- if (child instanceof TaskView) {
- scrollDiff = ((TaskView) child).getScrollAdjustment(showAsFullscreen, showAsGrid);
- } else if (child instanceof ClearAllButton) {
- scrollDiff = ((ClearAllButton) child).getScrollAdjustment(showAsFullscreen,
- showAsGrid);
- }
- final int pageScroll = newPageScrolls[i] + (int) scrollDiff;
+ int clearAllIndex = indexOfChild(mClearAllButton);
+ int clearAllScroll = 0;
+ int clearAllWidth = mOrientationHandler.getPrimarySize(mClearAllButton);
+ if (clearAllIndex != -1 && clearAllIndex < outPageScrolls.length) {
+ float scrollDiff = mClearAllButton.getScrollAdjustment(showAsFullscreen, showAsGrid);
+ clearAllScroll = newPageScrolls[clearAllIndex] + (int) scrollDiff;
+ if (outPageScrolls[clearAllIndex] != clearAllScroll) {
+ pageScrollChanged = true;
+ outPageScrolls[clearAllIndex] = clearAllScroll;
+ }
+ }
+
+ final int taskCount = getTaskViewCount();
+ for (int i = 0; i < taskCount; i++) {
+ TaskView taskView = getTaskViewAt(i);
+ float scrollDiff = taskView.getScrollAdjustment(showAsFullscreen, showAsGrid);
+ int pageScroll = newPageScrolls[i + mTaskViewStartIndex] + (int) scrollDiff;
+ if ((mIsRtl && pageScroll < clearAllScroll + clearAllWidth)
+ || (!mIsRtl && pageScroll > clearAllScroll - clearAllWidth)) {
+ pageScroll = clearAllScroll + (mIsRtl ? clearAllWidth : -clearAllWidth);
+ }
if (outPageScrolls[i] != pageScroll) {
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
@@ -3680,6 +3883,7 @@
public void setOverviewGridEnabled(boolean overviewGridEnabled) {
if (mOverviewGridEnabled != overviewGridEnabled) {
mOverviewGridEnabled = overviewGridEnabled;
+ updateActionsViewScrollAlpha();
// Request layout to ensure scroll position is recalculated with updated mGridProgress.
requestLayout();
}
@@ -3825,11 +4029,6 @@
&& mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS;
}
- public boolean shouldShowOverviewActionsForState(STATE_TYPE state) {
- return !state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())
- || getFocusedTaskView() != null;
- }
-
/**
* Used to register callbacks for when our empty message state changes.
*
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index bb8bc11..a712d1a 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -22,6 +22,8 @@
import android.view.Gravity;
import android.widget.FrameLayout;
+import androidx.annotation.Nullable;
+
import com.android.quickstep.util.SplitSelectStateController;
public class SplitPlaceholderView extends FrameLayout {
@@ -55,6 +57,11 @@
return mSplitController;
}
+ @Nullable
+ public IconView getIcon() {
+ return mIcon;
+ }
+
public void setIcon(IconView icon) {
if (mIcon == null) {
mIcon = new IconView(getContext());
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 906e854..1345a94 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,6 @@
package com.android.quickstep.views;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
import android.animation.Animator;
@@ -221,17 +220,7 @@
menuOptionView, mActivity.getDeviceProfile());
menuOptionView.setEnabled(menuOption.isEnabled());
menuOptionView.setAlpha(menuOption.isEnabled() ? 1 : 0.5f);
- menuOptionView.setOnClickListener(view -> {
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && !menuOption.hasFinishRecentsInAction()) {
- RecentsView recentsView = mTaskView.getRecentsView();
- recentsView.switchToScreenshot(null,
- () -> recentsView.finishRecentsAnimation(true /* toRecents */,
- false /* shouldPip */,
- () -> menuOption.onClick(view)));
- } else {
- menuOption.onClick(view);
- }
- });
+ menuOptionView.setOnClickListener(menuOption::onClick);
mOptionLayout.addView(menuOptionView);
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index c70596d..1ced86b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -19,7 +19,6 @@
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
@@ -32,8 +31,6 @@
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
@@ -85,7 +82,6 @@
private TaskOverlay mOverlay;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final Paint mClearPaint = new Paint();
private final Paint mDimmingPaintAfterClearing = new Paint();
private final int mDimColor;
@@ -116,7 +112,6 @@
super(context, attrs, defStyleAttr);
mPaint.setFilterBitmap(true);
mBackgroundPaint.setColor(Color.WHITE);
- mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mActivity = BaseActivity.fromContext(context);
// Initialize with placeholder value. It is overridden later by TaskView
mFullscreenParams = TEMP_PARAMS.get(context);
@@ -168,6 +163,7 @@
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(mBitmapShader);
updateThumbnailMatrix();
+ refreshOverlay();
} else {
mBitmapShader = null;
mThumbnailData = null;
@@ -309,17 +305,7 @@
float cornerRadius) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
- // TODO(b/189265196): Temporary fix to align the surface with the cutout perfectly.
- // Round up only when the live tile task is displayed in Overview.
- float rounding = comp(mFullscreenParams.mFullscreenProgress);
- float left = x + rounding / 2;
- float top = y + rounding / 2;
- float right = width - rounding;
- float bottom = height - rounding;
-
- canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
- mClearPaint);
- canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
+ canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
mDimmingPaintAfterClearing);
return;
}
@@ -431,7 +417,9 @@
*/
public static class PreviewPositionHelper {
- // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
+ private static final RectF EMPTY_RECT_F = new RectF();
+
+ // Contains the portion of the thumbnail that is unclipped when fullscreen progress = 1.
private final RectF mClippedInsets = new RectF();
private final Matrix mMatrix = new Matrix();
private boolean mIsOrientationChanged;
@@ -636,15 +624,17 @@
break;
}
mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
- mMatrix.postTranslate(translateX - mClippedInsets.left,
- translateY - mClippedInsets.top);
+ mMatrix.postTranslate(translateX, translateY);
+ if (TaskView.FULL_THUMBNAIL) {
+ mMatrix.postTranslate(-mClippedInsets.left, -mClippedInsets.top);
+ }
}
/**
* Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
*/
public RectF getInsetsToDrawInFullscreen() {
- return mClippedInsets;
+ return TaskView.FULL_THUMBNAIL ? mClippedInsets : EMPTY_RECT_F;
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2d322e9..159052a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -147,13 +147,18 @@
*/
public static final boolean CLIP_STATUS_AND_NAV_BARS = false;
+ /**
+ * Should the TaskView scale down to fit whole thumbnail in fullscreen.
+ */
+ public static final boolean FULL_THUMBNAIL = false;
+
private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
public static final long SCALE_ICON_DURATION = 120;
private static final long DIM_ANIM_DURATION = 700;
- private static final Interpolator FULLSCREEN_INTERPOLATOR = ACCEL_DEACCEL;
+ private static final Interpolator GRID_INTERPOLATOR = ACCEL_DEACCEL;
/**
* This technically can be a vanilla {@link TouchDelegate} class, however that class requires
@@ -167,7 +172,7 @@
private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
Collections.singletonList(new Rect());
- private static final FloatProperty<TaskView> FOCUS_TRANSITION =
+ public static final FloatProperty<TaskView> FOCUS_TRANSITION =
new FloatProperty<TaskView>("focusTransition") {
@Override
public void setValue(TaskView taskView, float v) {
@@ -284,55 +289,42 @@
}
};
- private static final FloatProperty<TaskView> FULLSCREEN_TRANSLATION_X =
- new FloatProperty<TaskView>("fullscreenTranslationX") {
+ private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_X =
+ new FloatProperty<TaskView>("nonGridTranslationX") {
@Override
public void setValue(TaskView taskView, float v) {
- taskView.setFullscreenTranslationX(v);
+ taskView.setNonGridTranslationX(v);
}
@Override
public Float get(TaskView taskView) {
- return taskView.mFullscreenTranslationX;
+ return taskView.mNonGridTranslationX;
}
};
- private static final FloatProperty<TaskView> FULLSCREEN_TRANSLATION_Y =
- new FloatProperty<TaskView>("fullscreenTranslationY") {
+ private static final FloatProperty<TaskView> NON_GRID_TRANSLATION_Y =
+ new FloatProperty<TaskView>("nonGridTranslationY") {
@Override
public void setValue(TaskView taskView, float v) {
- taskView.setFullscreenTranslationY(v);
+ taskView.setNonGridTranslationY(v);
}
@Override
public Float get(TaskView taskView) {
- return taskView.mFullscreenTranslationY;
+ return taskView.mNonGridTranslationY;
}
};
- private static final FloatProperty<TaskView> NON_FULLSCREEN_TRANSLATION_X =
- new FloatProperty<TaskView>("nonFullscreenTranslationX") {
+ public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
+ new FloatProperty<TaskView>("snapshotScale") {
@Override
public void setValue(TaskView taskView, float v) {
- taskView.setNonFullscreenTranslationX(v);
+ taskView.setSnapshotScale(v);
}
@Override
public Float get(TaskView taskView) {
- return taskView.mNonFullscreenTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> NON_FULLSCREEN_TRANSLATION_Y =
- new FloatProperty<TaskView>("nonFullscreenTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setNonFullscreenTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mNonFullscreenTranslationY;
+ return taskView.mSnapshotView.getScaleX();
}
};
@@ -344,7 +336,8 @@
private final DigitalWellBeingToast mDigitalWellBeingToast;
private float mFullscreenProgress;
private float mGridProgress;
- private float mFullscreenScale = 1;
+ private float mNonGridScale = 1;
+ private float mDismissScale = 1;
private final FullscreenDrawParams mCurrentFullscreenParams;
private final StatefulActivity mActivity;
@@ -356,16 +349,14 @@
private float mTaskResistanceTranslationX;
private float mTaskResistanceTranslationY;
// The following translation variables should only be used in the same orientation as Launcher.
- private float mFullscreenTranslationX;
- private float mFullscreenTranslationY;
- // Applied as a complement to fullscreenTranslation, for adjusting the carousel overview, or the
- // in transition carousel before forming the grid on tablets.
- private float mNonFullscreenTranslationX;
- private float mNonFullscreenTranslationY;
private float mBoxTranslationY;
// The following grid translations scales with mGridProgress.
private float mGridTranslationX;
private float mGridTranslationY;
+ // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
+ // switch.
+ private float mNonGridTranslationX;
+ private float mNonGridTranslationY;
// Used when in SplitScreenSelectState
private float mSplitSelectTranslationY;
private float mSplitSelectTranslationX;
@@ -524,8 +515,8 @@
return mTask;
}
- public boolean hasTaskId(int taskId) {
- return mTask != null && mTask.key != null && mTask.key.id == taskId;
+ public int getTaskId() {
+ return mTask != null && mTask.key != null ? mTask.key.id : -1;
}
public TaskThumbnailView getThumbnail() {
@@ -540,6 +531,9 @@
if (getTask() == null) {
return;
}
+ if (confirmSecondSplitSelectApp()) {
+ return;
+ }
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
if (!mIsClickableAsLiveTile) {
return;
@@ -558,7 +552,7 @@
if (targets == null) {
// If the recents animation is cancelled somehow between the parent if block and
// here, try to launch the task as a non live tile task.
- launcherNonLiveTileTask();
+ launchTaskAnimated();
return;
}
@@ -570,31 +564,28 @@
recentsView.getDepthController());
anim.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(Animator animator) {
- recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
- }
-
- @Override
public void onAnimationEnd(Animator animator) {
- recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
mIsClickableAsLiveTile = true;
}
});
anim.start();
} else {
- launcherNonLiveTileTask();
+ launchTaskAnimated();
}
mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
}
- private void launcherNonLiveTileTask() {
- if (mActivity.isInState(OVERVIEW_SPLIT_SELECT)) {
- // User tapped to select second split screen app
+ /**
+ * @return {@code true} if user is already in split select mode and this tap was to choose the
+ * second app. {@code false} otherwise
+ */
+ private boolean confirmSecondSplitSelectApp() {
+ boolean isSelectingSecondSplitApp = mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+ if (isSelectingSecondSplitApp) {
getRecentsView().confirmSplitSelect(this);
- } else {
- launchTaskAnimated();
}
+ return isSelectingSecondSplitApp;
}
/**
@@ -747,7 +738,16 @@
private void setIcon(Drawable icon) {
if (icon != null) {
mIconView.setDrawable(icon);
- mIconView.setOnClickListener(v -> showTaskMenu());
+ mIconView.setOnClickListener(v -> {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+ RecentsView recentsView = getRecentsView();
+ recentsView.switchToScreenshot(
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+ this::showTaskMenu));
+ } else {
+ showTaskMenu();
+ }
+ });
mIconView.setOnLongClickListener(v -> {
requestDisallowInterceptTouchEvent(true);
return showTaskMenu();
@@ -800,7 +800,7 @@
mIconView.setRotation(orientationHandler.getDegreesRotated());
snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
mSnapshotView.setLayoutParams(snapshotParams);
- getThumbnail().getTaskOverlay().updateOrientationState(orientationState);
+ mSnapshotView.getTaskOverlay().updateOrientationState(orientationState);
}
private void setIconAndDimTransitionProgress(float progress, boolean invert) {
@@ -862,6 +862,7 @@
mSplitSelectTranslationX = 0f;
mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY =
mSplitSelectTranslationY = 0f;
+ setSnapshotScale(1f);
applyTranslationX();
applyTranslationY();
setTranslationZ(0);
@@ -877,9 +878,8 @@
@Override
public void onRecycle() {
- mFullscreenTranslationX = mFullscreenTranslationY = mNonFullscreenTranslationX =
- mNonFullscreenTranslationY = mGridTranslationX = mGridTranslationY =
- mBoxTranslationY = 0f;
+ mNonGridTranslationX = mNonGridTranslationY =
+ mGridTranslationX = mGridTranslationY = mBoxTranslationY = 0f;
resetViewTransforms();
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
@@ -962,13 +962,19 @@
}
}
- private void setFullscreenScale(float fullscreenScale) {
- mFullscreenScale = fullscreenScale;
+ private void setNonGridScale(float nonGridScale) {
+ mNonGridScale = nonGridScale;
+ updateCornerRadius();
applyScale();
}
- public float getFullscreenScale() {
- return mFullscreenScale;
+ public float getNonGridScale() {
+ return mNonGridScale;
+ }
+
+ private void setSnapshotScale(float dismissScale) {
+ mDismissScale = dismissScale;
+ applyScale();
}
/**
@@ -985,12 +991,23 @@
private void applyScale() {
float scale = 1;
- float fullScreenProgress = FULLSCREEN_INTERPOLATOR.getInterpolation(mFullscreenProgress);
- scale *= Utilities.mapRange(fullScreenProgress, 1f, mFullscreenScale);
+ scale *= getPersistentScale();
+ scale *= mDismissScale;
setScaleX(scale);
setScaleY(scale);
}
+ /**
+ * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not
+ * change according to a temporary state.
+ */
+ public float getPersistentScale() {
+ float scale = 1;
+ float gridProgress = GRID_INTERPOLATOR.getInterpolation(mGridProgress);
+ scale *= Utilities.mapRange(gridProgress, mNonGridScale, 1f);
+ return scale;
+ }
+
private void setSplitSelectTranslationX(float x) {
mSplitSelectTranslationX = x;
applyTranslationX();
@@ -1030,23 +1047,13 @@
applyTranslationY();
}
- private void setFullscreenTranslationX(float fullscreenTranslationX) {
- mFullscreenTranslationX = fullscreenTranslationX;
+ private void setNonGridTranslationX(float nonGridTranslationX) {
+ mNonGridTranslationX = nonGridTranslationX;
applyTranslationX();
}
- private void setFullscreenTranslationY(float fullscreenTranslationY) {
- mFullscreenTranslationY = fullscreenTranslationY;
- applyTranslationY();
- }
-
- private void setNonFullscreenTranslationX(float nonFullscreenTranslationX) {
- mNonFullscreenTranslationX = nonFullscreenTranslationX;
- applyTranslationX();
- }
-
- private void setNonFullscreenTranslationY(float nonFullscreenTranslationY) {
- mNonFullscreenTranslationY = nonFullscreenTranslationY;
+ private void setNonGridTranslationY(float nonGridTranslationY) {
+ mNonGridTranslationY = nonGridTranslationY;
applyTranslationY();
}
@@ -1070,13 +1077,10 @@
public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
float scrollAdjustment = 0;
- if (fullscreenEnabled) {
- scrollAdjustment += getPrimaryFullscreenTranslationProperty().get(this);
- } else {
- scrollAdjustment += getPrimaryNonFullscreenTranslationProperty().get(this);
- }
if (gridEnabled) {
scrollAdjustment += mGridTranslationX;
+ } else {
+ scrollAdjustment += getPrimaryNonGridTranslationProperty().get(this);
}
return scrollAdjustment;
}
@@ -1088,7 +1092,7 @@
public float getSizeAdjustment(boolean fullscreenEnabled) {
float sizeAdjustment = 1;
if (fullscreenEnabled) {
- sizeAdjustment *= mFullscreenScale;
+ sizeAdjustment *= mNonGridScale;
}
return sizeAdjustment;
}
@@ -1113,9 +1117,7 @@
* change according to a temporary state (e.g. task offset).
*/
public float getPersistentTranslationX() {
- return getFullscreenTrans(mFullscreenTranslationX)
- + getNonFullscreenTrans(mNonFullscreenTranslationX)
- + getGridTrans(mGridTranslationX);
+ return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX);
}
/**
@@ -1124,8 +1126,7 @@
*/
public float getPersistentTranslationY() {
return mBoxTranslationY
- + getFullscreenTrans(mFullscreenTranslationY)
- + getNonFullscreenTrans(mNonFullscreenTranslationY)
+ + getNonGridTrans(mNonGridTranslationY)
+ getGridTrans(mGridTranslationY);
}
@@ -1159,24 +1160,14 @@
TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
}
- public FloatProperty<TaskView> getPrimaryFullscreenTranslationProperty() {
+ public FloatProperty<TaskView> getPrimaryNonGridTranslationProperty() {
return getPagedOrientationHandler().getPrimaryValue(
- FULLSCREEN_TRANSLATION_X, FULLSCREEN_TRANSLATION_Y);
+ NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y);
}
- public FloatProperty<TaskView> getSecondaryFullscreenTranslationProperty() {
+ public FloatProperty<TaskView> getSecondaryNonGridTranslationProperty() {
return getPagedOrientationHandler().getSecondaryValue(
- FULLSCREEN_TRANSLATION_X, FULLSCREEN_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getPrimaryNonFullscreenTranslationProperty() {
- return getPagedOrientationHandler().getPrimaryValue(
- NON_FULLSCREEN_TRANSLATION_X, NON_FULLSCREEN_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getSecondaryNonFullscreenTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- NON_FULLSCREEN_TRANSLATION_X, NON_FULLSCREEN_TRANSLATION_Y);
+ NON_GRID_TRANSLATION_X, NON_GRID_TRANSLATION_Y);
}
@Override
@@ -1312,28 +1303,21 @@
progress = Utilities.boundToRange(progress, 0, 1);
mFullscreenProgress = progress;
mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
- getThumbnail().getTaskOverlay().setFullscreenProgress(progress);
+ mSnapshotView.getTaskOverlay().setFullscreenProgress(progress);
- applyTranslationX();
- applyTranslationY();
- applyScale();
+ updateCornerRadius();
- TaskThumbnailView thumbnail = getThumbnail();
- updateCurrentFullscreenParams(thumbnail.getPreviewPositionHelper());
-
- if (!getRecentsView().isTaskIconScaledDown(this)) {
- // Some of the items in here are dependent on the current fullscreen params, but don't
- // update them if the icon is supposed to be scaled down.
- setIconScaleAndDim(progress, true /* invert */);
- }
-
- thumbnail.setFullscreenParams(mCurrentFullscreenParams);
+ mSnapshotView.setFullscreenParams(mCurrentFullscreenParams);
mOutlineProvider.updateParams(
mCurrentFullscreenParams,
mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
invalidateOutline();
}
+ private void updateCornerRadius() {
+ updateCurrentFullscreenParams(mSnapshotView.getPreviewPositionHelper());
+ }
+
void updateCurrentFullscreenParams(PreviewPositionHelper previewPositionHelper) {
if (getRecentsView() == null) {
return;
@@ -1341,6 +1325,7 @@
mCurrentFullscreenParams.setProgress(
mFullscreenProgress,
getRecentsView().getScaleX(),
+ mNonGridScale,
getWidth(), mActivity.getDeviceProfile(),
previewPositionHelper);
}
@@ -1351,7 +1336,7 @@
*/
void updateTaskSize() {
ViewGroup.LayoutParams params = getLayoutParams();
- float fullscreenScale;
+ float nonGridScale;
float boxTranslationY;
int expectedWidth;
int expectedHeight;
@@ -1364,56 +1349,36 @@
int boxWidth;
int boxHeight;
- float thumbnailRatio;
boolean isFocusedTask = isFocusedTask();
if (isFocusedTask) {
// Task will be focused and should use focused task size. Use focusTaskRatio
// that is associated with the original orientation of the focused task.
boxWidth = taskWidth;
boxHeight = taskHeight;
- thumbnailRatio = getRecentsView().getFocusedTaskRatio();
} else {
// Otherwise task is in grid, and should use lastComputedGridTaskSize.
Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
boxWidth = lastComputedGridTaskSize.width();
boxHeight = lastComputedGridTaskSize.height();
- thumbnailRatio = mTask != null ? mTask.getVisibleThumbnailRatio(
- TaskView.CLIP_STATUS_AND_NAV_BARS) : 0f;
}
- int boxLength = Math.max(boxWidth, boxHeight);
// Bound width/height to the box size.
- if (thumbnailRatio == 0f) {
- expectedWidth = boxWidth;
- expectedHeight = boxHeight + thumbnailPadding;
- } else if (thumbnailRatio > 1) {
- expectedWidth = boxLength;
- expectedHeight = (int) (boxLength / thumbnailRatio) + thumbnailPadding;
- } else {
- expectedWidth = (int) (boxLength * thumbnailRatio);
- expectedHeight = boxLength + thumbnailPadding;
- }
+ expectedWidth = boxWidth;
+ expectedHeight = boxHeight + thumbnailPadding;
// Scale to to fit task Rect.
- fullscreenScale = taskWidth / (float) boxWidth;
-
- // In full screen, scale back TaskView to original size.
- if (expectedWidth > boxWidth) {
- fullscreenScale *= boxWidth / (float) expectedWidth;
- } else if (expectedHeight - thumbnailPadding > boxHeight) {
- fullscreenScale *= boxHeight / (float) (expectedHeight - thumbnailPadding);
- }
+ nonGridScale = taskWidth / (float) boxWidth;
// Align to top of task Rect.
boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
} else {
- fullscreenScale = 1f;
+ nonGridScale = 1f;
boxTranslationY = 0f;
expectedWidth = ViewGroup.LayoutParams.MATCH_PARENT;
expectedHeight = ViewGroup.LayoutParams.MATCH_PARENT;
}
- setFullscreenScale(fullscreenScale);
+ setNonGridScale(nonGridScale);
setBoxTranslationY(boxTranslationY);
if (params.width != expectedWidth || params.height != expectedHeight) {
params.width = expectedWidth;
@@ -1422,20 +1387,15 @@
}
}
- private float getFullscreenTrans(float endTranslation) {
- float progress = FULLSCREEN_INTERPOLATOR.getInterpolation(mFullscreenProgress);
- return Utilities.mapRange(progress, 0, endTranslation);
- }
-
- private float getNonFullscreenTrans(float endTranslation) {
- return endTranslation - getFullscreenTrans(endTranslation);
- }
-
private float getGridTrans(float endTranslation) {
- float progress = ACCEL_DEACCEL.getInterpolation(mGridProgress);
+ float progress = GRID_INTERPOLATOR.getInterpolation(mGridProgress);
return Utilities.mapRange(progress, 0, endTranslation);
}
+ private float getNonGridTrans(float endTranslation) {
+ return endTranslation - getGridTrans(endTranslation);
+ }
+
public boolean isRunningTask() {
if (getRecentsView() == null) {
return false;
@@ -1487,7 +1447,6 @@
private final float mCornerRadius;
private final float mWindowCornerRadius;
- public float mFullscreenProgress;
public RectF mCurrentDrawnInsets = new RectF();
public float mCurrentDrawnCornerRadius;
/** The current scale we apply to the thumbnail to adjust for new left/right insets. */
@@ -1503,9 +1462,8 @@
/**
* Sets the progress in range [0, 1]
*/
- public void setProgress(float fullscreenProgress, float parentScale, int previewWidth,
- DeviceProfile dp, PreviewPositionHelper pph) {
- mFullscreenProgress = fullscreenProgress;
+ public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
+ int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
RectF insets = pph.getInsetsToDrawInFullscreen();
float currentInsetsLeft = insets.left * fullscreenProgress;
@@ -1516,7 +1474,7 @@
mCurrentDrawnCornerRadius =
Utilities.mapRange(fullscreenProgress, mCornerRadius, fullscreenCornerRadius)
- / parentScale;
+ / parentScale / taskViewScale;
// We scaled the thumbnail to fit the content (excluding insets) within task view width.
// Now that we are drawing left/right insets again, we need to scale down to fit them.
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 3e84a76..fef9304 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -16,6 +16,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.Launcher;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.quickstep.views.DigitalWellBeingToast;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -32,6 +33,7 @@
resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
@Test
+ @ScreenRecord //b/193440212
public void testToast() throws Exception {
startAppFast(CALCULATOR_PACKAGE);
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index a683d01..20b4715 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -59,6 +59,7 @@
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.FailureWatcher;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.quickstep.views.RecentsView;
import org.junit.After;
@@ -213,6 +214,7 @@
// b/143488140
//@NavigationModeSwitch
@Test
+ @ScreenRecord // b/194484556
public void testOverview() {
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java b/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
new file mode 100644
index 0000000..af5819a
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/NavigationBarRotationContextTest.java
@@ -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 */);
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 6e19436..f44a812 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -25,6 +25,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.Launcher;
+import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.util.RaceConditionReproducer;
import com.android.quickstep.NavigationModeSwitchRule.Mode;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -45,6 +46,7 @@
@Before
public void setUp() throws Exception {
super.setUp();
+ TaplTestsLauncher3.initialize(this);
// b/143488140
mLauncher.pressHome();
// Start an activity where the gestures start.
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index a5038a1..ea69b94 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertTrue;
import android.content.Intent;
-import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
@@ -37,8 +36,8 @@
import com.android.launcher3.tapl.Overview;
import com.android.launcher3.tapl.OverviewActions;
import com.android.launcher3.tapl.OverviewTask;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.views.RecentsView;
@@ -94,6 +93,7 @@
@Test
@PortraitLandscape
+ @ScreenRecord //b/193440212
public void testOverview() throws Exception {
startTestAppsWithCheck();
// mLauncher.pressHome() also tests an important case of pressing home while in background.
@@ -145,12 +145,8 @@
launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
numTasks - 1, getTaskCount(launcher)));
- // Test UIDevice.pressHome, once we are in AllApps.
- mDevice.pressHome();
- waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
-
// Test dismissing all tasks.
- mLauncher.getWorkspace().switchToOverview().dismissAllTasks();
+ mLauncher.pressHome().switchToOverview().dismissAllTasks();
waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
executeOnLauncher(
launcher -> assertEquals("Still have tasks after dismissing all",
diff --git a/res/color-v31/home_settings_switch_thumb_color.xml b/res/color-v31/home_settings_switch_thumb_color.xml
new file mode 100644
index 0000000..91d3d9b
--- /dev/null
+++ b/res/color-v31/home_settings_switch_thumb_color.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/home_settings_thumb_off_color" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/home_settings_thumb_off_color" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/home_settings_state_on_color" />
+</selector>
diff --git a/res/color-v31/home_settings_switch_track_color.xml b/res/color-v31/home_settings_switch_track_color.xml
new file mode 100644
index 0000000..50784f5
--- /dev/null
+++ b/res/color-v31/home_settings_switch_track_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/home_settings_track_off_color"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/home_settings_track_off_color" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/home_settings_track_on_color" />
+</selector>
diff --git a/res/drawable-v31/home_settings_switch_thumb.xml b/res/drawable-v31/home_settings_switch_thumb.xml
new file mode 100644
index 0000000..260d5ea
--- /dev/null
+++ b/res/drawable-v31/home_settings_switch_thumb.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="4dp"
+ android:left="4dp"
+ android:right="4dp"
+ android:bottom="4dp">
+ <shape android:shape="oval" >
+ <size android:height="20dp" android:width="20dp" />
+ <solid android:color="@color/home_settings_switch_thumb_color" />
+ </shape>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable-v31/home_settings_switch_track.xml b/res/drawable-v31/home_settings_switch_track.xml
new file mode 100644
index 0000000..502a300
--- /dev/null
+++ b/res/drawable-v31/home_settings_switch_track.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="52dp"
+ android:height="28dp">
+
+ <solid android:color="@color/home_settings_switch_track_color" />
+ <corners android:radius="35dp" />
+</shape>
\ No newline at end of file
diff --git a/res/layout-v31/settings_activity.xml b/res/layout-v31/settings_activity.xml
new file mode 100644
index 0000000..59e14f2
--- /dev/null
+++ b/res/layout-v31/settings_activity.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 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.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/content_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/colorPrimary"
+ android:fitsSystemWindows="true"
+ android:outlineAmbientShadowColor="@android:color/transparent"
+ android:outlineSpotShadowColor="@android:color/transparent"
+ android:theme="@style/HomeSettings.CollapsingToolbar">
+
+ <com.google.android.material.appbar.CollapsingToolbarLayout
+ android:id="@+id/collapsing_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="226dp"
+ android:clipToPadding="false"
+ app:collapsedTitleTextAppearance="@style/HomeSettings.CollapsedToolbarTitle"
+ app:contentScrim="@color/home_settings_header_collapsed"
+ app:expandedTitleMarginEnd="24dp"
+ app:expandedTitleMarginStart="24dp"
+ app:expandedTitleTextAppearance="@style/HomeSettings.ExpandedToolbarTitle"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
+ app:maxLines="3"
+ app:scrimAnimationDuration="50"
+ app:scrimVisibleHeightTrigger="174dp"
+ app:statusBarScrim="@null"
+ app:titleCollapseMode="fade"
+ app:toolbarId="@id/action_bar">
+
+ <Toolbar
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?android:attr/actionBarTheme"
+ android:transitionName="shared_element_view"
+ app:layout_collapseMode="pin" />
+
+ </com.google.android.material.appbar.CollapsingToolbarLayout>
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index 0e06690..c57b75a 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -64,12 +64,19 @@
android:textColor="?android:attr/textColorSecondary"
android:alpha="0.7"/>
- <include layout="@layout/widget_cell"
- android:id="@+id/widget_cell"
+ <ScrollView
+ android:id="@+id/widget_preview_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_marginVertical="16dp" />
+ android:layout_marginVertical="16dp"
+ android:layout_weight="1">
+
+ <include
+ android:id="@+id/widget_cell"
+ layout="@layout/widget_cell"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </ScrollView>
<LinearLayout
android:layout_width="match_parent"
@@ -80,9 +87,11 @@
<Button
style="@style/Button.FullRounded.Colored"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:textSize="14sp"
+ android:maxLines="2"
+ android:ellipsize="end"
android:textColor="@color/button_text"
android:text="@android:string/cancel"
android:onClick="onCancelClick"/>
@@ -94,9 +103,11 @@
<Button
style="@style/Button.FullRounded.Colored"
android:layout_width="wrap_content"
- android:layout_height="36dp"
+ android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:textSize="14sp"
+ android:maxLines="2"
+ android:ellipsize="end"
android:textColor="@color/button_text"
android:text="@string/add_to_home_screen"
android:onClick="onPlaceAutomaticallyClick"/>
diff --git a/res/layout/floating_split_select_view.xml b/res/layout/floating_split_select_view.xml
new file mode 100644
index 0000000..e184b91
--- /dev/null
+++ b/res/layout/floating_split_select_view.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.quickstep.views.FloatingTaskView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/thumbnail"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <com.android.quickstep.views.SplitPlaceholderView
+ android:id="@+id/split_placeholder"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/split_placeholder_size"
+ android:background="@android:color/white"
+ android:visibility="gone" />
+
+</com.android.quickstep.views.FloatingTaskView>
\ No newline at end of file
diff --git a/res/layout/launcher_preview_two_panel_layout.xml b/res/layout/launcher_preview_two_panel_layout.xml
new file mode 100644
index 0000000..7b227e0
--- /dev/null
+++ b/res/layout/launcher_preview_two_panel_layout.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<view class="com.android.launcher3.graphics.LauncherPreviewRenderer$LauncherPreviewLayout"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:launcher="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="false">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.android.launcher3.CellLayout
+ android:id="@+id/workspace_left"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:theme="@style/HomeScreenElementTheme"
+ launcher:containerType="workspace"
+ launcher:layout_constraintStart_toStartOf="parent"
+ launcher:layout_constraintTop_toTopOf="parent"
+ launcher:layout_constraintEnd_toStartOf="@id/workspace"
+ launcher:layout_constraintBottom_toBottomOf="parent"
+ launcher:pageIndicator="@+id/page_indicator" />
+
+ <com.android.launcher3.CellLayout
+ android:id="@+id/workspace"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:theme="@style/HomeScreenElementTheme"
+ launcher:containerType="workspace"
+ launcher:layout_constraintStart_toEndOf="@id/workspace_left"
+ launcher:layout_constraintTop_toTopOf="parent"
+ launcher:layout_constraintEnd_toEndOf="parent"
+ launcher:layout_constraintBottom_toBottomOf="parent"
+ launcher:pageIndicator="@+id/page_indicator" />
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <include
+ android:id="@+id/hotseat"
+ layout="@layout/hotseat" />
+
+</view>
\ No newline at end of file
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
new file mode 100644
index 0000000..2c1bc90
--- /dev/null
+++ b/res/values-night-v31/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+* Copyright (C) 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.
+*/
+-->
+
+<resources>
+ <color name="home_settings_header_accent">@android:color/system_accent1_100</color>
+ <color name="home_settings_header_collapsed">@android:color/system_neutral1_700</color>
+ <color name="home_settings_header_expanded">@android:color/system_neutral1_900</color>
+
+ <color name="home_settings_thumb_off_color">@android:color/system_neutral2_300</color>
+ <color name="home_settings_track_on_color">@android:color/system_accent2_700</color>
+ <color name="home_settings_track_off_color">@android:color/system_neutral1_700</color>
+</resources>
\ No newline at end of file
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index d73ee57..c2ebeff 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -42,6 +42,16 @@
<color name="folder_dot_color">@android:color/system_accent2_50</color>
+ <color name="home_settings_header_accent">@android:color/system_accent1_600</color>
+ <color name="home_settings_header_collapsed">@android:color/system_neutral1_100</color>
+ <color name="home_settings_header_expanded">@android:color/system_neutral1_50</color>
+
+ <color name="home_settings_state_on_color">@android:color/system_accent1_100</color>
+ <color name="home_settings_state_off_color">@android:color/system_accent2_100</color>
+ <color name="home_settings_thumb_off_color">@android:color/system_neutral2_100</color>
+ <color name="home_settings_track_on_color">@android:color/system_accent1_600</color>
+ <color name="home_settings_track_off_color">@android:color/system_neutral2_600</color>
+
<color name="workspace_accent_color_light">@android:color/system_accent1_100</color>
<color name="workspace_accent_color_dark">@android:color/system_accent2_600</color>
</resources>
diff --git a/res/values-v31/config.xml b/res/values-v31/config.xml
new file mode 100644
index 0000000..afb9e6d
--- /dev/null
+++ b/res/values-v31/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 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.
+ -->
+<resources>
+ <bool name="home_settings_icon_space_reserved">false</bool>
+ <bool name="home_settings_allow_divider">false</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values-v31/styles.xml b/res/values-v31/styles.xml
new file mode 100644
index 0000000..0d2fce0
--- /dev/null
+++ b/res/values-v31/styles.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 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.
+*/
+-->
+
+<resources>
+
+ <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
+ <item name="android:listPreferredItemPaddingEnd">16dp</item>
+ <item name="android:listPreferredItemPaddingStart">24dp</item>
+ <item name="android:navigationBarColor">@android:color/transparent</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:switchStyle">@style/HomeSettings.SwitchStyle</item>
+ <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
+ </style>
+
+ <style name="HomeSettings.PreferenceTheme" parent="@style/PreferenceThemeOverlay">
+ <item name="preferenceCategoryStyle">@style/HomeSettings.CategoryStyle</item>
+ <item name="preferenceCategoryTitleTextAppearance">@style/HomeSettings.CategoryTitle</item>
+ <item name="preferenceFragmentCompatStyle">@style/HomeSettings.FragmentCompatStyle</item>
+ <item name="preferenceScreenStyle">@style/HomeSettings.PreferenceScreenStyle</item>
+ <item name="preferenceStyle">@style/HomeSettings.PreferenceStyle</item>
+ <item name="switchPreferenceStyle">@style/HomeSettings.SwitchPreferenceStyle</item>
+ </style>
+
+ <style name="HomeSettings.CategoryStyle" parent="@style/Preference.Category.Material">
+ <item name="allowDividerAbove">@bool/home_settings_allow_divider</item>
+ <item name="allowDividerBelow">@bool/home_settings_allow_divider</item>
+ <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+ </style>
+
+ <style name="HomeSettings.PreferenceStyle" parent="@style/Preference.Material">
+ <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+ </style>
+
+ <style name="HomeSettings.PreferenceScreenStyle"
+ parent="@style/Preference.PreferenceScreen.Material">
+ <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+ </style>
+
+ <style name="HomeSettings.SwitchPreferenceStyle"
+ parent="@style/Preference.SwitchPreference.Material">
+ <item name="iconSpaceReserved">@bool/home_settings_icon_space_reserved</item>
+ </style>
+
+ <style name="HomeSettings.SwitchStyle"
+ parent="@android:style/Widget.Material.CompoundButton.Switch">
+ <item name="android:switchMinWidth">52dp</item>
+ <item name="android:thumb">@drawable/home_settings_switch_thumb</item>
+ <item name="android:track">@drawable/home_settings_switch_track</item>
+ </style>
+
+ <style name="HomeSettings.PreferenceTitle"
+ parent="@android:style/TextAppearance.Material.Subhead">
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:textSize">20sp</item>
+ </style>
+
+ <style name="HomeSettings.CategoryTitle" parent="@android:style/TextAppearance.Material.Body2">
+ <item name="android:fontFamily">google-sans-text-medium</item>
+ </style>
+
+ <style name="HomeSettings.CollapsingToolbar" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="colorAccent">@color/home_settings_header_accent</item>
+ <item name="colorPrimary">@color/home_settings_header_expanded</item>
+ <item name="elevationOverlayColor">?attr/colorPrimary</item>
+ <item name="elevationOverlayEnabled">true</item>
+ </style>
+
+ <style name="HomeSettings.CollapsedToolbarTitle"
+ parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:fontFamily">google-sans</item>
+ </style>
+
+ <style name="HomeSettings.ExpandedToolbarTitle" parent="HomeSettings.CollapsedToolbarTitle">
+ <item name="android:textSize">36sp</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f434644..dd27685 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -312,6 +312,10 @@
<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="taskbar_size">0dp</dimen>
+ <dimen name="qsb_widget_height">0dp</dimen>
+ <dimen name="taskbar_icon_size">44dp</dimen>
+ <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
+ <dimen name="taskbar_icon_spacing">8dp</dimen>
<!-- Size of the maximum radius for the enforced rounded rectangles. -->
<dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
@@ -322,6 +326,7 @@
<dimen name="overview_task_margin">0dp</dimen>
<dimen name="overview_actions_bottom_margin_gesture">0dp</dimen>
<dimen name="overview_actions_bottom_margin_three_button">0dp</dimen>
+ <dimen name="split_placeholder_size">110dp</dimen>
<!-- Workspace grid visualization parameters -->
<dimen name="grid_visualization_rounding_radius">22dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 1709c59..0e2dff0 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -20,4 +20,13 @@
<item type="id" name="view_type_widgets_list" />
<item type="id" name="view_type_widgets_header" />
<item type="id" name="view_type_widgets_search_header" />
+
+ <!-- Do not change, must be kept in sync with sysui navbar button IDs for tests! -->
+ <item type="id" name="home" />
+ <item type="id" name="recent_apps" />
+ <item type="id" name="back" />
+ <item type="id" name="ime_switcher" />
+ <item type="id" name="accessibility_button" />
+ <item type="id" name="rotate_suggestion" />
+ <!-- /Do not change, must be kept in sync with sysui navbar button IDs for tests! -->
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index d30b80c..e4a245a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -147,18 +147,18 @@
<style name="AppTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark.DarkMainColor" />
<style name="AppTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark.DarkText" />
- <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
+ <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:navigationBarColor">?android:colorPrimaryDark</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
- <item name="preferenceTheme">@style/HomeSettingsPreferenceTheme</item>
+ <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
</style>
- <style name="HomeSettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
- <item name="preferenceFragmentCompatStyle">@style/HomeSettingsFragmentCompatStyle</item>
+ <style name="HomeSettings.PreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
+ <item name="preferenceFragmentCompatStyle">@style/HomeSettings.FragmentCompatStyle</item>
</style>
- <style name="HomeSettingsFragmentCompatStyle" parent="@style/PreferenceFragment.Material">
+ <style name="HomeSettings.FragmentCompatStyle" parent="@style/PreferenceFragment.Material">
<item name="android:layout">@layout/home_settings</item>
</style>
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
index 8e49fae..d2051e0 100644
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
@@ -131,7 +131,7 @@
mIdp.numDatabaseHotseatIcons);
GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
- task.migrate();
+ task.migrate(mIdp);
// Check hotseat items
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
@@ -211,7 +211,7 @@
mIdp.numDatabaseHotseatIcons);
GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
- task.migrate();
+ task.migrate(mIdp);
// Check hotseat items
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
@@ -259,7 +259,7 @@
mIdp.numDatabaseHotseatIcons);
GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
- task.migrate();
+ task.migrate(mIdp);
// Check hotseat items
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index a2abfd5..4319355 100644
--- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -26,16 +26,17 @@
import android.os.Process;
-import com.android.launcher3.PagedView;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.shadows.ShadowLooperExecutor;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.LooperExecutor;
-import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.util.RunnableList;
import org.junit.Before;
import org.junit.Test;
@@ -92,7 +93,7 @@
// Add a new callback
cb1.reset();
MyCallbacks cb2 = spy(MyCallbacks.class);
- cb2.mPageToBindSync = 2;
+ cb2.mPageToBindSync = IntSet.wrap(2);
mModelHelper.getModel().addCallbacksAndLoad(cb2);
waitForLoaderAndTempMainThread();
@@ -106,14 +107,14 @@
// No effect on callbacks when removing an callback
mModelHelper.getModel().removeCallbacks(cb2);
waitForLoaderAndTempMainThread();
- assertNull(cb1.mDeferredExecutor);
- assertNull(cb2.mDeferredExecutor);
+ assertNull(cb1.mPendingTasks);
+ assertNull(cb2.mPendingTasks);
// Reloading only loads registered callbacks
mModelHelper.getModel().startLoader();
waitForLoaderAndTempMainThread();
cb1.verifySynchronouslyBound(3);
- assertNull(cb2.mDeferredExecutor);
+ assertNull(cb2.mPendingTasks);
}
@Test
@@ -178,21 +179,17 @@
private abstract static class MyCallbacks implements Callbacks {
final List<ItemInfo> mItems = new ArrayList<>();
- int mPageToBindSync = 0;
- int mPageBoundSync = PagedView.INVALID_PAGE;
- ViewOnDrawExecutor mDeferredExecutor;
+ IntSet mPageToBindSync = IntSet.wrap(0);
+ IntSet mPageBoundSync = new IntSet();
+ RunnableList mPendingTasks;
AppInfo[] mAppInfos;
MyCallbacks() { }
@Override
- public void onPageBoundSynchronously(int page) {
- mPageBoundSync = page;
- }
-
- @Override
- public void executeOnNextDraw(ViewOnDrawExecutor executor) {
- mDeferredExecutor = executor;
+ public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ mPageBoundSync = boundPages;
+ mPendingTasks = pendingTasks;
}
@Override
@@ -206,26 +203,26 @@
}
@Override
- public int getPageToBindSynchronously() {
+ public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
return mPageToBindSync;
}
public void reset() {
mItems.clear();
- mPageBoundSync = PagedView.INVALID_PAGE;
- mDeferredExecutor = null;
+ mPageBoundSync = new IntSet();
+ mPendingTasks = null;
mAppInfos = null;
}
public void verifySynchronouslyBound(int totalItems) {
// Verify that the requested page is bound synchronously
- assertEquals(mPageBoundSync, mPageToBindSync);
+ assertEquals(mPageToBindSync, mPageBoundSync);
assertEquals(mItems.size(), 1);
- assertEquals(mItems.get(0).screenId, mPageBoundSync);
- assertNotNull(mDeferredExecutor);
+ assertEquals(IntSet.wrap(mItems.get(0).screenId), mPageBoundSync);
+ assertNotNull(mPendingTasks);
// Verify that all other pages are bound properly
- mDeferredExecutor.runAllTasks();
+ mPendingTasks.executeAllAndDestroy();
assertEquals(mItems.size(), totalItems);
}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
index fdddab4..caad40e 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherUIHelper.java
@@ -81,7 +81,7 @@
doLayout(launcher);
ViewOnDrawExecutor executor = ReflectionHelpers.getField(launcher, "mPendingExecutor");
if (executor != null) {
- executor.runAllTasks();
+ executor.markCompleted();
}
return launcher;
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ddbd425..f800cf6 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -407,7 +407,7 @@
}
}
- void clearPressedBackground() {
+ public void clearPressedBackground() {
setPressed(false);
setStayPressed(false);
}
@@ -853,8 +853,9 @@
switch (display) {
case DISPLAY_ALL_APPS:
return grid.allAppsIconSizePx;
- case DISPLAY_WORKSPACE:
case DISPLAY_FOLDER:
+ return grid.folderChildIconSizePx;
+ case DISPLAY_WORKSPACE:
default:
return grid.iconSizePx;
}
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 61b5564..0d33b6f 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -211,11 +211,8 @@
}
final DragLayer dragLayer = mLauncher.getDragLayer();
final DragView dragView = d.dragView;
- final Rect from = new Rect();
- dragLayer.getViewRectRelativeToSelf(d.dragView, from);
-
final Rect to = getIconRect(d);
- final float scale = (float) to.width() / from.width();
+ final float scale = (float) to.width() / dragView.getMeasuredWidth();
dragView.disableColorExtraction();
dragView.detachContentView(/* reattachToPreviousParent= */ true);
mDropTargetBar.deferOnDragEnd();
@@ -229,9 +226,9 @@
post(dragView::resumeColorExtraction);
};
- dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
+ dragLayer.animateView(d.dragView, to, scale, 0.1f, 0.1f,
DRAG_VIEW_DROP_DURATION,
- Interpolators.DEACCEL_2, Interpolators.LINEAR, onAnimationEndRunnable,
+ Interpolators.DEACCEL_2, onAnimationEndRunnable,
DragLayer.ANIMATION_END_DISAPPEAR, null);
}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index a6adfc4..af6cce1 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -2148,7 +2148,7 @@
mShakeAnimators.clear();
}
- private void commitTempPlacement() {
+ private void commitTempPlacement(View dragView) {
mTmpOccupied.copyTo(mOccupied);
int screenId = Launcher.cast(mActivity).getWorkspace().getIdForScreen(this);
@@ -2166,7 +2166,7 @@
ItemInfo info = (ItemInfo) child.getTag();
// We do a null check here because the item info can be null in the case of the
// AllApps button in the hotseat.
- if (info != null) {
+ if (info != null && child != dragView) {
final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
|| info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
|| info.spanY != lp.cellVSpan);
@@ -2328,7 +2328,7 @@
animateItemsToSolution(swapSolution, dragView, commit);
if (commit) {
- commitTempPlacement();
+ commitTempPlacement(null);
completeAndClearReorderPreviewAnimations();
setItemPlacementDirty(false);
} else {
@@ -2422,7 +2422,8 @@
if (!DESTRUCTIVE_REORDER &&
(mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
- commitTempPlacement();
+ // Since the temp solution didn't update dragView, don't commit it either
+ commitTempPlacement(dragView);
completeAndClearReorderPreviewAnimations();
setItemPlacementDirty(false);
} else {
@@ -2878,7 +2879,7 @@
directionVector, null, false, configuration).isSolution) {
if (commitConfig) {
copySolutionToTempState(configuration, null);
- commitTempPlacement();
+ commitTempPlacement(null);
// undo marking cells occupied since there is actually nothing being placed yet.
mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
}
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 80ec192..477964a 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -25,6 +25,7 @@
import android.util.AttributeSet;
import android.view.View;
+import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.StatsLogManager;
@@ -33,6 +34,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.views.Snackbar;
public class DeleteDropTarget extends ButtonDropTarget {
@@ -127,11 +129,21 @@
public void completeDrop(DragObject d) {
ItemInfo item = d.dragInfo;
if (canRemove(item)) {
- int itemPage = mLauncher.getWorkspace().getCurrentPage();
+ ItemInfo pageItem = item;
+ if (item.container <= 0) {
+ View v = mLauncher.getWorkspace().getHomescreenIconByItemId(item.container);
+ if (v != null) {
+ pageItem = (ItemInfo) v.getTag();
+ }
+ }
+ IntSet pageIds = pageItem.container == Favorites.CONTAINER_DESKTOP
+ ? IntSet.wrap(pageItem.screenId)
+ : mLauncher.getWorkspace().getCurrentPageScreenIds();
+
onAccessibilityDrop(null, item);
ModelWriter modelWriter = mLauncher.getModelWriter();
Runnable onUndoClicked = () -> {
- mLauncher.setPageToBindSynchronously(itemPage);
+ mLauncher.setPagesToBindSynchronously(pageIds);
modelWriter.abortDelete();
mLauncher.getStatsLogManager().logger().log(LAUNCHER_UNDO);
};
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9ca753d..78c0ded 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -45,6 +45,7 @@
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.WindowBounds;
@@ -55,6 +56,8 @@
public class DeviceProfile {
private static final int DEFAULT_DOT_SIZE = 100;
+ // Ratio of empty space, qsb should take up to appear visually centered.
+ private static final float QSB_CENTER_FACTOR = .325f;
public final InvariantDeviceProfile inv;
private final Info mInfo;
@@ -161,6 +164,7 @@
// Start is the side next to the nav bar, end is the side next to the workspace
public final int hotseatBarSidePaddingStartPx;
public final int hotseatBarSidePaddingEndPx;
+ public final int hotseatQsbHeight;
public final float qsbBottomMarginOriginalPx;
public int qsbBottomMarginPx;
@@ -248,6 +252,7 @@
mMetrics = context.getResources().getDisplayMetrics();
final Resources res = context.getResources();
+ hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
isTaskbarPresent = isTablet && FeatureFlags.ENABLE_TASKBAR.get();
if (isTaskbarPresent) {
// Taskbar will be added later, but provides bottom insets that we should subtract
@@ -779,7 +784,10 @@
}
}
- public Rect getHotseatLayoutPadding() {
+ /**
+ * Returns the padding for hotseat view
+ */
+ public Rect getHotseatLayoutPadding(Context context) {
if (isVerticalBarLayout()) {
if (isSeascape()) {
mHotseatPadding.set(mInsets.left + hotseatBarSidePaddingStartPx,
@@ -788,6 +796,30 @@
mHotseatPadding.set(hotseatBarSidePaddingEndPx, mInsets.top,
mInsets.right + hotseatBarSidePaddingStartPx, mInsets.bottom);
}
+ } else if (isTaskbarPresent) {
+ int hotseatHeight = workspacePadding.bottom + taskbarSize;
+ int taskbarOffset = getTaskbarOffsetY();
+ int hotseatTopDiff = hotseatHeight - taskbarSize - taskbarOffset;
+
+ int endOffset = ApiWrapper.getHotseatEndOffset(context);
+ int requiredWidth = iconSizePx * numShownHotseatIcons;
+
+ Resources res = context.getResources();
+ float taskbarIconSize = res.getDimension(R.dimen.taskbar_icon_size);
+ float taskbarIconSpacing = 2 * res.getDimension(R.dimen.taskbar_icon_spacing);
+ int maxSize = (int) (requiredWidth
+ * (taskbarIconSize + taskbarIconSpacing) / taskbarIconSize);
+ int hotseatSize = Math.min(maxSize, availableWidthPx - endOffset);
+ int sideSpacing = (availableWidthPx - hotseatSize) / 2;
+ mHotseatPadding.set(sideSpacing, hotseatTopDiff, sideSpacing, taskbarOffset);
+
+ if (endOffset > sideSpacing) {
+ int diff = Utilities.isRtl(context.getResources())
+ ? sideSpacing - endOffset
+ : endOffset - sideSpacing;
+ mHotseatPadding.left -= diff;
+ mHotseatPadding.right += diff;
+ }
} else {
// We want the edges of the hotseat to line up with the edges of the workspace, but the
// icons in the hotseat are a different size, and so don't line up perfectly. To account
@@ -809,6 +841,29 @@
}
/**
+ * Returns the number of pixels the QSB is translated from the bottom of the screen.
+ */
+ public int getQsbOffsetY() {
+ int freeSpace = isTaskbarPresent
+ ? workspacePadding.bottom
+ : hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight;
+
+ if (isScalableGrid) {
+ return Math.min(qsbBottomMarginPx, freeSpace);
+ } else {
+ return (int) (freeSpace * QSB_CENTER_FACTOR)
+ + (isTaskbarPresent ? taskbarSize : getInsets().bottom);
+ }
+ }
+
+ /**
+ * Returns the number of pixels the taskbar is translated from the bottom of the screen.
+ */
+ public int getTaskbarOffsetY() {
+ return (getQsbOffsetY() - taskbarSize) / 2;
+ }
+
+ /**
* @return the bounds for which the open folders should be contained within
*/
public Rect getAbsoluteOpenFolderBounds() {
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 0a6f06c..b3ae15e 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -49,8 +49,6 @@
private final View mQsb;
private final int mQsbHeight;
- private final int mTaskbarViewHeight;
-
public Hotseat(Context context) {
this(context, null);
}
@@ -63,10 +61,9 @@
super(context, attrs, defStyle);
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
- mQsbHeight = mQsb.getLayoutParams().height;
addView(mQsb);
- mTaskbarViewHeight = context.getResources().getDimensionPixelSize(R.dimen.taskbar_size);
+ mQsbHeight = getResources().getDimensionPixelSize(R.dimen.qsb_widget_height);
}
/**
@@ -114,18 +111,13 @@
lp.gravity = Gravity.BOTTOM;
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = (grid.isTaskbarPresent
- ? grid.workspacePadding.bottom
+ ? grid.workspacePadding.bottom
: grid.hotseatBarSizePx)
+ (grid.isTaskbarPresent ? grid.taskbarSize : insets.bottom);
}
- if (!grid.isTaskbarPresent) {
- // When taskbar is present, we set the padding separately to ensure a seamless visual
- // handoff between taskbar and hotseat during drag and drop.
- Rect padding = grid.getHotseatLayoutPadding();
- setPadding(padding.left, padding.top, padding.right, padding.bottom);
- }
-
+ Rect padding = grid.getHotseatLayoutPadding(getContext());
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
setLayoutParams(lp);
InsettableFrameLayout.dispatchInsets(this, insets);
}
@@ -193,37 +185,12 @@
int left = (r - l - qsbWidth) / 2;
int right = left + qsbWidth;
- int bottom = b - t - getQsbOffsetY();
+ int bottom = b - t - mActivity.getDeviceProfile().getQsbOffsetY();
int top = bottom - mQsbHeight;
mQsb.layout(left, top, right, bottom);
}
/**
- * Returns the number of pixels the QSB is translated from the bottom of the screen.
- */
- private int getQsbOffsetY() {
- DeviceProfile dp = mActivity.getDeviceProfile();
- int freeSpace = dp.isTaskbarPresent
- ? dp.workspacePadding.bottom
- : dp.hotseatBarSizePx - dp.hotseatCellHeightPx - mQsbHeight;
-
- if (dp.isScalableGrid) {
- return Math.min(dp.qsbBottomMarginPx, freeSpace);
- } else {
- return (int) (freeSpace * QSB_CENTER_FACTOR) + (dp.isTaskbarPresent
- ? dp.taskbarSize
- : dp.getInsets().bottom);
- }
- }
-
- /**
- * Returns the number of pixels the taskbar is translated from the bottom of the screen.
- */
- public int getTaskbarOffsetY() {
- return (getQsbOffsetY() - mTaskbarViewHeight) / 2;
- }
-
- /**
* Sets the alpha value of just our ShortcutAndWidgetContainer.
*/
public void setIconsAlpha(float alpha) {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 29a0223..a2bd201 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -36,7 +36,6 @@
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
@@ -45,7 +44,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.IntArray;
@@ -253,17 +251,10 @@
private String initGrid(Context context, String gridName) {
Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
- // Determine if we have split display
-
- boolean isTablet = false, isPhone = false;
- for (WindowBounds bounds : displayInfo.supportedBounds) {
- if (displayInfo.isTablet(bounds)) {
- isTablet = true;
- } else {
- isPhone = true;
- }
- }
- boolean isSplitDisplay = isPhone && isTablet && ENABLE_TWO_PANEL_HOME.get();
+ // Each screen has two profiles (portrait/landscape), so devices with four or more
+ // supported profiles implies two or more internal displays.
+ boolean isSplitDisplay =
+ displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get();
ArrayList<DisplayOption> allOptions =
getPredefinedDeviceProfiles(context, gridName, isSplitDisplay);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 099f256..e736022 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -36,6 +36,7 @@
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -48,7 +49,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RECONFIGURED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
-import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -106,6 +106,7 @@
import android.widget.Toast;
import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
@@ -117,6 +118,7 @@
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -166,6 +168,7 @@
import com.android.launcher3.util.ActivityTracker;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
@@ -173,6 +176,7 @@
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
@@ -247,8 +251,6 @@
protected static final int REQUEST_LAST = 100;
// Type: int
- private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
- // Type: int
private static final String RUNTIME_STATE = "launcher.state";
// Type: PendingRequestArgs
private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
@@ -258,6 +260,8 @@
private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
// Type: SparseArray<Parcelable>
private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
+ // Type int[]
+ private static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
public static final String ON_CREATE_EVT = "Launcher.onCreate";
public static final String ON_START_EVT = "Launcher.onStart";
@@ -321,8 +325,8 @@
private PopupDataProvider mPopupDataProvider;
- private int mSynchronouslyBoundPage = PagedView.INVALID_PAGE;
- private int mPageToBindSynchronously = PagedView.INVALID_PAGE;
+ private IntSet mSynchronouslyBoundPages = new IntSet();
+ @NonNull private IntSet mPagesToBindSynchronously = new IntSet();
// We only want to get the SharedPreferences once since it does an FS stat each time we get
// it from the context.
@@ -457,13 +461,12 @@
restoreState(savedInstanceState);
mStateManager.reapplyState();
- // We only load the page synchronously if the user rotates (or triggers a
- // configuration change) while launcher is in the foreground
- int currentScreen = PagedView.INVALID_PAGE;
if (savedInstanceState != null) {
- currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
+ int[] pageIds = savedInstanceState.getIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS);
+ if (pageIds != null) {
+ mPagesToBindSynchronously = IntSet.wrap(pageIds);
+ }
}
- mPageToBindSynchronously = currentScreen;
if (!mModel.addCallbacksAndLoad(this)) {
if (!internalStateHandled) {
@@ -595,7 +598,7 @@
}
onDeviceProfileInitiated();
- mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true);
+ mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true, this);
}
public RotationHelper getRotationHelper() {
@@ -1308,7 +1311,8 @@
}
}
- public FolderIcon findFolderIcon(final int folderIconId) {
+ @Override
+ public @Nullable FolderIcon findFolderIcon(final int folderIconId) {
return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId);
}
@@ -1583,18 +1587,22 @@
@Override
public void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
- mWorkspace.restoreInstanceStateForChild(mSynchronouslyBoundPage);
+ if (mSynchronouslyBoundPages != null) {
+ mSynchronouslyBoundPages.forEach(screenId -> {
+ int pageIndex = mWorkspace.getPageIndexForScreenId(screenId);
+ if (pageIndex != PagedView.INVALID_PAGE) {
+ mWorkspace.restoreInstanceStateForChild(pageIndex);
+ }
+ });
+ }
}
@Override
protected void onSaveInstanceState(Bundle outState) {
- if (mWorkspace.getChildCount() > 0) {
- outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getNextPage());
-
- }
+ outState.putIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS,
+ mWorkspace.getCurrentPageScreenIds().getArray().toArray());
outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
-
AbstractFloatingView widgets = AbstractFloatingView
.getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
if (widgets != null) {
@@ -2073,25 +2081,49 @@
}
/**
- * Sets the next page to bind synchronously on next bind.
- * @param page
+ * Sets the next pages to bind synchronously on next bind.
+ * @param pages should not be null.
*/
- public void setPageToBindSynchronously(int page) {
- mPageToBindSynchronously = page;
+ public void setPagesToBindSynchronously(@NonNull IntSet pages) {
+ mPagesToBindSynchronously = pages;
}
- /**
- * Implementation of the method from LauncherModel.Callbacks.
- */
@Override
- public int getPageToBindSynchronously() {
- if (mPageToBindSynchronously != PagedView.INVALID_PAGE) {
- return mPageToBindSynchronously;
- } else if (mWorkspace != null) {
- return mWorkspace.getCurrentPage();
+ public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
+ IntSet visibleIds = mPagesToBindSynchronously.isEmpty()
+ ? mWorkspace.getCurrentPageScreenIds() : mPagesToBindSynchronously;
+ IntArray actualIds = new IntArray();
+
+ if (mDeviceProfile.isTwoPanels) {
+ actualIds.add(LEFT_PANEL_ID);
} else {
- return 0;
+ visibleIds.remove(LEFT_PANEL_ID);
}
+ IntSet result = new IntSet();
+ if (visibleIds.isEmpty()) {
+ return result;
+ }
+ for (int id : orderedScreenIds.toArray()) {
+ if (id != LEFT_PANEL_ID) {
+ actualIds.add(id);
+ }
+ }
+ int firstId = visibleIds.getArray().get(0);
+ if (actualIds.contains(firstId)) {
+ result.add(firstId);
+
+ if (mDeviceProfile.isTwoPanels) {
+ int index = actualIds.indexOf(firstId);
+ int nextIndex = ((int) (index / 2)) * 2;
+ if (nextIndex == index) {
+ nextIndex++;
+ }
+ if (nextIndex < actualIds.size()) {
+ result.add(actualIds.get(nextIndex));
+ }
+ }
+ }
+ return result;
}
/**
@@ -2101,7 +2133,7 @@
@Override
public void clearPendingBinds() {
if (mPendingExecutor != null) {
- mPendingExecutor.markCompleted();
+ mPendingExecutor.cancel();
mPendingExecutor = null;
// We might have set this flag previously and forgot to clear it.
@@ -2139,11 +2171,16 @@
@Override
public void bindScreens(IntArray orderedScreenIds) {
- // Make sure the first screen is always at the start.
+ // Make sure the first screen is at the start if there's no widget panel,
+ // or on the second place if the first is the widget panel
+ boolean isLeftPanelShown =
+ mWorkspace.mWorkspaceScreens.containsKey(LEFT_PANEL_ID);
+ int firstScreenPosition = isLeftPanelShown && orderedScreenIds.size() > 1 ? 1 : 0;
+
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
- orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
+ orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
- orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
+ orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
mWorkspace.addExtraEmptyScreen();
@@ -2160,10 +2197,17 @@
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
int screenId = orderedScreenIds.get(i);
- if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID) {
// No need to bind the first screen, as its always bound.
- mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
+ continue;
}
+
+ if (screenId == LEFT_PANEL_ID) {
+ // No need to bind the left panel, as its always bound.
+ continue;
+ }
+
+ mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
}
@@ -2238,6 +2282,11 @@
continue;
}
+ // Skip if the item is on the left widget panel but the panel is not shown
+ if (item.screenId == LEFT_PANEL_ID && !getDeviceProfile().isTwoPanels) {
+ continue;
+ }
+
final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
@@ -2528,25 +2577,6 @@
return info;
}
- public void onPageBoundSynchronously(int page) {
- mSynchronouslyBoundPage = page;
- mWorkspace.setCurrentPage(page);
- mPageToBindSynchronously = PagedView.INVALID_PAGE;
- }
-
- @Override
- public void executeOnNextDraw(ViewOnDrawExecutor executor) {
- clearPendingBinds();
- mPendingExecutor = executor;
- if (!isInState(ALL_APPS)) {
- mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
- mPendingExecutor.execute(() -> mAppsView.getAppsStore().disableDeferUpdates(
- AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
- }
-
- executor.attachTo(this);
- }
-
public void clearPendingExecutor(ViewOnDrawExecutor executor) {
if (mPendingExecutor == executor) {
mPendingExecutor = null;
@@ -2554,22 +2584,28 @@
}
@Override
- public void finishFirstPageBind(final ViewOnDrawExecutor executor) {
+ public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ mSynchronouslyBoundPages = boundPages;
+ mPagesToBindSynchronously = new IntSet();
+
+ clearPendingBinds();
+ ViewOnDrawExecutor executor = new ViewOnDrawExecutor(pendingTasks);
+ mPendingExecutor = executor;
+ if (!isInState(ALL_APPS)) {
+ mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
+ pendingTasks.add(() -> mAppsView.getAppsStore().disableDeferUpdates(
+ AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
+ }
+
AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
if (property.getValue() < 1) {
ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1);
- if (executor != null) {
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- executor.onLoadAnimationCompleted();
- }
- });
- }
+ anim.addListener(AnimatorListeners.forEndCallback(executor::onLoadAnimationCompleted));
anim.start();
- } else if (executor != null) {
+ } else {
executor.onLoadAnimationCompleted();
}
+ executor.attachTo(this);
}
/**
@@ -2577,7 +2613,7 @@
*
* Implementation of the method from LauncherModel.Callbacks.
*/
- public void finishBindingItems(int pageBoundFirst) {
+ public void finishBindingItems(IntSet pagesBoundFirst) {
Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
mWorkspace.restoreInstanceStateForRemainingPages();
@@ -2589,14 +2625,14 @@
mPendingActivityResult = null;
}
- ItemInstallQueue.INSTANCE.get(this)
- .resumeModelPush(FLAG_LOADER_RUNNING);
-
+ int currentPage = pagesBoundFirst != null && !pagesBoundFirst.isEmpty()
+ ? mWorkspace.getPageIndexForScreenId(pagesBoundFirst.getArray().get(0))
+ : PagedView.INVALID_PAGE;
// When undoing the removal of the last item on a page, return to that page.
// Since we are just resetting the current page without user interaction,
// override the previous page so we don't log the page switch.
- mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
- mPageToBindSynchronously = PagedView.INVALID_PAGE;
+ mWorkspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */);
+ mPagesToBindSynchronously = new IntSet();
// Cache one page worth of icons
getViewCache().setCacheSize(R.layout.folder_application,
@@ -2666,7 +2702,7 @@
@Override
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
if (!updated.isEmpty()) {
- mWorkspace.updateShortcuts(updated);
+ mWorkspace.updateWorkspaceItems(updated, this);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
}
@@ -2678,7 +2714,7 @@
*/
@Override
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
- mWorkspace.updateRestoreItems(updates);
+ mWorkspace.updateRestoreItems(updates, this);
}
/**
@@ -2880,13 +2916,6 @@
return new float[] {NO_SCALE, NO_OFFSET};
}
- /**
- * @see LauncherState#getTaskbarScale(Launcher)
- */
- public float getNormalTaskbarScale() {
- return 1f;
- }
-
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index eef3980..6966abf 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -144,9 +144,10 @@
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}
- public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
+ public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges,
+ @Nullable Callbacks owner) {
return new ModelWriter(mApp.getContext(), this, mBgDataModel,
- hasVerticalHotseat, verifyChanges);
+ hasVerticalHotseat, verifyChanges, owner);
}
@Override
@@ -329,7 +330,7 @@
public boolean addCallbacksAndLoad(Callbacks callbacks) {
synchronized (mLock) {
addCallbacks(callbacks);
- return startLoader();
+ return startLoader(new Callbacks[] { callbacks });
}
}
@@ -349,26 +350,32 @@
* @return true if the page could be bound synchronously.
*/
public boolean startLoader() {
+ return startLoader(new Callbacks[0]);
+ }
+
+ private boolean startLoader(Callbacks[] newCallbacks) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
ItemInstallQueue.INSTANCE.get(mApp.getContext())
.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
synchronized (mLock) {
- // Don't bother to start the thread if we know it's not going to do anything
- final Callbacks[] callbacksList = getCallbacks();
+ // If there is already one running, tell it to stop.
+ boolean wasRunning = stopLoader();
+ boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
+ boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
+ final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
+
if (callbacksList.length > 0) {
// Clear any pending bind-runnables from the synchronized load process.
for (Callbacks cb : callbacksList) {
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
- // If there is already one running, tell it to stop.
- stopLoader();
LoaderResults loaderResults = new LoaderResults(
mApp, mBgDataModel, mBgAllAppsList, callbacksList);
- if (mModelLoaded && !mIsLoaderTaskRunning) {
+ if (bindDirectly) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
- loaderResults.bindWorkspace();
+ loaderResults.bindWorkspace(bindAllCallbacks);
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
@@ -387,7 +394,7 @@
* If there is already a loader task running, tell it to stop.
* @return true if an existing loader was stopped.
*/
- public boolean stopLoader() {
+ private boolean stopLoader() {
synchronized (mLock) {
LoaderTask oldTask = mLoaderTask;
mLoaderTask = null;
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 440e9e3..a8ed6bc 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -61,7 +61,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DbDowngradeHelper;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@@ -1090,7 +1089,7 @@
}
private int initializeMaxScreenId(SQLiteDatabase db) {
- return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d",
+ return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
Favorites.CONTAINER_DESKTOP);
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 3399ce9..7985ab5 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -181,14 +181,6 @@
return launcher.getNormalOverviewScaleAndOffset();
}
- public float getTaskbarScale(Launcher launcher) {
- return launcher.getNormalTaskbarScale();
- }
-
- public float getTaskbarTranslationY(Launcher launcher) {
- return -launcher.getHotseat().getTaskbarOffsetY();
- }
-
public float getOverviewFullscreenProgress() {
return 0;
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index b423871..74b0d9c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+
import static com.android.launcher3.anim.Interpolators.SCROLL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
@@ -48,6 +50,7 @@
import android.widget.ScrollView;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -55,6 +58,7 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
import com.android.launcher3.util.EdgeEffectCompat;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
@@ -100,6 +104,10 @@
@ViewDebug.ExportedProperty(category = "launcher")
protected int mCurrentPage;
+ // Difference between current scroll position and mCurrentPage's page scroll. Used to maintain
+ // relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a
+ // page.
+ protected int mCurrentPageScrollDiff;
@ViewDebug.ExportedProperty(category = "launcher")
protected int mNextPage = INVALID_PAGE;
@@ -243,7 +251,7 @@
// If the current page is invalid, just reset the scroll position to zero
int newPosition = 0;
if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
- newPosition = getScrollForPage(mCurrentPage);
+ newPosition = getScrollForPage(mCurrentPage) + mCurrentPageScrollDiff;
}
mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition);
mScroller.startScroll(mScroller.getCurrX(), 0, newPosition - mScroller.getCurrX(), 0);
@@ -291,9 +299,15 @@
return newPage;
}
- private int getLeftmostVisiblePageForIndex(int pageIndex) {
+ /**
+ * In most cases where panelCount is 1, this method will just return the page index that was
+ * passed in.
+ * But for example when two panel home is enabled we might need the leftmost visible page index
+ * because that page is the current page.
+ */
+ public int getLeftmostVisiblePageForIndex(int pageIndex) {
int panelCount = getPanelCount();
- return (pageIndex / panelCount) * panelCount;
+ return pageIndex - pageIndex % panelCount;
}
/**
@@ -304,16 +318,34 @@
}
/**
+ * Returns an IntSet with the indices of the currently visible pages
+ */
+ @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
+ public IntSet getVisiblePageIndices() {
+ IntSet visiblePageIndices = new IntSet();
+ int panelCount = getPanelCount();
+ int pageCount = getPageCount();
+
+ // If a device goes from one panel to two panel (i.e. unfolding a foldable device) while
+ // an odd indexed page is the current page, then the new leftmost visible page will be
+ // different from the old mCurrentPage.
+ int currentPage = getLeftmostVisiblePageForIndex(mCurrentPage);
+ for (int page = currentPage; page < currentPage + panelCount && page < pageCount; page++) {
+ visiblePageIndices.add(page);
+ }
+ return visiblePageIndices;
+ }
+
+ /**
* Executes the callback against each visible page
*/
public void forEachVisiblePage(Consumer<View> callback) {
- int panelCount = getPanelCount();
- for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) {
- View page = getPageAt(i);
+ getVisiblePageIndices().forEach(pageIndex -> {
+ View page = getPageAt(pageIndex);
if (page != null) {
callback.accept(page);
}
- }
+ });
}
/**
@@ -424,6 +456,7 @@
* to provide custom behavior during animation.
*/
protected void onPageEndTransition() {
+ mCurrentPageScrollDiff = 0;
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
@@ -676,6 +709,7 @@
final int scrollOffsetStart = mOrientationHandler.getScrollOffsetStart(this, mInsets);
final int scrollOffsetEnd = mOrientationHandler.getScrollOffsetEnd(this, mInsets);
boolean pageScrollChanged = false;
+ int panelCount = getPanelCount();
for (int i = startIndex, childStart = scrollOffsetStart; i != endIndex; i += delta) {
final View child = getPageAt(i);
@@ -693,11 +727,15 @@
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
}
- childStart += primaryDimension + mPageSpacing + getChildGap();
+ childStart += primaryDimension + getChildGap();
+
+ // This makes sure that the space is added after the page, not after each panel
+ if (i % panelCount == panelCount - 1) {
+ childStart += mPageSpacing;
+ }
}
}
- int panelCount = getPanelCount();
if (panelCount > 1) {
for (int i = 0; i < childCount; i++) {
// In case we have multiple panels, always use left panel's page scroll for all
@@ -1054,26 +1092,25 @@
protected float getScrollProgress(int screenCenter, View v, int page) {
final int halfScreenSize = getMeasuredWidth() / 2;
-
int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
- int count = getChildCount();
+ int panelCount = getPanelCount();
+ int pageCount = getChildCount();
- final int totalDistance;
-
- int adjacentPage = page + 1;
+ int adjacentPage = page + panelCount;
if ((delta < 0 && !mIsRtl) || (delta > 0 && mIsRtl)) {
- adjacentPage = page - 1;
+ adjacentPage = page - panelCount;
}
- if (adjacentPage < 0 || adjacentPage > count - 1) {
- totalDistance = v.getMeasuredWidth() + mPageSpacing;
+ final int totalDistance;
+ if (adjacentPage < 0 || adjacentPage > pageCount - 1) {
+ totalDistance = (v.getMeasuredWidth() + mPageSpacing) * panelCount;
} else {
totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page));
}
float scrollProgress = delta / (totalDistance * 1.0f);
scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS);
- scrollProgress = Math.max(scrollProgress, - MAX_SCROLL_PROGRESS);
+ scrollProgress = Math.max(scrollProgress, -MAX_SCROLL_PROGRESS);
return scrollProgress;
}
@@ -1120,6 +1157,10 @@
mAllowOverScroll = enable;
}
+ protected float getSignificantMoveThreshold() {
+ return SIGNIFICANT_MOVE_THRESHOLD;
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Skip touch handling if there are no pages to swipe
@@ -1191,6 +1232,7 @@
}
delta -= consumed;
}
+ delta /= mOrientationHandler.getPrimaryScale(this);
// Only scroll and update mLastMotionX if we have moved some discrete amount. We
// keep the remainder because we are actually testing if we've moved from the last
@@ -1243,11 +1285,12 @@
int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
mActivePointerId);
- int delta = (int) (primaryDirection - mDownMotionPrimary);
+ float delta = primaryDirection - mDownMotionPrimary;
+ delta /= mOrientationHandler.getPrimaryScale(this);
int pageOrientedSize = mOrientationHandler.getMeasuredSize(getPageAt(mCurrentPage));
- boolean isSignificantMove = Math.abs(delta) > pageOrientedSize *
- SIGNIFICANT_MOVE_THRESHOLD;
+ boolean isSignificantMove = Math.abs(delta)
+ > pageOrientedSize * getSignificantMoveThreshold();
mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
boolean passedSlop = mAllowEasyFling || mTotalMotion > mPageSlop;
@@ -1610,7 +1653,7 @@
public boolean scrollLeft() {
if (getNextPage() > 0) {
- snapToPage(getNextPage() - 1);
+ snapToPage(getNextPage() - getPanelCount());
return true;
}
return mAllowOverScroll;
@@ -1618,7 +1661,7 @@
public boolean scrollRight() {
if (getNextPage() < getChildCount() - 1) {
- snapToPage(getNextPage() + 1);
+ snapToPage(getNextPage() + getPanelCount());
return true;
}
return mAllowOverScroll;
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 519b63d..bebbf4f 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -31,7 +31,7 @@
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.FolderIconParent {
static final String TAG = "ShortcutAndWidgetContainer";
@@ -104,9 +104,9 @@
public void setupLp(View child) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- if (child instanceof LauncherAppWidgetHostView) {
+ if (child instanceof NavigableAppWidgetHostView) {
DeviceProfile profile = mActivity.getDeviceProfile();
- ((LauncherAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
+ ((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing, mTempRect);
} else {
@@ -129,8 +129,8 @@
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
final DeviceProfile dp = mActivity.getDeviceProfile();
- if (child instanceof LauncherAppWidgetHostView) {
- ((LauncherAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
+ if (child instanceof NavigableAppWidgetHostView) {
+ ((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
dp.appWidgetScale.x, dp.appWidgetScale.y, mBorderSpacing, mTempRect);
} else {
@@ -178,16 +178,16 @@
*/
public void layoutChild(View child) {
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- if (child instanceof LauncherAppWidgetHostView) {
- LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) child;
+ if (child instanceof NavigableAppWidgetHostView) {
+ NavigableAppWidgetHostView nahv = (NavigableAppWidgetHostView) child;
// Scale and center the widget to fit within its cells.
DeviceProfile profile = mActivity.getDeviceProfile();
float scaleX = profile.appWidgetScale.x;
float scaleY = profile.appWidgetScale.y;
- lahv.setScaleToFit(Math.min(scaleX, scaleY));
- lahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+ nahv.setScaleToFit(Math.min(scaleX, scaleY));
+ nahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
-(lp.height - (lp.height * scaleY)) / 2.0f);
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 75f6278..7d818d2 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -85,6 +85,7 @@
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -380,6 +381,21 @@
}
/**
+ * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows different scales
+ * for X and Y
+ */
+ public static void scaleRectFAboutCenter(RectF r, float scaleX, float scaleY) {
+ float px = r.centerX();
+ float py = r.centerY();
+ r.offset(-px, -py);
+ r.left = r.left * scaleX;
+ r.top = r.top * scaleY;
+ r.right = r.right * scaleX;
+ r.bottom = r.bottom * scaleY;
+ r.offset(px, py);
+ }
+
+ /**
* Maps t from one range to another range.
* @param t The value to map.
* @param fromMin The lower bound of the range that t is being mapped from.
@@ -659,25 +675,26 @@
* @param outObj this is set to the internal data associated with {@param info},
* eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
*/
- public static Drawable getFullDrawable(Launcher launcher, ItemInfo info, int width, int height,
+ public static Drawable getFullDrawable(Context context, ItemInfo info, int width, int height,
Object[] outObj) {
- Drawable icon = loadFullDrawableWithoutTheme(launcher, info, width, height, outObj);
+ Drawable icon = loadFullDrawableWithoutTheme(context, info, width, height, outObj);
if (icon instanceof BitmapInfo.Extender) {
- icon = ((BitmapInfo.Extender) icon).getThemedDrawable(launcher);
+ icon = ((BitmapInfo.Extender) icon).getThemedDrawable(context);
}
return icon;
}
- private static Drawable loadFullDrawableWithoutTheme(Launcher launcher, ItemInfo info,
+ private static Drawable loadFullDrawableWithoutTheme(Context context, ItemInfo info,
int width, int height, Object[] outObj) {
- LauncherAppState appState = LauncherAppState.getInstance(launcher);
+ ActivityContext activity = ActivityContext.lookupContext(context);
+ LauncherAppState appState = LauncherAppState.getInstance(context);
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- LauncherActivityInfo activityInfo = launcher.getSystemService(LauncherApps.class)
+ LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
.resolveActivity(info.getIntent(), info.user);
outObj[0] = activityInfo;
- return activityInfo == null ? null : LauncherAppState.getInstance(launcher)
+ return activityInfo == null ? null : LauncherAppState.getInstance(context)
.getIconProvider().getIcon(
- activityInfo, launcher.getDeviceProfile().inv.fillResIconDpi);
+ activityInfo, activity.getDeviceProfile().inv.fillResIconDpi);
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
if (info instanceof PendingAddShortcutInfo) {
ShortcutConfigActivityInfo activityInfo =
@@ -686,18 +703,18 @@
return activityInfo.getFullResIcon(appState.getIconCache());
}
List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
- .buildRequest(launcher)
+ .buildRequest(context)
.query(ShortcutRequest.ALL);
if (si.isEmpty()) {
return null;
} else {
outObj[0] = si.get(0);
- return ShortcutCachingLogic.getIcon(launcher, si.get(0),
+ return ShortcutCachingLogic.getIcon(context, si.get(0),
appState.getInvariantDeviceProfile().fillResIconDpi);
}
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
- launcher, info.id, new Point(width, height));
+ activity, info.id, new Point(width, height));
if (icon == null) {
return null;
}
@@ -715,8 +732,8 @@
* badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
**/
@TargetApi(Build.VERSION_CODES.O)
- public static Drawable getBadge(Launcher launcher, ItemInfo info, Object obj) {
- LauncherAppState appState = LauncherAppState.getInstance(launcher);
+ public static Drawable getBadge(Context context, ItemInfo info, Object obj) {
+ LauncherAppState appState = LauncherAppState.getInstance(context);
int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
boolean iconBadged = (info instanceof ItemInfoWithIcon)
@@ -736,7 +753,7 @@
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
return ((FolderAdaptiveIcon) obj).getBadge();
} else {
- return launcher.getPackageManager()
+ return context.getPackageManager()
.getUserBadgedIcon(new FixedSizeEmptyDrawable(iconSize), info.user);
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5bdc402..faf2dd2 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static androidx.annotation.VisibleForTesting.PROTECTED;
+
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -63,6 +65,8 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
+import androidx.annotation.VisibleForTesting;
+
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.Interpolators;
@@ -79,7 +83,6 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.logger.LauncherAtom;
@@ -99,8 +102,10 @@
import com.android.launcher3.util.EdgeEffectCompat;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.OverlayEdgeEffect;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
@@ -119,7 +124,6 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -132,7 +136,7 @@
public class Workspace extends PagedView<WorkspacePageIndicator>
implements DropTarget, DragSource, View.OnTouchListener,
DragController.DragListener, Insettable, StateHandler<LauncherState>,
- WorkspaceLayoutManager {
+ WorkspaceLayoutManager, LauncherBindableItemsContainer {
/** The value that {@link #mTransitionProgress} must be greater than for
* {@link #transitionStateShouldAllowDrop()} to return true. */
@@ -312,9 +316,7 @@
// Increase our bottom insets so we don't overlap with the taskbar.
mInsets.bottom += grid.nonOverlappingTaskbarInset;
- if (isTwoPanelEnabled()) {
- setPageSpacing(0); // we have two pages and we don't want any spacing
- } else if (mWorkspaceFadeInAdjacentScreens) {
+ if (mWorkspaceFadeInAdjacentScreens) {
// In landscape mode the page spacing is set to the default.
setPageSpacing(grid.edgeMarginPx);
} else {
@@ -462,7 +464,8 @@
}
@Override
- protected int getPanelCount() {
+ @VisibleForTesting(otherwise = PROTECTED)
+ public int getPanelCount() {
return isTwoPanelEnabled() ? 2 : super.getPanelCount();
}
@@ -552,8 +555,12 @@
if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
return;
}
+ if (isTwoPanelEnabled()) {
+ insertNewWorkspaceScreen(Workspace.LEFT_PANEL_ID, getChildCount());
+ }
+
// Add the first page
- CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, 0);
+ CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
// Always add a QSB on the first screen.
if (qsb == null) {
// In transposed layout, we add the QSB in the Grid. As workspace does not touch the
@@ -785,6 +792,10 @@
return indexOfChild(mWorkspaceScreens.get(screenId));
}
+ public IntSet getCurrentPageScreenIds() {
+ return IntSet.wrap(getScreenIdForPageIndex(getCurrentPage()));
+ }
+
public int getScreenIdForPageIndex(int index) {
if (0 <= index && index < mScreenOrder.size()) {
return mScreenOrder.get(index);
@@ -2690,9 +2701,6 @@
public void animateWidgetDrop(ItemInfo info, CellLayout cellLayout, final DragView dragView,
final Runnable onCompleteRunnable, int animationType, final View finalView,
boolean external) {
- Rect from = new Rect();
- mLauncher.getDragLayer().getViewRectRelativeToSelf(dragView, from);
-
int[] finalPos = new int[2];
float scaleXY[] = new float[2];
boolean scalePreview = !(info instanceof PendingAddShortcutInfo);
@@ -2736,8 +2744,8 @@
}
}
};
- dragLayer.animateViewIntoPosition(dragView, from.left, from.top, finalPos[0],
- finalPos[1], 1, 1, 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
+ dragLayer.animateViewIntoPosition(dragView, finalPos[0],
+ finalPos[1], 1, scaleXY[0], scaleXY[1], onComplete, endStyle,
duration, this);
}
}
@@ -2965,9 +2973,9 @@
* @param user The user of the app to match.
*/
public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
- final Workspace.ItemOperator preferredItem = (ItemInfo info, View view) ->
+ final ItemOperator preferredItem = (ItemInfo info, View view) ->
info != null && info.id == preferredItemId;
- final Workspace.ItemOperator preferredItemInFolder = (info, view) -> {
+ final ItemOperator preferredItemInFolder = (info, view) -> {
if (info instanceof FolderInfo) {
FolderInfo folderInfo = (FolderInfo) info;
for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
@@ -2978,14 +2986,14 @@
}
return false;
};
- final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
+ final ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
info != null
&& info.itemType == ITEM_TYPE_APPLICATION
&& info.user.equals(user)
&& info.getTargetComponent() != null
&& TextUtils.equals(info.getTargetComponent().getPackageName(),
packageName);
- final Workspace.ItemOperator packageAndUserAndAppInFolder = (info, view) -> {
+ final ItemOperator packageAndUserAndAppInFolder = (info, view) -> {
if (info instanceof FolderInfo) {
FolderInfo folderInfo = (FolderInfo) info;
for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
@@ -3104,22 +3112,7 @@
stripEmptyScreens();
}
- public interface ItemOperator {
- /**
- * Process the next itemInfo, possibly with side-effect on the next item.
- *
- * @param info info for the shortcut
- * @param view view for the shortcut
- * @return true if done, false to continue the map
- */
- boolean evaluate(ItemInfo info, View view);
- }
-
- /**
- * Map the operator over the shortcuts and widgets, return the first-non-null value.
- *
- * @param op the operator to map over the shortcuts
- */
+ @Override
public void mapOverItems(ItemOperator op) {
for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
if (mapOverCellLayout(layout, op) != null) {
@@ -3145,31 +3138,6 @@
return null;
}
- void updateShortcuts(List<WorkspaceItemInfo> shortcuts) {
- final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
- ItemOperator op = (info, v) -> {
- if (v instanceof BubbleTextView && updates.contains(info)) {
- WorkspaceItemInfo si = (WorkspaceItemInfo) info;
- BubbleTextView shortcut = (BubbleTextView) v;
- Drawable oldIcon = shortcut.getIcon();
- boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
- && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
- shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
- }
-
- // Iterate all items
- return false;
- };
-
- mapOverItems(op);
- Folder openFolder = Folder.getOpen(mLauncher);
- if (openFolder != null) {
- openFolder.iterateOverItems(op);
- }
- }
-
public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
@@ -3209,28 +3177,6 @@
removeItemsByMatcher(matcher);
}
- public void updateRestoreItems(final HashSet<ItemInfo> updates) {
- ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
- && updates.contains(info)) {
- ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */);
- } else if (v instanceof PendingAppWidgetHostView
- && info instanceof LauncherAppWidgetInfo
- && updates.contains(info)) {
- ((PendingAppWidgetHostView) v).applyState();
- } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
- }
- // process all the shortcuts
- return false;
- };
- mapOverItems(op);
- Folder folder = Folder.getOpen(mLauncher);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- }
-
public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
if (!changedInfo.isEmpty()) {
DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index d6302ce..326e3c3 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -32,6 +32,8 @@
int EXTRA_EMPTY_SCREEN_ID = -201;
// The is the first screen. It is always present, even if its empty.
int FIRST_SCREEN_ID = 0;
+ // This panel is shown on the first page if the panel count is greater than 1.
+ int LEFT_PANEL_ID = -777;
/**
* At bind time, we use the rank (screenId) to compute x and y for hotseat items.
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 01f7de6..3ab893b 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -56,6 +56,10 @@
mAnim = new AnimatorSet();
}
+ public long getDuration() {
+ return mDuration;
+ }
+
/**
* Utility method to sent an interpolator on an animation and add it to the list
*/
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 30c3417..97052b2 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -89,6 +89,14 @@
sendEventToTest(accessibilityManager, context, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
}
+ public static void sendDismissAnimationEndsEventToTest(Context context) {
+ final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
+ if (accessibilityManager == null) return;
+
+ sendEventToTest(accessibilityManager, context, TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE,
+ null);
+ }
+
private static void sendEventToTest(
AccessibilityManager accessibilityManager,
Context context, String eventTag, Bundle data) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index aab6cb2..1779ddb 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -220,14 +220,14 @@
+ "predictions to be updated while they are visible to the user.");
public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
- "ENABLE_TASKBAR", false, "Allows a system Taskbar to be shown on larger devices.");
+ "ENABLE_TASKBAR", true, "Allows a system Taskbar to be shown on larger devices.");
public static final BooleanFlag ENABLE_OVERVIEW_GRID = getDebugFlag(
- "ENABLE_OVERVIEW_GRID", false, "Uses grid overview layout. "
+ "ENABLE_OVERVIEW_GRID", true, "Uses grid overview layout. "
+ "Only applicable on large screen devices.");
public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(
- "ENABLE_TWO_PANEL_HOME", false,
+ "ENABLE_TWO_PANEL_HOME", true,
"Uses two panel on home screen. Only applicable on large screen devices.");
public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(
@@ -254,6 +254,10 @@
"ENABLE_WALLPAPER_SCRIM", false,
"Enables scrim over wallpaper for text protection.");
+ public static final BooleanFlag WIDGETS_IN_LAUNCHER_PREVIEW = getDebugFlag(
+ "WIDGETS_IN_LAUNCHER_PREVIEW", true,
+ "Enables widgets in Launcher preview for the Wallpaper app.");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
@@ -291,7 +295,7 @@
public static class BooleanFlag {
public final String key;
- public boolean defaultValue;
+ public final boolean defaultValue;
public BooleanFlag(String key, boolean defaultValue) {
this.key = key;
@@ -310,16 +314,12 @@
protected StringBuilder appendProps(StringBuilder src) {
return src.append(key).append(", defaultValue=").append(defaultValue);
}
-
- public void addChangeListener(Context context, Runnable r) { }
-
- public void removeChangeListener(Runnable r) {}
}
public static class DebugFlag extends BooleanFlag {
public final String description;
- private boolean mCurrentValue;
+ protected boolean mCurrentValue;
public DebugFlag(String key, boolean defaultValue, String description) {
super(key, defaultValue);
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 55be4a4..92ed18a 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -189,10 +189,18 @@
if (appWidgetHostView != null) {
bounds = new Rect();
appWidgetHostView.getSourceVisualDragBounds(bounds);
- bounds.offset(appWidgetHostView.getLeft() - (int) mLastTouchPos.x,
- appWidgetHostView.getTop() - (int) mLastTouchPos.y);
- listener = new PinItemDragListener(mRequest, bounds,
- appWidgetHostView.getMeasuredWidth(), appWidgetHostView.getMeasuredWidth());
+ float appWidgetHostViewScale = mWidgetCell.getAppWidgetHostViewScale();
+ int xOffset =
+ appWidgetHostView.getLeft() - (int) (mLastTouchPos.x * appWidgetHostViewScale);
+ int yOffset =
+ appWidgetHostView.getTop() - (int) (mLastTouchPos.y * appWidgetHostViewScale);
+ bounds.offset(xOffset, yOffset);
+ listener = new PinItemDragListener(
+ mRequest,
+ bounds,
+ appWidgetHostView.getMeasuredWidth(),
+ appWidgetHostView.getMeasuredWidth(),
+ appWidgetHostViewScale);
} else {
bounds = img.getBitmapBounds();
bounds.offset(img.getLeft() - (int) mLastTouchPos.x,
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 5731db4..1e0edac 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -74,7 +74,7 @@
/** Coordinate for last touch event **/
protected final Point mLastTouch = new Point();
- private final Point mTmpPoint = new Point();
+ protected final Point mTmpPoint = new Point();
protected DropTarget.DragObject mDragObject;
@@ -317,7 +317,7 @@
mDragObject.dragView.animateTo(mMotionDown.x, mMotionDown.y, onCompleteRunnable, duration);
}
- private void callOnDragEnd() {
+ protected void callOnDragEnd() {
if (mIsInPreDrag && mOptions.preDragCondition != null) {
mOptions.preDragCondition.onPreDragEnd(mDragObject, false /* dragStarted*/);
}
@@ -343,7 +343,7 @@
/**
* Clamps the position to the drag layer bounds.
*/
- private Point getClampedDragLayerPos(float x, float y) {
+ protected Point getClampedDragLayerPos(float x, float y) {
mActivity.getDragLayer().getLocalVisibleRect(mRectTemp);
mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1));
mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1));
@@ -390,7 +390,7 @@
return false;
}
- Point dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+ Point dragLayerPos = getClampedDragLayerPos(getX(ev), getY(ev));
mLastTouch.set(dragLayerPos.x, dragLayerPos.y);
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Remember location of down touch
@@ -403,6 +403,14 @@
return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
}
+ protected float getX(MotionEvent ev) {
+ return ev.getX();
+ }
+
+ protected float getY(MotionEvent ev) {
+ return ev.getY();
+ }
+
/**
* Call this from a drag source view.
*/
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index d4ce308..72e47e5 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -165,8 +165,11 @@
* Class for driving an internal (i.e. not using framework) drag/drop operation.
*/
static class InternalDragDriver extends DragDriver {
+ private final DragController mDragController;
+
InternalDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
super(dragController, sec);
+ mDragController = dragController;
}
@Override
@@ -176,11 +179,14 @@
switch (action) {
case MotionEvent.ACTION_MOVE:
- mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+ mEventListener.onDriverDragMove(mDragController.getX(ev),
+ mDragController.getY(ev));
break;
case MotionEvent.ACTION_UP:
- mEventListener.onDriverDragMove(ev.getX(), ev.getY());
- mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+ mEventListener.onDriverDragMove(mDragController.getX(ev),
+ mDragController.getY(ev));
+ mEventListener.onDriverDragEnd(mDragController.getX(ev),
+ mDragController.getY(ev));
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
@@ -197,7 +203,8 @@
switch (action) {
case MotionEvent.ACTION_UP:
- mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+ mEventListener.onDriverDragEnd(mDragController.getX(ev),
+ mDragController.getY(ev));
break;
case MotionEvent.ACTION_CANCEL:
mEventListener.onDriverDragCancel();
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 011325d..5ee4203 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -17,14 +17,19 @@
package com.android.launcher3.dragndrop;
+import static android.animation.ObjectAnimator.ofFloat;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.Utilities.mapRange;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.TypeEvaluator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -44,10 +49,11 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Workspace;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.SpringProperty;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.graphics.Scrim;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
-import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
@@ -69,11 +75,9 @@
private DragController mDragController;
// Variables relating to animation of views after drop
- private ValueAnimator mDropAnim = null;
+ private Animator mDropAnim = null;
- @Thunk DragView mDropView = null;
- @Thunk int mAnchorViewInitialScrollX = 0;
- @Thunk View mAnchorView = null;
+ private DragView mDropView = null;
private boolean mHoverPointClosesFolder = false;
@@ -220,12 +224,7 @@
public void animateViewIntoPosition(DragView dragView, final int[] pos, float alpha,
float scaleX, float scaleY, int animationEndStyle, Runnable onFinishRunnable,
int duration) {
- Rect r = new Rect();
- getViewRectRelativeToSelf(dragView, r);
- final int fromX = r.left;
- final int fromY = r.top;
-
- animateViewIntoPosition(dragView, fromX, fromY, pos[0], pos[1], alpha, 1, 1, scaleX, scaleY,
+ animateViewIntoPosition(dragView, pos[0], pos[1], alpha, scaleX, scaleY,
onFinishRunnable, animationEndStyle, duration, null);
}
@@ -241,11 +240,6 @@
parentChildren.measureChild(child);
parentChildren.layoutChild(child);
- Rect dragViewBounds = new Rect();
- getViewRectRelativeToSelf(dragView, dragViewBounds);
- final int fromX = dragViewBounds.left;
- final int fromY = dragViewBounds.top;
-
float coord[] = new float[2];
float childScale = child.getScaleX();
@@ -288,51 +282,50 @@
child.setVisibility(INVISIBLE);
Runnable onCompleteRunnable = () -> child.setVisibility(VISIBLE);
- animateViewIntoPosition(dragView, fromX, fromY, toX, toY, 1, 1, 1, toScale, toScale,
+ animateViewIntoPosition(dragView, toX, toY, 1, toScale, toScale,
onCompleteRunnable, ANIMATION_END_DISAPPEAR, duration, anchorView);
}
- public void animateViewIntoPosition(final DragView view, final int fromX, final int fromY,
- final int toX, final int toY, float finalAlpha, float initScaleX, float initScaleY,
- float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
- int animationEndStyle, int duration, View anchorView) {
- Rect from = new Rect(fromX, fromY, fromX +
- view.getMeasuredWidth(), fromY + view.getMeasuredHeight());
- Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
- animateView(view, from, to, finalAlpha, initScaleX, initScaleY, finalScaleX, finalScaleY, duration,
- null, null, onCompleteRunnable, animationEndStyle, anchorView);
- }
-
/**
* This method animates a view at the end of a drag and drop animation.
- *
+ */
+ public void animateViewIntoPosition(final DragView view,
+ final int toX, final int toY, float finalAlpha,
+ float finalScaleX, float finalScaleY, Runnable onCompleteRunnable,
+ int animationEndStyle, int duration, View anchorView) {
+ Rect to = new Rect(toX, toY, toX + view.getMeasuredWidth(), toY + view.getMeasuredHeight());
+ animateView(view, to, finalAlpha, finalScaleX, finalScaleY, duration,
+ null, onCompleteRunnable, animationEndStyle, anchorView);
+ }
+
+ /**
+ * This method animates a view at the end of a drag and drop animation.
* @param view The view to be animated. This view is drawn directly into DragLayer, and so
* doesn't need to be a child of DragLayer.
- * @param from The initial location of the view. Only the left and top parameters are used.
* @param to The final location of the view. Only the left and top parameters are used. This
- * location doesn't account for scaling, and so should be centered about the desired
- * final location (including scaling).
+* location doesn't account for scaling, and so should be centered about the desired
+* final location (including scaling).
* @param finalAlpha The final alpha of the view, in case we want it to fade as it animates.
* @param finalScaleX The final scale of the view. The view is scaled about its center.
* @param finalScaleY The final scale of the view. The view is scaled about its center.
* @param duration The duration of the animation.
* @param motionInterpolator The interpolator to use for the location of the view.
- * @param alphaInterpolator The interpolator to use for the alpha of the view.
* @param onCompleteRunnable Optional runnable to run on animation completion.
* @param animationEndStyle Whether or not to fade out the view once the animation completes.
- * {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
+* {@link #ANIMATION_END_DISAPPEAR} or {@link #ANIMATION_END_REMAIN_VISIBLE}.
* @param anchorView If not null, this represents the view which the animated view stays
- * anchored to in case scrolling is currently taking place. Note: currently this is
- * only used for the X dimension for the case of the workspace.
*/
- public void animateView(final DragView view, final Rect from, final Rect to,
- final float finalAlpha, final float initScaleX, final float initScaleY,
- final float finalScaleX, final float finalScaleY, int duration,
- final Interpolator motionInterpolator, final Interpolator alphaInterpolator,
- final Runnable onCompleteRunnable, final int animationEndStyle, View anchorView) {
+ public void animateView(final DragView view, final Rect to,
+ final float finalAlpha, final float finalScaleX, final float finalScaleY, int duration,
+ final Interpolator motionInterpolator, final Runnable onCompleteRunnable,
+ final int animationEndStyle, View anchorView) {
+ view.cancelAnimation();
+ view.requestLayout();
+
+ final int[] from = getViewLocationRelativeToSelf(view);
// Calculate the duration of the animation based on the object's distance
- final float dist = (float) Math.hypot(to.left - from.left, to.top - from.top);
+ final float dist = (float) Math.hypot(to.left - from[0], to.top - from[1]);
final Resources res = getResources();
final float maxDist = (float) res.getInteger(R.integer.config_dropAnimMaxDist);
@@ -346,93 +339,45 @@
}
// Fall back to cubic ease out interpolator for the animation if none is specified
- TimeInterpolator interpolator = null;
- if (alphaInterpolator == null || motionInterpolator == null) {
- interpolator = DEACCEL_1_5;
- }
+ TimeInterpolator interpolator =
+ motionInterpolator == null ? DEACCEL_1_5 : motionInterpolator;
// Animate the view
- final float initAlpha = view.getAlpha();
- final float dropViewScale = view.getScaleX();
- AnimatorUpdateListener updateCb = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- final float percent = (Float) animation.getAnimatedValue();
- final int width = view.getMeasuredWidth();
- final int height = view.getMeasuredHeight();
+ PendingAnimation anim = new PendingAnimation(duration);
+ anim.add(ofFloat(view, View.SCALE_X, finalScaleX), interpolator, SpringProperty.DEFAULT);
+ anim.add(ofFloat(view, View.SCALE_Y, finalScaleY), interpolator, SpringProperty.DEFAULT);
+ anim.setViewAlpha(view, finalAlpha, interpolator);
+ anim.setFloat(view, VIEW_TRANSLATE_Y, to.top, interpolator);
- float alphaPercent = alphaInterpolator == null ? percent :
- alphaInterpolator.getInterpolation(percent);
- float motionPercent = motionInterpolator == null ? percent :
- motionInterpolator.getInterpolation(percent);
-
- float initialScaleX = initScaleX * dropViewScale;
- float initialScaleY = initScaleY * dropViewScale;
- float scaleX = finalScaleX * percent + initialScaleX * (1 - percent);
- float scaleY = finalScaleY * percent + initialScaleY * (1 - percent);
- float alpha = finalAlpha * alphaPercent + initAlpha * (1 - alphaPercent);
-
- float fromLeft = from.left + (initialScaleX - 1f) * width / 2;
- float fromTop = from.top + (initialScaleY - 1f) * height / 2;
-
- int x = (int) (fromLeft + Math.round(((to.left - fromLeft) * motionPercent)));
- int y = (int) (fromTop + Math.round(((to.top - fromTop) * motionPercent)));
-
- int anchorAdjust = mAnchorView == null ? 0 : (int) (mAnchorView.getScaleX() *
- (mAnchorViewInitialScrollX - mAnchorView.getScrollX()));
-
- int xPos = x - mDropView.getScrollX() + anchorAdjust;
- int yPos = y - mDropView.getScrollY();
-
- mDropView.setTranslationX(xPos);
- mDropView.setTranslationY(yPos);
- mDropView.setScaleX(scaleX);
- mDropView.setScaleY(scaleY);
- mDropView.setAlpha(alpha);
- }
- };
- animateView(view, updateCb, duration, interpolator, onCompleteRunnable, animationEndStyle,
- anchorView);
+ ObjectAnimator xMotion = ofFloat(view, VIEW_TRANSLATE_X, to.left);
+ if (anchorView != null) {
+ final int startScroll = anchorView.getScrollX();
+ TypeEvaluator<Float> evaluator = (f, s, e) -> mapRange(f, s, e)
+ + (anchorView.getScaleX() * (startScroll - anchorView.getScrollX()));
+ xMotion.setEvaluator(evaluator);
+ }
+ anim.add(xMotion, interpolator, SpringProperty.DEFAULT);
+ if (onCompleteRunnable != null) {
+ anim.addListener(forEndCallback(onCompleteRunnable));
+ }
+ playDropAnimation(view, anim.buildAnim(), animationEndStyle);
}
- public void animateView(final DragView view, AnimatorUpdateListener updateCb, int duration,
- TimeInterpolator interpolator, final Runnable onCompleteRunnable,
- final int animationEndStyle, View anchorView) {
+ /**
+ * Runs a previously constructed drop animation
+ */
+ public void playDropAnimation(final DragView view, Animator animator, int animationEndStyle) {
// Clean up the previous animations
if (mDropAnim != null) mDropAnim.cancel();
// Show the drop view if it was previously hidden
mDropView = view;
- mDropView.cancelAnimation();
- mDropView.requestLayout();
-
- // Set the anchor view if the page is scrolling
- if (anchorView != null) {
- mAnchorViewInitialScrollX = anchorView.getScrollX();
- }
- mAnchorView = anchorView;
-
// Create and start the animation
- mDropAnim = new ValueAnimator();
- mDropAnim.setInterpolator(interpolator);
- mDropAnim.setDuration(duration);
- mDropAnim.setFloatValues(0f, 1f);
- mDropAnim.addUpdateListener(updateCb);
- mDropAnim.addListener(new AnimatorListenerAdapter() {
- public void onAnimationEnd(Animator animation) {
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
- switch (animationEndStyle) {
- case ANIMATION_END_DISAPPEAR:
- clearAnimatedView();
- break;
- case ANIMATION_END_REMAIN_VISIBLE:
- break;
- }
- mDropAnim = null;
- }
- });
+ mDropAnim = animator;
+ mDropAnim.addListener(forEndCallback(() -> mDropAnim = null));
+ if (animationEndStyle == ANIMATION_END_DISAPPEAR) {
+ mDropAnim.addListener(forEndCallback(this::clearAnimatedView));
+ }
mDropAnim.start();
}
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 3fdb256..f2ab96c 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -53,23 +53,20 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.Thunk;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
/** A custom view for rendering an icon, folder, shortcut or widget during drag-n-drop. */
-public class DragView extends FrameLayout implements StateListener<LauncherState> {
+public abstract class DragView<T extends Context & ActivityContext> extends FrameLayout {
public static final int VIEW_ZOOM_DURATION = 150;
@@ -82,19 +79,18 @@
private final int mHeight;
private final int mBlurSizeOutline;
- private final int mRegistrationX;
- private final int mRegistrationY;
+ protected final int mRegistrationX;
+ protected final int mRegistrationY;
private final float mInitialScale;
- private final float mScaleOnDrop;
- private final int[] mTempLoc = new int[2];
+ protected final float mScaleOnDrop;
+ protected final int[] mTempLoc = new int[2];
private final RunnableList mOnDragStartCallback = new RunnableList();
private Point mDragVisualizeOffset = null;
private Rect mDragRegion = null;
- private final Launcher mLauncher;
- private final DragLayer mDragLayer;
- @Thunk final DragController mDragController;
+ protected final T mActivity;
+ private final BaseDragLayer<T> mDragLayer;
private boolean mHasDrawn = false;
final ValueAnimator mAnim;
@@ -110,7 +106,7 @@
private Path mScaledMaskPath;
private Drawable mBadge;
- public DragView(Launcher launcher, Drawable drawable, int registrationX,
+ public DragView(T launcher, Drawable drawable, int registrationX,
int registrationY, final float initialScale, final float scaleOnDrop,
final float finalScaleDps) {
this(launcher, getViewFromDrawable(launcher, drawable),
@@ -123,7 +119,7 @@
* <p>
* The registration point is the point inside our view that the touch events should
* be centered upon.
- * @param launcher The Launcher instance
+ * @param activity The Launcher instance/ActivityContext this DragView is in.
* @param content the view content that is attached to the drag view.
* @param width the width of the dragView
* @param height the height of the dragView
@@ -133,13 +129,12 @@
* @param scaleOnDrop the scale used in the drop animation.
* @param finalScaleDps the scale used in the zoom out animation when the drag view is shown.
*/
- public DragView(Launcher launcher, View content, int width, int height, int registrationX,
+ public DragView(T activity, View content, int width, int height, int registrationX,
int registrationY, final float initialScale, final float scaleOnDrop,
final float finalScaleDps) {
- super(launcher);
- mLauncher = launcher;
- mDragLayer = launcher.getDragLayer();
- mDragController = launcher.getDragController();
+ super(activity);
+ mActivity = activity;
+ mDragLayer = activity.getDragLayer();
mContent = content;
mWidth = width;
@@ -153,6 +148,12 @@
addView(content, new LayoutParams(width, height));
+ // If there is already a scale set on the content, we don't want to clip the children.
+ if (content.getScaleX() != 1 || content.getScaleY() != 1) {
+ setClipChildren(false);
+ setClipToPadding(false);
+ }
+
final float scale = (width + finalScaleDps) / width;
// Set the initial scale to avoid any jumps
@@ -188,24 +189,6 @@
setWillNotDraw(false);
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLauncher.getStateManager().addStateListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mLauncher.getStateManager().removeStateListener(this);
- }
-
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- setVisibility((finalState == LauncherState.NORMAL
- || finalState == LauncherState.SPRING_LOADED) ? VISIBLE : INVISIBLE);
- }
-
/**
* Initialize {@code #mIconDrawable} if the item can be represented using
* an {@link AdaptiveIconDrawable} or {@link FolderAdaptiveIcon}.
@@ -222,10 +205,10 @@
Object[] outObj = new Object[1];
int w = mWidth;
int h = mHeight;
- Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
+ Drawable dr = Utilities.getFullDrawable(mActivity, info, w, h, outObj);
if (dr instanceof AdaptiveIconDrawable) {
- int blurMargin = (int) mLauncher.getResources()
+ int blurMargin = (int) mActivity.getResources()
.getDimension(R.dimen.blur_size_medium_outline) / 2;
Rect bounds = new Rect(0, 0, w, h);
@@ -233,13 +216,13 @@
// Badge is applied after icon normalization so the bounds for badge should not
// be scaled down due to icon normalization.
Rect badgeBounds = new Rect(bounds);
- mBadge = getBadge(mLauncher, info, outObj[0]);
+ mBadge = getBadge(mActivity, info, outObj[0]);
mBadge.setBounds(badgeBounds);
// Do not draw the background in case of folder as its translucent
final boolean shouldDrawBackground = !(dr instanceof FolderAdaptiveIcon);
- try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
+ try (LauncherIcons li = LauncherIcons.obtain(mActivity)) {
Drawable nDr; // drawable to be normalized
if (shouldDrawBackground) {
nDr = dr;
@@ -430,12 +413,11 @@
applyTranslation();
}
- public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
- mTempLoc[0] = toTouchX - mRegistrationX;
- mTempLoc[1] = toTouchY - mRegistrationY;
- mDragLayer.animateViewIntoPosition(this, mTempLoc, 1f, mScaleOnDrop, mScaleOnDrop,
- DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
- }
+ /**
+ * Animate this DragView to the given DragLayer coordinates and then remove it.
+ */
+ public abstract void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable,
+ int duration);
public void animateShift(final int shiftX, final int shiftY) {
if (mAnim.isStarted()) {
@@ -471,7 +453,7 @@
Picture picture = new Picture();
mContent.draw(picture.beginRecording(mWidth, mHeight));
picture.endRecording();
- View view = new View(mLauncher);
+ View view = new View(mActivity);
view.setBackground(new PictureDrawable(picture));
view.measure(makeMeasureSpec(mWidth, EXACTLY), makeMeasureSpec(mHeight, EXACTLY));
view.layout(mContent.getLeft(), mContent.getTop(),
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 98c0cfc..74d9a22 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -32,12 +32,12 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.views.ActivityContext;
/**
* {@link AdaptiveIconDrawable} representation of a {@link FolderIcon}
@@ -70,14 +70,14 @@
}
public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
- Launcher launcher, int folderId, Point dragViewSize) {
+ ActivityContext activity, int folderId, Point dragViewSize) {
Preconditions.assertNonUiThread();
// Create the actual drawable on the UI thread to avoid race conditions with
// FolderIcon draw pass
try {
return MAIN_EXECUTOR.submit(() -> {
- FolderIcon icon = launcher.findFolderIcon(folderId);
+ FolderIcon icon = activity.findFolderIcon(folderId);
return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
}).get();
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java
index a98d70c..0e8b0a5 100644
--- a/src/com/android/launcher3/dragndrop/LauncherDragController.java
+++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java
@@ -96,7 +96,7 @@
final float scaleDps = mIsInPreDrag
? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
final DragView dragView = mDragObject.dragView = drawable != null
- ? new DragView(
+ ? new LauncherDragView(
mActivity,
drawable,
registrationX,
@@ -104,7 +104,7 @@
initialDragViewScale,
dragViewScaleOnDrop,
scaleDps)
- : new DragView(
+ : new LauncherDragView(
mActivity,
view,
view.getMeasuredWidth(),
diff --git a/src/com/android/launcher3/dragndrop/LauncherDragView.java b/src/com/android/launcher3/dragndrop/LauncherDragView.java
new file mode 100644
index 0000000..cc68e2e
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/LauncherDragView.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 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.dragndrop;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.statemanager.StateManager;
+
+/**
+ * A DragView drawn/used by the Launcher activity.
+ */
+public class LauncherDragView extends DragView<Launcher>
+ implements StateManager.StateListener<LauncherState> {
+
+
+ public LauncherDragView(Launcher launcher, Drawable drawable, int registrationX,
+ int registrationY, float initialScale, float scaleOnDrop, float finalScaleDps) {
+ super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
+ finalScaleDps);
+ }
+
+ public LauncherDragView(Launcher launcher, View content, int width, int height,
+ int registrationX, int registrationY, float initialScale, float scaleOnDrop,
+ float finalScaleDps) {
+ super(launcher, content, width, height, registrationX, registrationY, initialScale,
+ scaleOnDrop, finalScaleDps);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mActivity.getStateManager().addStateListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mActivity.getStateManager().removeStateListener(this);
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ setVisibility((finalState == LauncherState.NORMAL
+ || finalState == LauncherState.SPRING_LOADED) ? VISIBLE : INVISIBLE);
+ }
+
+ @Override
+ public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+ mTempLoc[0] = toTouchX - mRegistrationX;
+ mTempLoc[1] = toTouchY - mRegistrationY;
+ mActivity.getDragLayer().animateViewIntoPosition(this, mTempLoc, 1f, mScaleOnDrop,
+ mScaleOnDrop, DragLayer.ANIMATION_END_DISAPPEAR, onCompleteRunnable, duration);
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 2bdf8a0..af43ae8 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -48,12 +48,19 @@
private final PinItemRequest mRequest;
private final CancellationSignal mCancelSignal;
+ private final float mPreviewScale;
public PinItemDragListener(PinItemRequest request, Rect previewRect,
int previewBitmapWidth, int previewViewWidth) {
+ this(request, previewRect, previewBitmapWidth, previewViewWidth, /* previewScale= */ 1f);
+ }
+
+ public PinItemDragListener(PinItemRequest request, Rect previewRect,
+ int previewBitmapWidth, int previewViewWidth, float previewScale) {
super(previewRect, previewBitmapWidth, previewViewWidth);
mRequest = request;
mCancelSignal = new CancellationSignal();
+ mPreviewScale = previewScale;
}
@Override
@@ -98,7 +105,7 @@
PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
- dragHelper.setRemoteViewsPreview(getPreview(mRequest));
+ dragHelper.setRemoteViewsPreview(getPreview(mRequest), mPreviewScale);
}
return dragHelper;
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 22bb56c..7187188 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -75,7 +75,6 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
@@ -94,6 +93,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -1196,8 +1196,7 @@
}
void replaceFolderWithFinalItem() {
- mLauncherDelegate.replaceFolderWithFinalItem(this);
- mDestroyed = true;
+ mDestroyed = mLauncherDelegate.replaceFolderWithFinalItem(this);
}
public boolean isDestroyed() {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 96030f9..60d8cdb 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -336,8 +336,6 @@
if (animateView != null && mActivity instanceof Launcher) {
final Launcher launcher = (Launcher) mActivity;
DragLayer dragLayer = launcher.getDragLayer();
- Rect from = new Rect();
- dragLayer.getViewRectRelativeToSelf(animateView, from);
Rect to = finalRect;
if (to == null) {
to = new Rect();
@@ -403,13 +401,14 @@
}
final int finalIndex = index;
- dragLayer.animateView(animateView, from, to, finalAlpha,
- 1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
- Interpolators.DEACCEL_2, Interpolators.ACCEL_2,
+ dragLayer.animateView(animateView, to, finalAlpha,
+ finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
+ Interpolators.DEACCEL_2,
() -> {
mPreviewItemManager.hidePreviewItem(finalIndex, false);
mFolder.showItem(item);
- }, DragLayer.ANIMATION_END_DISAPPEAR, null);
+ },
+ DragLayer.ANIMATION_END_DISAPPEAR, null);
mFolder.hideItem(item);
@@ -683,6 +682,7 @@
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
+ updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
@@ -694,6 +694,7 @@
@Override
public void onRemove(List<WorkspaceItemInfo> items) {
+ updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
boolean isDotted = mDotInfo.hasDot();
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 3d2884a..65991e4 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -41,12 +41,12 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index f7d8e8c..e599e8c 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -93,7 +93,7 @@
}
}
- void replaceFolderWithFinalItem(Folder folder) {
+ boolean replaceFolderWithFinalItem(Folder folder) {
// Add the last remaining child to the workspace in place of the folder
Runnable onCompleteRunnable = new Runnable() {
@Override
@@ -147,6 +147,7 @@
} else {
onCompleteRunnable.run();
}
+ return true;
}
@@ -191,7 +192,7 @@
ModelWriter getModelWriter() {
if (mWriter == null) {
mWriter = LauncherAppState.getInstance((Context) mContext).getModel()
- .getWriter(false, false);
+ .getWriter(false, false, null);
}
return mWriter;
}
@@ -205,7 +206,9 @@
}
@Override
- void replaceFolderWithFinalItem(Folder folder) { }
+ boolean replaceFolderWithFinalItem(Folder folder) {
+ return false;
+ }
@Override
boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) {
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index a549750..f027b33 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -32,13 +32,13 @@
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import java.nio.ByteBuffer;
@@ -150,7 +150,7 @@
}
public float getScaleAndPosition(Drawable preview, int[] outPos) {
- float scale = Launcher.getLauncher(mView.getContext())
+ float scale = ActivityContext.lookupContext(mView.getContext())
.getDragLayer().getLocationInDragLayer(mView, outPos);
if (mView instanceof LauncherAppWidgetHostView) {
// App widgets are technically scaled, but are drawn at their expected size -- so the
@@ -167,7 +167,7 @@
/** Returns the scale and position of a given view for drag-n-drop. */
public float getScaleAndPosition(View view, int[] outPos) {
- float scale = Launcher.getLauncher(mView.getContext())
+ float scale = ActivityContext.lookupContext(mView.getContext())
.getDragLayer().getLocationInDragLayer(mView, outPos);
if (mView instanceof LauncherAppWidgetHostView) {
// App widgets are technically scaled, but are drawn at their expected size -- so the
@@ -201,7 +201,7 @@
public void run() {
Bitmap preview = convertPreviewToAlphaBitmap(mPreviewSnapshot);
if (mIsIcon) {
- int size = Launcher.getLauncher(mContext).getDeviceProfile().iconSizePx;
+ int size = ActivityContext.lookupContext(mContext).getDeviceProfile().iconSizePx;
preview = Bitmap.createScaledBitmap(preview, size, size, false);
}
//else case covers AppWidgetHost (doesn't drag/drop across different device profiles)
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index a387f04..94778a2 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -29,6 +29,7 @@
import android.app.Fragment;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
+import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
@@ -84,11 +85,15 @@
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.BaseLauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherAppWidgetHost;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LocalColorExtractor;
+import com.android.launcher3.widget.NavigableAppWidgetHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.ArrayList;
@@ -205,7 +210,8 @@
private final LayoutInflater mHomeElementInflater;
private final InsettableFrameLayout mRootView;
private final Hotseat mHotseat;
- private final CellLayout mWorkspace;
+ private final Map<Integer, CellLayout> mWorkspaceScreens = new HashMap<>();
+ private final AppWidgetHost mAppWidgetHost;
private final SparseIntArray mWallpaperColorResources;
public LauncherPreviewRenderer(Context context,
@@ -250,19 +256,31 @@
new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
mHomeElementInflater.setFactory2(this);
+ int layoutRes = mDp.isTwoPanels ? R.layout.launcher_preview_two_panel_layout
+ : R.layout.launcher_preview_layout;
mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
- R.layout.launcher_preview_layout, null, false);
+ layoutRes, null, false);
mRootView.setInsets(mInsets);
measureView(mRootView, mDp.widthPx, mDp.heightPx);
mHotseat = mRootView.findViewById(R.id.hotseat);
mHotseat.resetLayout(false);
- mWorkspace = mRootView.findViewById(R.id.workspace);
- mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
+ if (mDp.isTwoPanels) {
+ CellLayout leftPanel = mRootView.findViewById(R.id.workspace_left);
+ leftPanel.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
+ mDp.workspacePadding.top,
+ mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+ mDp.workspacePadding.bottom);
+ mWorkspaceScreens.put(LEFT_PANEL_ID, leftPanel);
+ }
+
+ CellLayout firstScreen = mRootView.findViewById(R.id.workspace);
+ firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
mDp.workspacePadding.top,
mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
mDp.workspacePadding.bottom);
+ mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen);
if (Utilities.ATLEAST_S) {
WallpaperColors wallpaperColors = wallpaperColorsOverride != null
@@ -273,6 +291,9 @@
} else {
mWallpaperColorResources = null;
}
+ mAppWidgetHost = FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()
+ ? new LauncherPreviewAppWidgetHost(context)
+ : null;
}
/** Populate preview and render it. */
@@ -330,18 +351,20 @@
@Override
public CellLayout getScreenWithId(int screenId) {
- return mWorkspace;
+ return mWorkspaceScreens.get(screenId);
}
private void inflateAndAddIcon(WorkspaceItemInfo info) {
+ CellLayout screen = mWorkspaceScreens.get(info.screenId);
BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
- R.layout.app_icon, mWorkspace, false);
+ R.layout.app_icon, screen, false);
icon.applyFromWorkspaceItem(info);
addInScreenFromBind(icon, info);
}
private void inflateAndAddFolder(FolderInfo info) {
- FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
+ CellLayout screen = mWorkspaceScreens.get(info.screenId);
+ FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, screen,
info);
addInScreenFromBind(folderIcon, info);
}
@@ -372,20 +395,31 @@
private void inflateAndAddWidgets(
LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
- AppWidgetHostView view = new AppWidgetHostView(mContext);
- view.setAppWidget(-1, providerInfo);
- view.updateAppWidget(null);
- view.setTag(info);
+ AppWidgetHostView view;
+ if (FeatureFlags.WIDGETS_IN_LAUNCHER_PREVIEW.get()) {
+ view = mAppWidgetHost.createView(mContext, info.appWidgetId, providerInfo);
+ } else {
+ view = new NavigableAppWidgetHostView(this) {
+ @Override
+ protected boolean shouldAllowDirectClick() {
+ return false;
+ }
+ };
+ view.setAppWidget(-1, providerInfo);
+ view.updateAppWidget(null);
+ }
if (mWallpaperColorResources != null) {
view.setColorResources(mWallpaperColorResources);
}
+ view.setTag(info);
addInScreenFromBind(view, info);
}
private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
- View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info);
+ CellLayout screen = mWorkspaceScreens.get(info.screenId);
+ View view = PredictedAppIconInflater.inflate(mHomeElementInflater, screen, info);
if (view != null) {
addInScreenFromBind(view, info);
}
@@ -416,11 +450,13 @@
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
- filterCurrentWorkspaceItems(0 /* currentScreenId */,
- dataModel.workspaceItems, currentWorkspaceItems,
- otherWorkspaceItems);
- filterCurrentWorkspaceItems(0 /* currentScreenId */, dataModel.appWidgets,
- currentAppWidgets, otherAppWidgets);
+
+ IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet());
+ filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems,
+ currentWorkspaceItems, otherWorkspaceItems);
+ filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
+ otherAppWidgets);
+
sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
for (ItemInfo itemInfo : currentWorkspaceItems) {
switch (itemInfo.itemType) {
@@ -473,12 +509,13 @@
// Add first page QSB
if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
View qsb = mHomeElementInflater.inflate(
- R.layout.search_container_workspace, mWorkspace, false);
+ R.layout.search_container_workspace, firstScreen, false);
CellLayout.LayoutParams lp =
- new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
+ new CellLayout.LayoutParams(0, 0, firstScreen.getCountX(), 1);
lp.canReorder = false;
- mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+ firstScreen.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
}
measureView(mRootView, mDp.widthPx, mDp.heightPx);
@@ -493,6 +530,32 @@
view.layout(0, 0, width, height);
}
+ private class LauncherPreviewAppWidgetHost extends AppWidgetHost {
+
+ private LauncherPreviewAppWidgetHost(Context context) {
+ super(context, LauncherAppWidgetHost.APPWIDGET_HOST_ID);
+ }
+
+ @Override
+ protected AppWidgetHostView onCreateView(
+ Context context,
+ int appWidgetId,
+ AppWidgetProviderInfo appWidget) {
+ return new LauncherPreviewAppWidgetHostView(LauncherPreviewRenderer.this);
+ }
+ }
+
+ private static class LauncherPreviewAppWidgetHostView extends BaseLauncherAppWidgetHostView {
+ private LauncherPreviewAppWidgetHostView(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected boolean shouldAllowDirectClick() {
+ return false;
+ }
+ }
+
/** Root layout for launcher preview that intercepts all touch events. */
public static class LauncherPreviewLayout extends InsettableFrameLayout {
public LauncherPreviewLayout(Context context, AttributeSet attrs) {
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index df49359..f3087c0 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -38,11 +38,13 @@
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.GridSizeMigrationTask;
@@ -163,10 +165,15 @@
@Override
public void run() {
+ DeviceProfile deviceProfile = mIdp.getDeviceProfile(mContext);
+ String query = (deviceProfile.isTwoPanels ? LauncherSettings.Favorites.SCREEN
+ + " = " + Workspace.LEFT_PANEL_ID + " or " : "")
+ + LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
+ + " or " + LauncherSettings.Favorites.CONTAINER + " = "
+ + LauncherSettings.Favorites.CONTAINER_HOTSEAT;
loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
- LauncherSettings.Favorites.SCREEN + " = 0 or "
- + LauncherSettings.Favorites.CONTAINER + " = "
- + LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+ query);
+
MAIN_EXECUTOR.execute(() -> {
renderView(previewContext, mBgDataModel, mWidgetProvidersMap);
mOnDestroyCallbacks.add(previewContext::onDestroy);
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 01b3e6e..4f12d0b 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
+
import android.content.Intent;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
@@ -27,6 +30,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
@@ -38,6 +42,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
@@ -287,28 +292,23 @@
// Find appropriate space for the item.
int screenId = 0;
- int[] cordinates = new int[2];
+ int[] coordinates = new int[2];
boolean found = false;
int screenCount = workspaceScreens.size();
// First check the preferred screen.
- int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
- if (preferredScreenIndex < screenCount) {
- screenId = workspaceScreens.get(preferredScreenIndex);
- found = findNextAvailableIconSpaceInScreen(
- app, screenItems.get(screenId), cordinates, spanX, spanY);
+ IntSet screensToExclude = IntSet.wrap(LEFT_PANEL_ID);
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ screensToExclude.add(FIRST_SCREEN_ID);
}
- if (!found) {
- // Search on any of the screens starting from the first screen.
- for (int screen = 1; screen < screenCount; screen++) {
- screenId = workspaceScreens.get(screen);
- if (findNextAvailableIconSpaceInScreen(
- app, screenItems.get(screenId), cordinates, spanX, spanY)) {
- // We found a space for it
- found = true;
- break;
- }
+ for (int screen = 0; screen < screenCount; screen++) {
+ screenId = workspaceScreens.get(screen);
+ if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen(
+ app, screenItems.get(screenId), coordinates, spanX, spanY)) {
+ // We found a space for it
+ found = true;
+ break;
}
}
@@ -324,11 +324,11 @@
// If we still can't find an empty space, then God help us all!!!
if (!findNextAvailableIconSpaceInScreen(
- app, screenItems.get(screenId), cordinates, spanX, spanY)) {
+ app, screenItems.get(screenId), coordinates, spanX, spanY)) {
throw new RuntimeException("Can't find space to add the item");
}
}
- return new int[] {screenId, cordinates[0], cordinates[1]};
+ return new int[] {screenId, coordinates[0], coordinates[1]};
}
private boolean findNextAvailableIconSpaceInScreen(
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 5c85bab..0e132c2 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,24 +16,27 @@
package com.android.launcher3.model;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import android.os.Process;
import android.util.Log;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.PagedView;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.LooperIdleLock;
-import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.util.RunnableList;
import java.util.ArrayList;
import java.util.Collections;
@@ -71,7 +74,7 @@
/**
* Binds all loaded data to actual views on the main thread.
*/
- public void bindWorkspace() {
+ public void bindWorkspace(boolean incrementBindId) {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
@@ -83,7 +86,9 @@
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
mBgDataModel.extraItems.forEach(extraItems::add);
- mBgDataModel.lastBindId++;
+ if (incrementBindId) {
+ mBgDataModel.lastBindId++;
+ }
mMyBindingId = mBgDataModel.lastBindId;
}
@@ -160,20 +165,7 @@
}
private void bind() {
- final int currentScreen;
- {
- // Create an anonymous scope to calculate currentScreen as it has to be a
- // final variable.
- int currScreen = mCallbacks.getPageToBindSynchronously();
- if (currScreen >= mOrderedScreenIds.size()) {
- // There may be no workspace screens (just hotseat items and an empty page).
- currScreen = PagedView.INVALID_PAGE;
- }
- currentScreen = currScreen;
- }
- final boolean validFirstPage = currentScreen >= 0;
- final int currentScreenId =
- validFirstPage ? mOrderedScreenIds.get(currentScreen) : INVALID_SCREEN_ID;
+ IntSet currentScreenIds = mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
// Separate the items that are on the current screen, and all the other remaining items
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
@@ -181,9 +173,9 @@
ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
- filterCurrentWorkspaceItems(currentScreenId, mWorkspaceItems, currentWorkspaceItems,
+ filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
- filterCurrentWorkspaceItems(currentScreenId, mAppWidgets, currentAppWidgets,
+ filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
otherAppWidgets);
final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
@@ -198,40 +190,29 @@
// Bind workspace screens
executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
- Executor mainExecutor = mUiExecutor;
// Load items on the current page.
- bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
- bindAppWidgets(currentAppWidgets, mainExecutor);
+ bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
+ bindAppWidgets(currentAppWidgets, mUiExecutor);
mExtraItems.forEach(item ->
- executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
+ executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
- // In case of validFirstPage, only bind the first screen, and defer binding the
- // remaining screens after first onDraw (and an optional the fade animation whichever
- // happens later).
- // This ensures that the first screen is immediately visible (eg. during rotation)
- // In case of !validFirstPage, bind all pages one after other.
+ RunnableList pendingTasks = new RunnableList();
+ Executor pendingExecutor = pendingTasks::add;
+ bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
+ bindAppWidgets(otherAppWidgets, pendingExecutor);
+ executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
+ pendingExecutor.execute(
+ () -> {
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+ ItemInstallQueue.INSTANCE.get(mApp.getContext())
+ .resumeModelPush(FLAG_LOADER_RUNNING);
+ });
- final Executor deferredExecutor =
- validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
-
- executeCallbacksTask(c -> c.finishFirstPageBind(
- validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null), mainExecutor);
-
- bindWorkspaceItems(otherWorkspaceItems, deferredExecutor);
- bindAppWidgets(otherAppWidgets, deferredExecutor);
- // Tell the workspace that we're done binding items
- executeCallbacksTask(c -> c.finishBindingItems(currentScreen), deferredExecutor);
-
- if (validFirstPage) {
- executeCallbacksTask(c -> {
- // We are loading synchronously, which means, some of the pages will be
- // bound after first draw. Inform the mCallbacks that page binding is
- // not complete, and schedule the remaining pages.
- c.onPageBoundSynchronously(currentScreen);
- c.executeOnNextDraw((ViewOnDrawExecutor) deferredExecutor);
-
- }, mUiExecutor);
- }
+ executeCallbacksTask(
+ c -> {
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ c.onInitialBindComplete(currentScreenIds, pendingTasks);
+ }, mUiExecutor);
}
private void bindWorkspaceItems(
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index ad553d5..a3a4717 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -90,7 +90,7 @@
public ModelWriter getModelWriter() {
// Updates from model task, do not deal with icon position in hotseat. Also no need to
// verify changes as the ModelTasks always push the changes to callbacks
- return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */);
+ return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */, null);
}
public void bindUpdatedWorkspaceItems(List<WorkspaceItemInfo> allUpdates) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 1d7d1a2..13ad90e 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -49,7 +49,7 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import java.io.FileDescriptor;
@@ -446,37 +446,50 @@
int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
/**
- * Returns the page number to bind first, synchronously if possible or -1
+ * Returns an IntSet of page ids to bind first, synchronously if possible
+ * or an empty IntSet
+ * @param orderedScreenIds All the page ids to be bound
*/
- int getPageToBindSynchronously();
- void clearPendingBinds();
- void startBinding();
- void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
- void bindScreens(IntArray orderedScreenIds);
- void finishFirstPageBind(ViewOnDrawExecutor executor);
- void finishBindingItems(int pageBoundFirst);
- void preAddApps();
- void bindAppsAdded(IntArray newScreens,
- ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
+ default IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
+ return new IntSet();
+ }
+
+ default void clearPendingBinds() { }
+ default void startBinding() { }
+
+ default void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+ default void bindScreens(IntArray orderedScreenIds) { }
+ default void finishBindingItems(IntSet pagesBoundFirst) { }
+ default void preAddApps() { }
+ default void bindAppsAdded(IntArray newScreens,
+ ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated) { }
+
+ /**
+ * Called when some persistent property of an item is modified
+ */
+ default void bindItemsModified(List<ItemInfo> items) { }
/**
* Binds updated incremental download progress
*/
- void bindIncrementalDownloadProgressUpdated(AppInfo app);
- void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated);
- void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
- void bindRestoreItemsChange(HashSet<ItemInfo> updates);
- void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
- void bindAllWidgets(List<WidgetsListBaseEntry> widgets);
- void onPageBoundSynchronously(int page);
- void executeOnNextDraw(ViewOnDrawExecutor executor);
- void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
+ default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
+ default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
+ default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+ default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+ default void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+ default void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
+
+ default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ pendingTasks.executeAllAndDestroy();
+ }
+
+ default void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) { }
/**
* Binds extra item provided any external source
*/
default void bindExtraContainerItems(FixedContainerItems item) { }
- void bindAllApplications(AppInfo[] apps, int flags);
+ default void bindAllApplications(AppInfo[] apps, int flags) { }
}
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 8a1d73e..3935bcf 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -41,6 +41,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
@@ -183,7 +184,7 @@
Point targetSize = new Point(idp.numColumns, idp.numRows);
GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(context, t.getDb(),
srcReader, destReader, idp.numDatabaseHotseatIcons, targetSize);
- task.migrate();
+ task.migrate(idp);
if (!migrateForPreview) {
dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
@@ -210,7 +211,7 @@
}
@VisibleForTesting
- protected boolean migrate() {
+ protected boolean migrate(InvariantDeviceProfile idp) {
if (mHotseatDiff.isEmpty() && mWorkspaceDiff.isEmpty()) {
return false;
}
@@ -224,7 +225,17 @@
Collections.sort(mWorkspaceDiff);
// Migrate workspace.
+ // First we create a collection of the screens
+ List<Integer> screens = new ArrayList<>();
+ if (idp.getDeviceProfile(mContext).isTwoPanels) {
+ screens.add(Workspace.LEFT_PANEL_ID);
+ }
for (int screenId = 0; screenId <= mDestReader.mLastScreenId; screenId++) {
+ screens.add(screenId);
+ }
+
+ // Then we place the items on the screens
+ for (int screenId : screens) {
if (DEBUG) {
Log.d(TAG, "Migrating " + screenId);
}
@@ -236,6 +247,8 @@
}
}
+ // In case the new grid is smaller, there might be some leftover items that don't fit on
+ // any of the screens, in this case we add them to new screens until all of them are placed.
int screenId = mDestReader.mLastScreenId + 1;
while (!mWorkspaceDiff.isEmpty()) {
GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 28fd389..43f9be5 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
+import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -86,6 +87,8 @@
import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PackageUserKey;
@@ -174,10 +177,17 @@
private void sendFirstScreenActiveInstallsBroadcast() {
ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
- // Screen set is never empty
- final int firstScreen = mBgDataModel.collectWorkspaceScreens().get(0);
- filterCurrentWorkspaceItems(firstScreen, allItems, firstScreenItems,
+ // Screen set is never empty
+ IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
+ final int firstScreen = allScreens.get(0);
+
+ IntSet firstScreens = IntSet.wrap(firstScreen);
+ if (firstScreen == LEFT_PANEL_ID && allScreens.size() >= 2) {
+ firstScreens.add(allScreens.get(1));
+ }
+
+ filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
new ArrayList<>() /* otherScreenItems are ignored */);
mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
}
@@ -208,7 +218,7 @@
}
verifyNotStopped();
- mResults.bindWorkspace();
+ mResults.bindWorkspace(true /* incrementBindId */);
logASplit(logger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 9b5fac8..58aa9e5 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -51,7 +51,8 @@
* Filters the set of items who are directly or indirectly (via another container) on the
* specified screen.
*/
- public static <T extends ItemInfo> void filterCurrentWorkspaceItems(int currentScreenId,
+ public static <T extends ItemInfo> void filterCurrentWorkspaceItems(
+ IntSet currentScreenIds,
ArrayList<T> allWorkspaceItems,
ArrayList<T> currentScreenItems,
ArrayList<T> otherScreenItems) {
@@ -65,7 +66,7 @@
(lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
for (T info : allWorkspaceItems) {
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
- if (info.screenId == currentScreenId) {
+ if (currentScreenIds.contains(info.screenId)) {
currentScreenItems.add(info);
itemsOnScreen.add(info.id);
} else {
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 080ce20..0439e75 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -23,12 +23,13 @@
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -36,19 +37,22 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.widget.LauncherAppWidgetHost;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -63,7 +67,10 @@
private final Context mContext;
private final LauncherModel mModel;
private final BgDataModel mBgDataModel;
- private final Handler mUiHandler;
+ private final LooperExecutor mUiExecutor;
+
+ @Nullable
+ private final Callbacks mOwner;
private final boolean mHasVerticalHotseat;
private final boolean mVerifyChanges;
@@ -73,13 +80,15 @@
private boolean mPreparingToUndo;
public ModelWriter(Context context, LauncherModel model, BgDataModel dataModel,
- boolean hasVerticalHotseat, boolean verifyChanges) {
+ boolean hasVerticalHotseat, boolean verifyChanges,
+ @Nullable Callbacks owner) {
mContext = context;
mModel = model;
mBgDataModel = dataModel;
mHasVerticalHotseat = hasVerticalHotseat;
mVerifyChanges = verifyChanges;
- mUiHandler = new Handler(Looper.getMainLooper());
+ mOwner = owner;
+ mUiExecutor = Executors.MAIN_EXECUTOR;
}
private void updateItemInfoProps(
@@ -155,6 +164,8 @@
public void moveItemInDatabase(final ItemInfo item,
int container, int screenId, int cellX, int cellY) {
updateItemInfoProps(item, container, screenId, cellX, cellY);
+ notifyItemModified(item);
+
enqueueDeleteRunnable(new UpdateItemRunnable(item, () ->
new ContentWriter(mContext)
.put(Favorites.CONTAINER, item.container)
@@ -171,6 +182,7 @@
public void moveItemsInDatabase(final ArrayList<ItemInfo> items, int container, int screen) {
ArrayList<ContentValues> contentValues = new ArrayList<>();
int count = items.size();
+ notifyOtherCallbacks(c -> c.bindItemsModified(items));
for (int i = 0; i < count; i++) {
ItemInfo item = items.get(i);
@@ -196,8 +208,9 @@
updateItemInfoProps(item, container, screenId, cellX, cellY);
item.spanX = spanX;
item.spanY = spanY;
+ notifyItemModified(item);
- ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () ->
+ MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () ->
new ContentWriter(mContext)
.put(Favorites.CONTAINER, item.container)
.put(Favorites.CELLX, item.cellX)
@@ -212,13 +225,18 @@
* Update an item to the database in a specified container.
*/
public void updateItemInDatabase(ItemInfo item) {
- ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> {
+ notifyItemModified(item);
+ MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () -> {
ContentWriter writer = new ContentWriter(mContext);
item.onAddToDatabase(writer);
return writer;
}));
}
+ private void notifyItemModified(ItemInfo item) {
+ notifyOtherCallbacks(c -> c.bindItemsModified(Collections.singletonList(item)));
+ }
+
/**
* Add an item to the database in a specified container. Sets the container, screen, cellX and
* cellY fields of the item. Also assigns an ID to the item.
@@ -229,10 +247,11 @@
final ContentResolver cr = mContext.getContentResolver();
item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE);
+ notifyOtherCallbacks(c -> c.bindItems(Collections.singletonList(item), false));
ModelVerifier verifier = new ModelVerifier();
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- ((Executor) MODEL_EXECUTOR).execute(() -> {
+ MODEL_EXECUTOR.execute(() -> {
// Write the item on background thread, as some properties might have been updated in
// the background.
final ContentWriter writer = new ContentWriter(mContext);
@@ -274,6 +293,7 @@
(item) -> item.getTargetComponent() == null ? ""
: item.getTargetComponent().getPackageName()).collect(
Collectors.joining(",")), new Exception());
+ notifyDelete(items);
enqueueDeleteRunnable(() -> {
for (ItemInfo item : items) {
final Uri uri = Favorites.getContentUri(item.id);
@@ -290,6 +310,7 @@
*/
public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
ModelVerifier verifier = new ModelVerifier();
+ notifyDelete(Collections.singleton(info));
enqueueDeleteRunnable(() -> {
ContentResolver cr = mContext.getContentResolver();
@@ -308,6 +329,7 @@
* Deletes the widget info and the widget id.
*/
public void deleteWidgetInfo(final LauncherAppWidgetInfo info, LauncherAppWidgetHost host) {
+ notifyDelete(Collections.singleton(info));
if (host != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) {
// Deleting an app widget ID is a void call but writes to disk before returning
// to the caller...
@@ -316,6 +338,10 @@
deleteItemFromDatabase(info);
}
+ private void notifyDelete(Collection<? extends ItemInfo> items) {
+ notifyOtherCallbacks(c -> c.bindWorkspaceComponentsRemoved(ItemInfoMatcher.ofItems(items)));
+ }
+
/**
* Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called
* if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or
@@ -341,14 +367,14 @@
if (mPreparingToUndo) {
mDeleteRunnables.add(r);
} else {
- ((Executor) MODEL_EXECUTOR).execute(r);
+ MODEL_EXECUTOR.execute(r);
}
}
public void commitDelete() {
mPreparingToUndo = false;
for (Runnable runnable : mDeleteRunnables) {
- ((Executor) MODEL_EXECUTOR).execute(runnable);
+ MODEL_EXECUTOR.execute(runnable);
}
mDeleteRunnables.clear();
}
@@ -364,6 +390,20 @@
mModel.forceReload();
}
+ private void notifyOtherCallbacks(CallbackTask task) {
+ if (mOwner == null) {
+ // If the call is happening from a model, it will take care of updating the callbacks
+ return;
+ }
+ mUiExecutor.execute(() -> {
+ for (Callbacks c : mModel.getCallbacks()) {
+ if (c != mOwner) {
+ task.execute(c);
+ }
+ }
+ });
+ }
+
private class UpdateItemRunnable extends UpdateItemBaseRunnable {
private final ItemInfo mItem;
private final Supplier<ContentWriter> mWriter;
@@ -484,7 +524,7 @@
int executeId = mBgDataModel.lastBindId;
- mUiHandler.post(() -> {
+ mUiExecutor.post(() -> {
int currentId = mBgDataModel.lastBindId;
if (currentId > executeId) {
// Model was already bound after job was executed.
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index d3f4909..e5424cf 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -50,8 +50,6 @@
*/
private boolean isEnabled = true;
- private boolean mHasFinishRecentsInAction = false;
-
public SystemShortcut(int iconResId, int labelResId, T target, ItemInfo itemInfo) {
mIconResId = iconResId;
mLabelResId = labelResId;
@@ -102,14 +100,6 @@
return mAccessibilityActionId == action;
}
- public void setHasFinishRecentsInAction(boolean hasFinishRecentsInAction) {
- mHasFinishRecentsInAction = hasFinishRecentsInAction;
- }
-
- public boolean hasFinishRecentsInAction() {
- return mHasFinishRecentsInAction;
- }
-
public interface Factory<T extends BaseDraggingActivity> {
@Nullable SystemShortcut<T> getShortcut(T activity, ItemInfo itemInfo);
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 5999091..1a96c23 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -35,22 +35,13 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
/**
* Launcher activity for secondary displays
@@ -175,67 +166,10 @@
}
@Override
- public int getPageToBindSynchronously() {
- return 0;
- }
-
- @Override
- public void clearPendingBinds() { }
-
- @Override
- public void startBinding() { }
-
- @Override
- public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
-
- @Override
- public void bindScreens(IntArray orderedScreenIds) { }
-
- @Override
- public void finishFirstPageBind(ViewOnDrawExecutor executor) {
- if (executor != null) {
- executor.onLoadAnimationCompleted();
- }
- }
-
- @Override
- public void finishBindingItems(int pageBoundFirst) { }
-
- @Override
- public void preAddApps() { }
-
- @Override
- public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
- ArrayList<ItemInfo> addAnimated) { }
-
- @Override
public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
mAppsView.getAppsStore().updateProgressBar(app);
}
- @Override
- public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
-
- @Override
- public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
-
- @Override
- public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
-
- @Override
- public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
-
- @Override
- public void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
-
- @Override
- public void onPageBoundSynchronously(int page) { }
-
- @Override
- public void executeOnNextDraw(ViewOnDrawExecutor executor) {
- executor.attachTo(getDragLayer(), false, null);
- }
-
/**
* Called when apps-button is clicked
*/
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 4261d08..944a41f 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -117,6 +117,15 @@
TestProtocol.sDisableSensorRotation = true;
return response;
+ case TestProtocol.REQUEST_IS_TABLET:
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, mDeviceProfile.isTablet);
+ return response;
+
+ case TestProtocol.REQUEST_IS_TWO_PANELS:
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ mDeviceProfile.isTwoPanels);
+ return response;
+
default:
return null;
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index b6da7fc..65bec25 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -24,6 +24,7 @@
public static final String SWITCHED_TO_STATE_MESSAGE = "TAPL_SWITCHED_TO_STATE";
public static final String SCROLL_FINISHED_MESSAGE = "TAPL_SCROLL_FINISHED";
public static final String PAUSE_DETECTED_MESSAGE = "TAPL_PAUSE_DETECTED";
+ public static final String DISMISS_ANIMATION_ENDS_MESSAGE = "TAPL_DISMISS_ANIMATION_ENDS";
public static final int NORMAL_STATE_ORDINAL = 0;
public static final int SPRING_LOADED_STATE_ORDINAL = 1;
public static final int OVERVIEW_STATE_ORDINAL = 2;
@@ -94,6 +95,8 @@
public static final String REQUEST_GET_TEST_EVENTS = "get-test-events";
public static final String REQUEST_STOP_EVENT_LOGGING = "stop-event-logging";
public static final String REQUEST_CLEAR_DATA = "clear-data";
+ public static final String REQUEST_IS_TABLET = "is-tablet";
+ public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
public static boolean sDebugTracing = false;
public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index d047eca..816e5dc 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -205,6 +205,16 @@
}
@Override
+ public void setPrimaryScale(View view, float scale) {
+ view.setScaleY(scale);
+ }
+
+ @Override
+ public void setSecondaryScale(View view, float scale) {
+ view.setScaleX(scale);
+ }
+
+ @Override
public int getChildStart(View view) {
return view.getTop();
}
@@ -353,6 +363,25 @@
}
@Override
+ public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
+ SplitPositionOption splitPositionOption, Rect out) {
+ // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
+ // which is the same bounds as 0 rotation.
+ int width = dp.widthPx;
+ out.set(0, 0, width, placeholderHeight);
+ }
+
+ @Override
+ public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+ SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
+ // In fake land/seascape, the window bounds are always top and bottom half
+ int screenHeight = dp.heightPx;
+ int screenWidth = dp.widthPx;
+ out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
+ out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
+ }
+
+ @Override
public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
DeviceProfile deviceProfile) {
return primary;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 266e05f..dae2dde 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -99,6 +99,8 @@
boolean getRecentsRtlSetting(Resources resources);
float getDegreesRotated();
int getRotation();
+ void setPrimaryScale(View view, float scale);
+ void setSecondaryScale(View view, float scale);
<T> T getPrimaryValue(T x, T y);
<T> T getSecondaryValue(T x, T y);
@@ -114,6 +116,22 @@
DeviceProfile deviceProfile);
int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+ /**
+ * @param splitholderSize height of placeholder view in portrait, width in landscape
+ */
+ void getInitialSplitPlaceholderBounds(int splitholderSize, DeviceProfile dp,
+ SplitPositionOption splitPositionOption, Rect out);
+
+ /**
+ * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
+ * @param initialSplitOption the split position option (top/left, bottom/right) of the first
+ * task selected for entering split
+ * @param out1 the bounds for where the first selected app will be
+ * @param out2 the bounds for where the second selected app will be, complimentary to
+ * {@param out1} based on {@param initialSplitOption}
+ */
+ void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+ SplitPositionOption initialSplitOption, Rect out1, Rect out2);
// Overview TaskMenuView methods
float getTaskMenuX(float x, View thumbnailView, int overScroll);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index dd97af5..1253589 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
import android.content.res.Resources;
+import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -47,6 +48,9 @@
public class PortraitPagedViewHandler implements PagedOrientationHandler {
+ private final Matrix mTmpMatrix = new Matrix();
+ private final RectF mTmpRectF = new RectF();
+
@Override
public <T> T getPrimaryValue(T x, T y) {
return x;
@@ -207,6 +211,16 @@
}
@Override
+ public void setPrimaryScale(View view, float scale) {
+ view.setScaleX(scale);
+ }
+
+ @Override
+ public void setSecondaryScale(View view, float scale) {
+ view.setScaleY(scale);
+ }
+
+ @Override
public int getChildStart(View view) {
return view.getLeft();
}
@@ -398,6 +412,62 @@
}
@Override
+ public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
+ SplitPositionOption splitPositionOption, Rect out) {
+ int width = dp.widthPx;
+ out.set(0, 0, width, placeholderHeight);
+ if (!dp.isLandscape) {
+ // portrait, phone or tablet - spans width of screen, nothing else to do
+ return;
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ boolean pinToRight = splitPositionOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
+
+ int screenHeight = dp.heightPx;
+ float postRotateScale = (float) screenHeight / width;
+ mTmpMatrix.reset();
+ mTmpMatrix.postRotate(pinToRight ? 90 : 270);
+ mTmpMatrix.postTranslate(pinToRight ? width : 0, pinToRight ? 0 : width);
+ // The placeholder height stays constant after rotation, so we don't change width scale
+ mTmpMatrix.postScale(1, postRotateScale);
+
+ mTmpRectF.set(out);
+ mTmpMatrix.mapRect(mTmpRectF);
+ mTmpRectF.roundOut(out);
+ }
+
+ @Override
+ public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+ SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
+ int screenHeight = dp.heightPx;
+ int screenWidth = dp.widthPx;
+ out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
+ out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
+ if (!dp.isLandscape) {
+ // Portrait - the window bounds are always top and bottom half
+ return;
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ boolean pinToRight = initialSplitOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
+ float postRotateScale = (float) screenHeight / screenWidth;
+
+ mTmpMatrix.reset();
+ mTmpMatrix.postRotate(pinToRight ? 90 : 270);
+ mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
+ mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
+
+ mTmpRectF.set(out1);
+ mTmpMatrix.mapRect(mTmpRectF);
+ mTmpRectF.roundOut(out1);
+
+ mTmpRectF.set(out2);
+ mTmpMatrix.mapRect(mTmpRectF);
+ mTmpRectF.roundOut(out2);
+ }
+
+ @Override
public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
DeviceProfile dp) {
if (dp.isLandscape) { // or seascape
diff --git a/src/com/android/launcher3/util/FlingAnimation.java b/src/com/android/launcher3/util/FlingAnimation.java
index c9aa51c..ac864e9 100644
--- a/src/com/android/launcher3/util/FlingAnimation.java
+++ b/src/com/android/launcher3/util/FlingAnimation.java
@@ -1,12 +1,14 @@
package com.android.launcher3.util;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
@@ -35,7 +37,7 @@
protected final float mUX, mUY;
protected Rect mIconRect;
- protected Rect mFrom;
+ protected RectF mFrom;
protected int mDuration;
protected float mAnimationTimeFraction;
@@ -55,17 +57,17 @@
@Override
public void run() {
mIconRect = mDropTarget.getIconRect(mDragObject);
+ mDragObject.dragView.cancelAnimation();
+ mDragObject.dragView.requestLayout();
// Initiate from
- mFrom = new Rect();
- mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, mFrom);
- float scale = mDragObject.dragView.getScaleX();
- float xOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredWidth()) / 2f;
- float yOffset = ((scale - 1f) * mDragObject.dragView.getMeasuredHeight()) / 2f;
- mFrom.left += xOffset;
- mFrom.right -= xOffset;
- mFrom.top += yOffset;
- mFrom.bottom -= yOffset;
+ Rect from = new Rect();
+ mDragLayer.getViewRectRelativeToSelf(mDragObject.dragView, from);
+
+ mFrom = new RectF(from);
+ mFrom.inset(
+ ((1 - mDragObject.dragView.getScaleX()) * from.width()) / 2f,
+ ((1 - mDragObject.dragView.getScaleY()) * from.height()) / 2f);
mDuration = Math.abs(mUY) > Math.abs(mUX) ? initFlingUpDuration() : initFlingLeftDuration();
mAnimationTimeFraction = ((float) mDuration) / (mDuration + DRAG_END_DELAY);
@@ -95,17 +97,15 @@
}
};
- Runnable onAnimationEndRunnable = new Runnable() {
- @Override
- public void run() {
- mLauncher.getStateManager().goToState(NORMAL);
- mDropTarget.completeDrop(mDragObject);
- }
- };
-
mDropTarget.onDrop(mDragObject, mDragOptions);
- mDragLayer.animateView(mDragObject.dragView, this, duration, tInterpolator,
- onAnimationEndRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.setDuration(duration).setInterpolator(tInterpolator);
+ anim.addUpdateListener(this);
+ anim.addListener(forEndCallback(() -> {
+ mLauncher.getStateManager().goToState(NORMAL);
+ mDropTarget.completeDrop(mDragObject);
+ }));
+ mDragLayer.playDropAnimation(mDragObject.dragView, anim, DragLayer.ANIMATION_END_DISAPPEAR);
}
/**
@@ -129,7 +129,7 @@
}
double t = (-mUY - Math.sqrt(d)) / mAY;
- float sX = -mFrom.exactCenterX() + mIconRect.exactCenterX();
+ float sX = -mFrom.centerX() + mIconRect.exactCenterX();
// Find horizontal acceleration such that: u*t + a*t*t/2 = s
mAX = (float) ((sX - t * mUX) * 2 / (t * t));
@@ -157,7 +157,7 @@
}
double t = (-mUX - Math.sqrt(d)) / mAX;
- float sY = -mFrom.exactCenterY() + mIconRect.exactCenterY();
+ float sY = -mFrom.centerY() + mIconRect.exactCenterY();
// Find vertical acceleration such that: u*t + a*t*t/2 = s
mAY = (float) ((sY - t * mUY) * 2 / (t * t));
diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java
index 7252f7a..e7235e7 100644
--- a/src/com/android/launcher3/util/IntArray.java
+++ b/src/com/android/launcher3/util/IntArray.java
@@ -17,13 +17,14 @@
package com.android.launcher3.util;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.StringTokenizer;
/**
* Copy of the platform hidden implementation of android.util.IntArray.
* Implements a growing array of int primitives.
*/
-public class IntArray implements Cloneable {
+public class IntArray implements Cloneable, Iterable<Integer> {
private static final int MIN_CAPACITY_INCREMENT = 12;
private static final int[] EMPTY_INT = new int[0];
@@ -272,4 +273,30 @@
throw new ArrayIndexOutOfBoundsException("length=" + len + "; index=" + index);
}
}
+
+ @Override
+ public Iterator<Integer> iterator() {
+ return new ValueIterator();
+ }
+
+ @Thunk
+ class ValueIterator implements Iterator<Integer> {
+
+ private int mNextIndex = 0;
+
+ @Override
+ public boolean hasNext() {
+ return mNextIndex < size();
+ }
+
+ @Override
+ public Integer next() {
+ return get(mNextIndex++);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/IntSet.java b/src/com/android/launcher3/util/IntSet.java
index 851f129..e5b4f59 100644
--- a/src/com/android/launcher3/util/IntSet.java
+++ b/src/com/android/launcher3/util/IntSet.java
@@ -16,11 +16,13 @@
package com.android.launcher3.util;
import java.util.Arrays;
+import java.util.Iterator;
/**
* A wrapper over IntArray implementing a growing set of int primitives.
+ * The elements in the array are sorted in ascending order.
*/
-public class IntSet {
+public class IntSet implements Iterable<Integer> {
final IntArray mArray = new IntArray();
@@ -34,6 +36,16 @@
}
}
+ /**
+ * Removes the specified value from the set if it exist.
+ */
+ public void remove(int value) {
+ int index = Arrays.binarySearch(mArray.mValues, 0, mArray.mSize, value);
+ if (index >= 0) {
+ mArray.removeIndex(index);
+ }
+ }
+
public boolean contains(int value) {
return Arrays.binarySearch(mArray.mValues, 0, mArray.mSize, value) >= 0;
}
@@ -61,6 +73,9 @@
return (obj instanceof IntSet) && ((IntSet) obj).mArray.equals(mArray);
}
+ /**
+ * Returns the wrapped IntArray. The elements in the array are sorted in ascending order.
+ */
public IntArray getArray() {
return mArray;
}
@@ -78,4 +93,30 @@
Arrays.sort(set.mArray.mValues, 0, set.mArray.mSize);
return set;
}
+
+ /**
+ * Returns an IntSet with the given values.
+ */
+ public static IntSet wrap(int... array) {
+ return wrap(IntArray.wrap(array));
+ }
+
+ /**
+ * Returns an IntSet with the given values.
+ */
+ public static IntSet wrap(Iterable<Integer> iterable) {
+ IntSet set = new IntSet();
+ iterable.forEach(set::add);
+ return set;
+ }
+
+ @Override
+ public Iterator<Integer> iterator() {
+ return mArray.iterator();
+ }
+
+ @Override
+ public String toString() {
+ return "IntSet{" + mArray.toConcatString() + '}';
+ }
}
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 354609d..e8ba28f 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -23,6 +23,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
+import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -89,4 +90,13 @@
static ItemInfoMatcher ofItemIds(IntSet ids) {
return (info, cn) -> ids.contains(info.id);
}
+
+ /**
+ * Returns a matcher for items with provided items
+ */
+ static ItemInfoMatcher ofItems(Collection<? extends ItemInfo> items) {
+ IntSet ids = new IntSet();
+ items.forEach(item -> ids.add(item.id));
+ return ofItemIds(ids);
+ }
}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
new file mode 100644
index 0000000..a4cb30a
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 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.util;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
+
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Interface representing a container which can bind Launcher items with some utility methods
+ */
+public interface LauncherBindableItemsContainer {
+
+ /**
+ * Called to update workspace items as a result of
+ * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)}
+ */
+ default void updateWorkspaceItems(List<WorkspaceItemInfo> shortcuts, ActivityContext context) {
+ final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
+ ItemOperator op = (info, v) -> {
+ if (v instanceof BubbleTextView && updates.contains(info)) {
+ WorkspaceItemInfo si = (WorkspaceItemInfo) info;
+ BubbleTextView shortcut = (BubbleTextView) v;
+ Drawable oldIcon = shortcut.getIcon();
+ boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+ && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
+ shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
+ } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
+ ((FolderIcon) v).updatePreviewItems(updates::contains);
+ }
+
+ // Iterate all items
+ return false;
+ };
+
+ mapOverItems(op);
+ Folder openFolder = Folder.getOpen(context);
+ if (openFolder != null) {
+ openFolder.iterateOverItems(op);
+ }
+ }
+
+ /**
+ * Called to update restored items as a result of
+ * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}}
+ */
+ default void updateRestoreItems(final HashSet<ItemInfo> updates, ActivityContext context) {
+ ItemOperator op = (info, v) -> {
+ if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
+ && updates.contains(info)) {
+ ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */);
+ } else if (v instanceof PendingAppWidgetHostView
+ && info instanceof LauncherAppWidgetInfo
+ && updates.contains(info)) {
+ ((PendingAppWidgetHostView) v).applyState();
+ } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
+ ((FolderIcon) v).updatePreviewItems(updates::contains);
+ }
+ // process all the shortcuts
+ return false;
+ };
+
+ mapOverItems(op);
+ Folder folder = Folder.getOpen(context);
+ if (folder != null) {
+ folder.iterateOverItems(op);
+ }
+ }
+
+ /**
+ * Map the operator over the shortcuts and widgets.
+ *
+ * @param op the operator to map over the shortcuts
+ */
+ void mapOverItems(ItemOperator op);
+
+ interface ItemOperator {
+ /**
+ * Process the next itemInfo, possibly with side-effect on the next item.
+ *
+ * @param info info for the shortcut
+ * @param view view for the shortcut
+ * @return true if done, false to continue the map
+ */
+ boolean evaluate(ItemInfo info, View view);
+ }
+}
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 5be9529..8591872 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -16,6 +16,8 @@
package com.android.launcher3.util;
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
import android.util.FloatProperty;
import android.view.View;
@@ -121,5 +123,12 @@
public String toString() {
return Float.toString(mValue);
}
+
+ /**
+ * Creates and returns an Animator from the current value to the given value.
+ */
+ public Animator animateToValue(float value) {
+ return ObjectAnimator.ofFloat(this, VALUE, value);
+ }
}
}
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 82e24c2..5d90291 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,28 +16,21 @@
package com.android.launcher3.util;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.os.Process;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewTreeObserver.OnDrawListener;
-import androidx.annotation.VisibleForTesting;
-
import com.android.launcher3.Launcher;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* An executor which runs all the tasks after the first onDraw is called on the target view.
*/
-public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable,
+public class ViewOnDrawExecutor implements OnDrawListener, Runnable,
OnAttachStateChangeListener {
- private final ArrayList<Runnable> mTasks = new ArrayList<>();
+ private final RunnableList mTasks;
private Consumer<ViewOnDrawExecutor> mOnClearCallback;
private View mAttachedView;
@@ -46,22 +39,16 @@
private boolean mLoadAnimationCompleted;
private boolean mFirstDrawCompleted;
- public void attachTo(Launcher launcher) {
- attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
- launcher::clearPendingExecutor);
+ private boolean mCancelled;
+
+ public ViewOnDrawExecutor(RunnableList tasks) {
+ mTasks = tasks;
}
- /**
- * Attached the executor to the existence of the view
- */
- public void attachTo(View attachedView, boolean waitForLoadAnimation,
- Consumer<ViewOnDrawExecutor> onClearCallback) {
- mOnClearCallback = onClearCallback;
- mAttachedView = attachedView;
+ public void attachTo(Launcher launcher) {
+ mOnClearCallback = launcher::clearPendingExecutor;
+ mAttachedView = launcher.getWorkspace();
mAttachedView.addOnAttachStateChangeListener(this);
- if (!waitForLoadAnimation) {
- mLoadAnimationCompleted = true;
- }
if (mAttachedView.isAttachedToWindow()) {
attachObserver();
@@ -75,12 +62,6 @@
}
@Override
- public void execute(Runnable command) {
- mTasks.add(command);
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- }
-
- @Override
public void onViewAttachedToWindow(View v) {
attachObserver();
}
@@ -105,12 +86,17 @@
public void run() {
// Post the pending tasks after both onDraw and onLoadAnimationCompleted have been called.
if (mLoadAnimationCompleted && mFirstDrawCompleted && !mCompleted) {
- runAllTasks();
+ markCompleted();
}
}
+ /**
+ * Executes all tasks immediately
+ */
public void markCompleted() {
- mTasks.clear();
+ if (!mCancelled) {
+ mTasks.executeAllAndDestroy();
+ }
mCompleted = true;
if (mAttachedView != null) {
mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
@@ -119,21 +105,10 @@
if (mOnClearCallback != null) {
mOnClearCallback.accept(this);
}
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
- protected boolean isCompleted() {
- return mCompleted;
- }
-
- /**
- * Executes all tasks immediately
- */
- @VisibleForTesting
- public void runAllTasks() {
- for (final Runnable r : mTasks) {
- r.run();
- }
+ public void cancel() {
+ mCancelled = true;
markCompleted();
}
}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 646b669..b95904e 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -21,9 +21,12 @@
import android.view.LayoutInflater;
import android.view.View.AccessibilityDelegate;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ViewCache;
@@ -100,6 +103,13 @@
}
/**
+ * Returns the FolderIcon with the given item id, if it exists.
+ */
+ default @Nullable FolderIcon findFolderIcon(final int folderIconId) {
+ return null;
+ }
+
+ /**
* Returns the ActivityContext associated with the given Context.
*/
static <T extends Context & ActivityContext> T lookupContext(Context context) {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 01c0b56..76dfb3c 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -430,18 +430,20 @@
}
public void getViewRectRelativeToSelf(View v, Rect r) {
+ int[] loc = getViewLocationRelativeToSelf(v);
+ r.set(loc[0], loc[1], loc[0] + v.getMeasuredWidth(), loc[1] + v.getMeasuredHeight());
+ }
+
+ protected int[] getViewLocationRelativeToSelf(View v) {
int[] loc = new int[2];
getLocationInWindow(loc);
int x = loc[0];
int y = loc[1];
v.getLocationInWindow(loc);
- int vX = loc[0];
- int vY = loc[1];
-
- int left = vX - x;
- int top = vY - y;
- r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight());
+ loc[0] -= x;
+ loc[1] -= y;
+ return loc;
}
@Override
diff --git a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
index 1cc7f53..b92c476 100644
--- a/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/AddItemWidgetsBottomSheet.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Utilities.ATLEAST_R;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.widget.BaseWidgetSheet.MAX_WIDTH_SCALE_FOR_LARGER_SCREEN;
import android.animation.PropertyValuesHolder;
import android.annotation.SuppressLint;
@@ -25,10 +26,12 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowInsets;
+import android.widget.ScrollView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -44,6 +47,7 @@
private static final int DEFAULT_CLOSE_DURATION = 200;
private final Rect mInsets;
+ private ScrollView mWidgetPreviewScrollView;
public AddItemWidgetsBottomSheet(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -68,6 +72,19 @@
}
@Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = false;
+ // Suppress drag to dismiss gesture if the scroll view is being scrolled.
+ if (getPopupContainer().isEventOverView(mWidgetPreviewScrollView, ev)
+ && mWidgetPreviewScrollView.getScrollY() > 0) {
+ mNoIntercept = true;
+ }
+ }
+ return super.onControllerInterceptTouchEvent(ev);
+ }
+
+ @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
@@ -93,6 +110,15 @@
2 * (mInsets.left + mInsets.right));
}
+ if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
+ // In large screen devices, we restrict the width of the widgets picker to show part of
+ // the home screen. Let's ensure the minimum width used is at least the minimum width
+ // that isn't taken by the widgets picker.
+ int minUsedWidth = (int) (deviceProfile.availableWidthPx
+ * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
+ widthUsed = Math.max(widthUsed, minUsedWidth);
+ }
+
int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
measureChildWithMargins(mContent, widthMeasureSpec,
widthUsed, heightMeasureSpec, heightUsed);
@@ -104,6 +130,7 @@
protected void onFinishInflate() {
super.onFinishInflate();
mContent = findViewById(R.id.add_item_bottom_sheet_content);
+ mWidgetPreviewScrollView = findViewById(R.id.widget_preview_scroll_view);
}
private void animateOpen() {
diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
new file mode 100644
index 0000000..2742882
--- /dev/null
+++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 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.widget;
+
+import android.content.Context;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.RemoteViews;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.Executors;
+
+/**
+ * Launcher AppWidgetHostView with support for rounded corners and a fallback View.
+ */
+public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHostView {
+
+ protected final LayoutInflater mInflater;
+
+ private final Rect mEnforcedRectangle = new Rect();
+ private final float mEnforcedCornerRadius;
+ private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
+ outline.setEmpty();
+ } else {
+ outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
+ }
+ }
+ };
+
+ public BaseLauncherAppWidgetHostView(Context context) {
+ super(context);
+
+ setExecutor(Executors.THREAD_POOL_EXECUTOR);
+
+ mInflater = LayoutInflater.from(context);
+ mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
+ }
+
+ @Override
+ protected View getErrorView() {
+ return mInflater.inflate(R.layout.appwidget_error, this, false);
+ }
+
+ /**
+ * Fall back to error layout instead of showing widget.
+ */
+ public void switchToErrorView() {
+ // Update the widget with 0 Layout id, to reset the view to error view.
+ updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ try {
+ super.onLayout(changed, left, top, right, bottom);
+ } catch (final RuntimeException e) {
+ post(this::switchToErrorView);
+ }
+
+ enforceRoundedCorners();
+ }
+
+ @UiThread
+ private void resetRoundedCorners() {
+ setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+ setClipToOutline(false);
+ }
+
+ @UiThread
+ private void enforceRoundedCorners() {
+ if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+ resetRoundedCorners();
+ return;
+ }
+ View background = RoundedCornerEnforcement.findBackground(this);
+ if (background == null
+ || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+ resetRoundedCorners();
+ return;
+ }
+ RoundedCornerEnforcement.computeRoundedRectangle(this,
+ background,
+ mEnforcedRectangle);
+ setOutlineProvider(mCornerRadiusEnforcementOutline);
+ setClipToOutline(true);
+ }
+
+ /** Returns the corner radius currently enforced, in pixels. */
+ public float getEnforcedCornerRadius() {
+ return mEnforcedCornerRadius;
+ }
+
+ /** Returns true if the corner radius are enforced for this App Widget. */
+ public boolean hasEnforcedCornerRadius() {
+ return getClipToOutline();
+ }
+}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 9f0b9d9..8e9769c 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -51,6 +51,13 @@
public abstract class BaseWidgetSheet extends AbstractSlideInView<Launcher>
implements OnClickListener, OnLongClickListener, DragSource,
PopupDataProvider.PopupDataChangeListener, Insettable {
+ /** The default number of cells that can fit horizontally in a widget sheet. */
+ protected static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
+ /**
+ * The maximum scale, [0, 1], of the device screen width that the widgets picker can consume
+ * on large screen devices.
+ */
+ protected static final float MAX_WIDTH_SCALE_FOR_LARGER_SCREEN = 0.89f;
protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
"launcher.widgets_education_tip_seen";
@@ -131,6 +138,15 @@
2 * (mInsets.left + mInsets.right));
}
+ if (deviceProfile.isTablet || deviceProfile.isTwoPanels) {
+ // In large screen devices, we restrict the width of the widgets picker to show part of
+ // the home screen. Let's ensure the minimum width used is at least the minimum width
+ // that isn't taken by the widgets picker.
+ int minUsedWidth = (int) (deviceProfile.availableWidthPx
+ * (1 - MAX_WIDTH_SCALE_FOR_LARGER_SCREEN));
+ widthUsed = Math.max(widthUsed, minUsedWidth);
+ }
+
int heightUsed = mInsets.top + deviceProfile.edgeMarginPx;
measureChildWithMargins(mContent, widthMeasureSpec,
widthUsed, heightMeasureSpec, heightUsed);
@@ -138,6 +154,17 @@
MeasureSpec.getSize(heightMeasureSpec));
}
+ /** Returns the number of cells that can fit horizontally in a given {@code content}. */
+ protected int computeMaxHorizontalSpans(View content, int contentHorizontalPaddingPx) {
+ DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+ int availableWidth = content.getMeasuredWidth() - contentHorizontalPaddingPx;
+ Point cellSize = deviceProfile.getCellSize();
+ if (cellSize.x > 0) {
+ return availableWidth / cellSize.x;
+ }
+ return DEFAULT_MAX_HORIZONTAL_SPANS;
+ }
+
private boolean beginDraggingWidget(WidgetCell v) {
// Get the widget preview as the drag representation
WidgetImageView image = v.getWidgetView();
@@ -149,7 +176,9 @@
}
PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
- dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview());
+ // RemoteViews are being rendered in AppWidgetHostView in WidgetCell. And thus, the scale of
+ // RemoteViews is equivalent to the AppWidgetHostView scale.
+ dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview(), v.getAppWidgetHostViewScale());
dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
if (image.getDrawable() != null) {
@@ -159,11 +188,11 @@
dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
} else {
- View preview = v.getAppWidgetHostViewPreview();
+ NavigableAppWidgetHostView preview = v.getAppWidgetHostViewPreview();
int[] loc = new int[2];
getPopupContainer().getLocationInDragLayer(preview, loc);
-
- Rect r = new Rect(0, 0, preview.getWidth(), preview.getHeight());
+ Rect r = new Rect();
+ preview.getWorkspaceVisualDragBounds(r);
dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(),
new Point(loc[0], loc[1]), this, new DragOptions());
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 63bc416..0313e68 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -20,19 +20,16 @@
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
-import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Handler;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.AdapterView;
import android.widget.Advanceable;
@@ -40,7 +37,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.Launcher;
@@ -51,7 +47,6 @@
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener;
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
@@ -61,7 +56,7 @@
/**
* {@inheritDoc}
*/
-public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView
+public class LauncherAppWidgetHostView extends BaseLauncherAppWidgetHostView
implements TouchCompleteListener, View.OnLongClickListener,
LocalColorExtractor.Listener {
@@ -76,8 +71,6 @@
// Maximum duration for which updates can be deferred.
private static final long UPDATE_LOCK_TIMEOUT_MILLIS = 1000;
- protected final LayoutInflater mInflater;
-
private final CheckLongPressHelper mLongPressHelper;
protected final Launcher mLauncher;
private final Workspace mWorkspace;
@@ -91,28 +84,14 @@
private boolean mIsScrollable;
private boolean mIsAttachedToWindow;
private boolean mIsAutoAdvanceRegistered;
- private boolean mIsInDragMode = false;
private Runnable mAutoAdvanceRunnable;
private RectF mLastLocationRegistered = null;
- @Nullable private AppWidgetHostViewDragListener mDragListener;
// Used to store the widget sizes in drag layer coordinates.
private final Rect mCurrentWidgetSize = new Rect();
private final Rect mWidgetSizeAtDrag = new Rect();
private final RectF mTempRectF = new RectF();
- private final Rect mEnforcedRectangle = new Rect();
- private final float mEnforcedCornerRadius;
- private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
- outline.setEmpty();
- } else {
- outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
- }
- }
- };
private final Object mUpdateLock = new Object();
private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper;
private long mDeferUpdatesUntilMillis = 0;
@@ -121,23 +100,28 @@
private @Nullable SparseIntArray mDeferredColorChange = null;
private boolean mEnableColorExtraction = true;
+ // The following member variables are only used during drag-n-drop.
+ private boolean mIsInDragMode = false;
+ @Nullable private AppWidgetHostViewDragListener mDragListener;
+ /** The drag content width which is only set when the drag content scale is not 1f. */
+ private int mDragContentWidth = 0;
+ /** The drag content height which is only set when the drag content scale is not 1f. */
+ private int mDragContentHeight = 0;
+
public LauncherAppWidgetHostView(Context context) {
super(context);
mLauncher = Launcher.getLauncher(context);
mWorkspace = mLauncher.getWorkspace();
mLongPressHelper = new CheckLongPressHelper(this, this);
- mInflater = LayoutInflater.from(context);
setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
setBackgroundResource(R.drawable.widget_internal_focus_bg);
- setExecutor(Executors.THREAD_POOL_EXECUTOR);
if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
setOnLightBackground(true);
}
mColorExtractor = LocalColorExtractor.newInstance(getContext());
mColorExtractor.setListener(this);
- mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer());
}
@@ -169,11 +153,6 @@
}
@Override
- protected View getErrorView() {
- return mInflater.inflate(R.layout.appwidget_error, this, false);
- }
-
- @Override
public void updateAppWidget(RemoteViews remoteViews) {
synchronized (mUpdateLock) {
if (isDeferringUpdates()) {
@@ -329,34 +308,36 @@
}
}
- public void switchToErrorView() {
- // Update the widget with 0 Layout id, to reset the view to error view.
- updateAppWidget(new RemoteViews(getAppWidgetInfo().provider.getPackageName(), 0));
- }
-
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- try {
- super.onLayout(changed, left, top, right, bottom);
- } catch (final RuntimeException e) {
- post(new Runnable() {
- @Override
- public void run() {
- switchToErrorView();
- }
- });
- }
+ super.onLayout(changed, left, top, right, bottom);
mIsScrollable = checkScrollableRecursively(this);
updateColorExtraction();
+ }
- enforceRoundedCorners();
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mIsInDragMode && mDragContentWidth > 0 && mDragContentHeight > 0
+ && getChildCount() == 1) {
+ measureChild(getChildAt(0), MeasureSpec.getSize(mDragContentWidth),
+ MeasureSpec.getSize(mDragContentHeight));
+ }
}
/** Starts the drag mode. */
public void startDrag(AppWidgetHostViewDragListener dragListener) {
mIsInDragMode = true;
mDragListener = dragListener;
+ // In the case of dragging a scaled preview from widgets picker, we should reuse the
+ // previously measured dimension from WidgetCell#measureAndComputeWidgetPreviewScale, which
+ // measures the dimension of a widget preview without its parent's bound before scaling
+ // down.
+ if ((getScaleX() != 1f || getScaleY() != 1f) && getChildCount() == 1) {
+ mDragContentWidth = getChildAt(0).getMeasuredWidth();
+ mDragContentHeight = getChildAt(0).getMeasuredHeight();
+ }
}
/** Handles a drag event occurred on a workspace page, {@code pageId}. */
@@ -369,6 +350,8 @@
public void endDrag() {
mIsInDragMode = false;
mDragListener = null;
+ mDragContentWidth = 0;
+ mDragContentHeight = 0;
mWidgetSizeAtDrag.setEmpty();
}
@@ -564,40 +547,4 @@
}
return false;
}
-
- @UiThread
- private void resetRoundedCorners() {
- setOutlineProvider(ViewOutlineProvider.BACKGROUND);
- setClipToOutline(false);
- }
-
- @UiThread
- private void enforceRoundedCorners() {
- if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
- resetRoundedCorners();
- return;
- }
- View background = RoundedCornerEnforcement.findBackground(this);
- if (background == null
- || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
- resetRoundedCorners();
- return;
- }
- RoundedCornerEnforcement.computeRoundedRectangle(this,
- background,
- mEnforcedRectangle);
- setOutlineProvider(mCornerRadiusEnforcementOutline);
- setClipToOutline(true);
- }
-
- /** Returns the corner radius currently enforced, in pixels. */
- public float getEnforcedCornerRadius() {
- return mEnforcedCornerRadius;
- }
-
- /** Returns true if the corner radius are enforced for this App Widget. */
- public boolean hasEnforcedCornerRadius() {
- return getClipToOutline();
- }
-
}
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 6163b51..d12fe74 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -26,7 +26,6 @@
import android.view.ViewDebug;
import android.view.ViewGroup;
-import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
@@ -59,7 +58,7 @@
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mChildrenFocused;
- protected final BaseActivity mActivity;
+ protected final ActivityContext mActivity;
public NavigableAppWidgetHostView(Context context) {
super(context);
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index cea4de7..a4003d4 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -22,6 +22,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.util.Size;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.RemoteViews;
@@ -41,6 +42,7 @@
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.RoundDrawableWrapper;
import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
+import com.android.launcher3.widget.util.WidgetSizes;
/**
* Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts
@@ -54,6 +56,7 @@
private int[] mEstimatedCellSize;
@Nullable private RemoteViews mRemoteViewsPreview;
+ private float mRemoteViewsPreviewScale = 1f;
@Nullable private NavigableAppWidgetHostView mAppWidgetHostViewPreview;
private final float mEnforcedRoundedCornersForWidget;
@@ -68,8 +71,10 @@
* Sets a {@link RemoteViews} which shows an app widget preview provided by app developers in
* the pin widget flow.
*/
- public void setRemoteViewsPreview(@Nullable RemoteViews remoteViewsPreview) {
+ public void setRemoteViewsPreview(@Nullable RemoteViews remoteViewsPreview,
+ float previewScale) {
mRemoteViewsPreview = remoteViewsPreview;
+ mRemoteViewsPreviewScale = previewScale;
}
/** Sets a {@link NavigableAppWidgetHostView} which shows a preview layout of an app widget. */
@@ -120,13 +125,14 @@
mAppWidgetHostViewPreview.setPadding(padding.left, padding.top, padding.right,
padding.bottom);
mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ mRemoteViewsPreview);
- int width =
- deviceProfile.cellWidthPx * mAddInfo.spanX + padding.left + padding.right;
- int height =
- deviceProfile.cellHeightPx * mAddInfo.spanY + padding.top + padding.bottom;
+ Size widgetSizes = WidgetSizes.getWidgetPaddedSizePx(launcher,
+ mAddInfo.componentName, deviceProfile, mAddInfo.spanX, mAddInfo.spanY);
mAppWidgetHostViewPreview.measure(
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(widgetSizes.getWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(widgetSizes.getHeight(), MeasureSpec.EXACTLY));
+ mAppWidgetHostViewPreview.setClipChildren(false);
+ mAppWidgetHostViewPreview.setClipToPadding(false);
+ mAppWidgetHostViewPreview.setScaleToFit(mRemoteViewsPreviewScale);
}
if (mAppWidgetHostViewPreview != null) {
previewSizeBeforeScale[0] = mAppWidgetHostViewPreview.getMeasuredWidth();
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 5769ba0..167eb09 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -16,12 +16,15 @@
package com.android.launcher3.widget;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
import static com.android.launcher3.Utilities.ATLEAST_S;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.CancellationSignal;
import android.util.AttributeSet;
@@ -31,6 +34,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -72,11 +76,36 @@
/** Widget preview width is calculated by multiplying this factor to the widget cell width. */
private static final float PREVIEW_SCALE = 0.8f;
- protected int mPreviewWidth;
- protected int mPreviewHeight;
+ /**
+ * The maximum dimension that can be used as the size in
+ * {@link android.view.View.MeasureSpec#makeMeasureSpec(int, int)}.
+ *
+ * <p>This is equal to (1 << MeasureSpec.MODE_SHIFT) - 1.
+ */
+ private static final int MAX_MEASURE_SPEC_DIMENSION = (1 << 30) - 1;
+
+ /**
+ * The target preview width, in pixels, of a widget or a shortcut.
+ *
+ * <p>The actual preview width may be smaller than or equal to this value subjected to scaling.
+ */
+ protected int mTargetPreviewWidth;
+
+ /**
+ * The target preview height, in pixels, of a widget or a shortcut.
+ *
+ * <p>The actual preview height may be smaller than or equal to this value subjected to scaling.
+ */
+ protected int mTargetPreviewHeight;
+
protected int mPresetPreviewSize;
+
private int mCellSize;
- private float mPreviewScale = 1f;
+
+ /**
+ * The scale of the preview container.
+ */
+ private float mPreviewContainerScale = 1f;
private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
@@ -97,10 +126,10 @@
protected final BaseActivity mActivity;
private final CheckLongPressHelper mLongPressHelper;
private final float mEnforcedCornerRadius;
- private final int mShortcutPreviewPadding;
private RemoteViews mRemoteViewsPreview;
private NavigableAppWidgetHostView mAppWidgetHostViewPreview;
+ private float mAppWidgetHostViewScale = 1f;
private int mSourceContainer = CONTAINER_WIDGETS_TRAY;
public WidgetCell(Context context) {
@@ -123,14 +152,12 @@
setClipToPadding(false);
setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
- mShortcutPreviewPadding =
- 2 * getResources().getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding);
}
private void setContainerWidth() {
mCellSize = (int) (mActivity.getDeviceProfile().allAppsIconSizePx * WIDTH_SCALE);
mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
- mPreviewWidth = mPreviewHeight = mPresetPreviewSize;
+ mTargetPreviewWidth = mTargetPreviewHeight = mPresetPreviewSize;
}
@Override
@@ -153,6 +180,11 @@
return mRemoteViewsPreview;
}
+ /** Returns the app widget host view scale, which is a value between [0f, 1f]. */
+ public float getAppWidgetHostViewScale() {
+ return mAppWidgetHostViewScale;
+ }
+
/**
* Called to clear the view and free attached resources. (e.g., {@link Bitmap}
*/
@@ -167,7 +199,7 @@
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
mWidgetDescription.setVisibility(GONE);
- mPreviewWidth = mPreviewHeight = mPresetPreviewSize;
+ mTargetPreviewWidth = mTargetPreviewHeight = mPresetPreviewSize;
if (mActiveRequest != null) {
mActiveRequest.cancel();
@@ -178,6 +210,7 @@
mWidgetImageContainer.removeView(mAppWidgetHostViewPreview);
}
mAppWidgetHostViewPreview = null;
+ mAppWidgetHostViewScale = 1f;
mItem = null;
}
@@ -249,16 +282,6 @@
@Nullable RemoteViews remoteViews) {
appWidgetHostViewPreview.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
appWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1, providerInfo);
- Rect padding;
- DeviceProfile deviceProfile = mActivity.getDeviceProfile();
- if (deviceProfile.shouldInsetWidgets()) {
- padding = new Rect();
- appWidgetHostViewPreview.getWidgetInset(deviceProfile, padding);
- } else {
- padding = deviceProfile.inv.defaultWidgetPadding;
- }
- appWidgetHostViewPreview.setPadding(padding.left, padding.top, padding.right,
- padding.bottom);
appWidgetHostViewPreview.updateAppWidget(remoteViews);
}
@@ -306,12 +329,12 @@
if (getWidth() > 0 && getHeight() > 0) {
// Scale down the preview size if it's wider than the cell.
float maxWidth = getWidth();
- float previewWidth = drawable.getIntrinsicWidth() * mPreviewScale;
+ float previewWidth = drawable.getIntrinsicWidth() * mPreviewContainerScale;
scale = Math.min(maxWidth / previewWidth, 1);
}
setContainerSize(
- Math.round(drawable.getIntrinsicWidth() * scale),
- Math.round(drawable.getIntrinsicHeight() * scale));
+ Math.round(drawable.getIntrinsicWidth() * scale * mPreviewContainerScale),
+ Math.round(drawable.getIntrinsicHeight() * scale * mPreviewContainerScale));
mWidgetImage.setDrawable(drawable);
mWidgetImage.setVisibility(View.VISIBLE);
if (mAppWidgetHostViewPreview != null) {
@@ -330,16 +353,32 @@
private void setContainerSize(int width, int height) {
LayoutParams layoutParams = (LayoutParams) mWidgetImageContainer.getLayoutParams();
- layoutParams.width = (int) (width * mPreviewScale);
- layoutParams.height = (int) (height * mPreviewScale);
+ layoutParams.width = width;
+ layoutParams.height = height;
mWidgetImageContainer.setLayoutParams(layoutParams);
}
public void ensurePreview() {
if (mAppWidgetHostViewPreview != null) {
- setContainerSize(mPreviewWidth, mPreviewHeight);
+ int containerWidth = (int) (mTargetPreviewWidth * mPreviewContainerScale);
+ int containerHeight = (int) (mTargetPreviewHeight * mPreviewContainerScale);
+ setContainerSize(containerWidth, containerHeight);
+ if (mAppWidgetHostViewPreview.getChildCount() == 1) {
+ View widgetContent = mAppWidgetHostViewPreview.getChildAt(0);
+ ViewGroup.LayoutParams layoutParams = widgetContent.getLayoutParams();
+ // We only scale preview if both the width & height of the outermost view group are
+ // not set to MATCH_PARENT.
+ boolean shouldScale =
+ layoutParams.width != MATCH_PARENT && layoutParams.height != MATCH_PARENT;
+ if (shouldScale) {
+ setNoClip(mWidgetImageContainer);
+ setNoClip(mAppWidgetHostViewPreview);
+ mAppWidgetHostViewScale = measureAndComputeWidgetPreviewScale();
+ mAppWidgetHostViewPreview.setScaleToFit(mAppWidgetHostViewScale);
+ }
+ }
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
- mPreviewWidth, mPreviewHeight, Gravity.FILL);
+ containerWidth, containerHeight, Gravity.FILL);
mAppWidgetHostViewPreview.setLayoutParams(params);
mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
mWidgetImage.setVisibility(View.GONE);
@@ -351,7 +390,7 @@
}
mActiveRequest = mWidgetPreviewLoader.loadPreview(
BaseActivity.fromContext(getContext()), mItem,
- new Size(mPreviewWidth, mPreviewHeight),
+ new Size(mTargetPreviewWidth, mTargetPreviewHeight),
this::applyPreview);
}
@@ -364,9 +403,9 @@
public Size setPreviewSize(WidgetItem widgetItem, float previewScale) {
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile, widgetItem);
- mPreviewWidth = widgetSize.getWidth();
- mPreviewHeight = widgetSize.getHeight();
- mPreviewScale = previewScale;
+ mTargetPreviewWidth = widgetSize.getWidth();
+ mTargetPreviewHeight = widgetSize.getHeight();
+ mPreviewContainerScale = previewScale;
return widgetSize;
}
@@ -429,4 +468,48 @@
super.onInitializeAccessibilityNodeInfo(info);
info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
}
+
+ private static void setNoClip(ViewGroup view) {
+ view.setClipChildren(false);
+ view.setClipToPadding(false);
+ }
+
+ private float measureAndComputeWidgetPreviewScale() {
+ if (mAppWidgetHostViewPreview.getChildCount() != 1) {
+ return 1f;
+ }
+
+ // Measure the largest possible width & height that the app widget wants to display.
+ mAppWidgetHostViewPreview.measure(
+ makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED),
+ makeMeasureSpec(MAX_MEASURE_SPEC_DIMENSION, MeasureSpec.UNSPECIFIED));
+ View widgetContent = mAppWidgetHostViewPreview.getChildAt(0);
+ int appWidgetContentWidth = widgetContent.getMeasuredWidth();
+ int appWidgetContentHeight = widgetContent.getMeasuredHeight();
+ if (appWidgetContentWidth == 0 || appWidgetContentHeight == 0) {
+ return 1f;
+ }
+
+ // If the width / height of the widget content is set to wrap content, overrides the width /
+ // height with the measured dimension. This avoids incorrect measurement after scaling.
+ FrameLayout.LayoutParams layoutParam =
+ (FrameLayout.LayoutParams) widgetContent.getLayoutParams();
+ if (layoutParam.width == WRAP_CONTENT) {
+ layoutParam.width = widgetContent.getMeasuredWidth();
+ }
+ if (layoutParam.height == WRAP_CONTENT) {
+ layoutParam.height = widgetContent.getMeasuredHeight();
+ }
+ widgetContent.setLayoutParams(layoutParam);
+
+ int horizontalPadding = mAppWidgetHostViewPreview.getPaddingStart()
+ + mAppWidgetHostViewPreview.getPaddingEnd();
+ int verticalPadding = mAppWidgetHostViewPreview.getPaddingTop()
+ + mAppWidgetHostViewPreview.getPaddingBottom();
+ return Math.min(
+ (mTargetPreviewWidth - horizontalPadding) * mPreviewContainerScale
+ / appWidgetContentWidth,
+ (mTargetPreviewHeight - verticalPadding) * mPreviewContainerScale
+ / appWidgetContentHeight);
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index dedcc65..406de10 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -70,9 +70,11 @@
private static final int DEFAULT_CLOSE_DURATION = 200;
private static final long EDUCATION_TIP_DELAY_MS = 300;
+ private final int mWidgetSheetContentHorizontalPadding;
+
private ItemInfo mOriginalItemInfo;
private final int mMaxTableHeight;
- private int mMaxHorizontalSpan = 4;
+ private int mMaxHorizontalSpan = DEFAULT_MAX_HORIZONTAL_SPANS;
private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
new OnLayoutChangeListener() {
@@ -117,6 +119,9 @@
if (!hasSeenEducationTip()) {
addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
}
+
+ mWidgetSheetContentHorizontalPadding = getResources().getDimensionPixelSize(
+ R.dimen.widget_list_horizontal_margin);
}
@Override
@@ -137,10 +142,8 @@
private boolean updateMaxSpansPerRow() {
if (getMeasuredWidth() == 0) return false;
- int paddingPx = 2 * getResources().getDimensionPixelOffset(
- R.dimen.widget_cell_horizontal_padding);
- int maxHorizontalSpan = findViewById(R.id.widgets_table).getMeasuredWidth()
- / (mActivityContext.getDeviceProfile().cellWidthPx + paddingPx);
+ int maxHorizontalSpan = computeMaxHorizontalSpans(mContent,
+ mWidgetSheetContentHorizontalPadding);
if (mMaxHorizontalSpan != maxHorizontalSpan) {
// Ensure the table layout is showing widgets in the right column after measure.
mMaxHorizontalSpan = maxHorizontalSpan;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index f2fee0a..e4ce27c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Process;
import android.os.UserHandle;
@@ -150,13 +151,13 @@
private final int mTabsHeight;
private final int mViewPagerTopPadding;
private final int mSearchAndRecommendationContainerBottomMargin;
- private final int mWidgetCellHorizontalPadding;
+ private final int mWidgetSheetContentHorizontalPadding;
@Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
@Nullable private PersonalWorkPagedView mViewPager;
private boolean mIsInSearchMode;
private boolean mIsNoWidgetsViewNeeded;
- private int mMaxSpansPerRow = 4;
+ private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
private View mTabsView;
private TextView mNoWidgetsView;
private SearchAndRecommendationViewHolder mSearchAndRecommendationViewHolder;
@@ -168,19 +169,20 @@
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
+
+ Resources resources = getResources();
mTabsHeight = mHasWorkProfile
- ? getContext().getResources()
- .getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
+ ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
: 0;
mViewPagerTopPadding = mHasWorkProfile
? getContext().getResources()
.getDimensionPixelSize(R.dimen.widget_picker_view_pager_top_padding)
: 0;
- mSearchAndRecommendationContainerBottomMargin = getContext().getResources()
- .getDimensionPixelSize(mHasWorkProfile
+ mSearchAndRecommendationContainerBottomMargin = resources.getDimensionPixelSize(
+ mHasWorkProfile
? R.dimen.search_and_recommended_widgets_container_small_bottom_margin
: R.dimen.search_and_recommended_widgets_container_bottom_margin);
- mWidgetCellHorizontalPadding = 2 * getResources().getDimensionPixelOffset(
+ mWidgetSheetContentHorizontalPadding = 2 * resources.getDimensionPixelSize(
R.dimen.widget_cell_horizontal_padding);
}
@@ -377,11 +379,10 @@
private boolean updateMaxSpansPerRow() {
if (getMeasuredWidth() == 0) return false;
- int previousMaxSpansPerRow = mMaxSpansPerRow;
- mMaxSpansPerRow = getMeasuredWidth()
- / (mActivityContext.getDeviceProfile().cellWidthPx + mWidgetCellHorizontalPadding);
-
- if (previousMaxSpansPerRow != mMaxSpansPerRow) {
+ int maxHorizontalSpans = computeMaxHorizontalSpans(mContent,
+ mWidgetSheetContentHorizontalPadding);
+ if (mMaxSpansPerRow != maxHorizontalSpans) {
+ mMaxSpansPerRow = maxHorizontalSpans;
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
mMaxSpansPerRow);
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index 4407fe1..cc90e6c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,6 +17,7 @@
package com.android.launcher3.uioverrides;
import android.app.Person;
+import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.view.Display;
@@ -36,4 +37,11 @@
public static boolean isInternalDisplay(Display display) {
return display.getDisplayId() == Display.DEFAULT_DISPLAY;
}
+
+ /**
+ * Returns the minimum space that should be left empty at the end of hotseat
+ */
+ public static int getHotseatEndOffset(Context context) {
+ return 0;
+ }
}
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index 33066e4..032a7b4 100644
--- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -25,8 +25,8 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import org.junit.After;
import org.junit.Test;
@@ -75,7 +75,7 @@
@Test
public void testPromiseIcon_addedFromEligibleSession() throws Throwable {
final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
- final Workspace.ItemOperator findPromiseApp = (info, view) ->
+ final ItemOperator findPromiseApp = (info, view) ->
info != null && TextUtils.equals(info.title, appLabel);
// Create and add test session
@@ -97,7 +97,7 @@
@Test
public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable {
final String appLabel = "Test Promise App " + UUID.randomUUID().toString();
- final Workspace.ItemOperator findPromiseApp = (info, view) ->
+ final ItemOperator findPromiseApp = (info, view) ->
info != null && TextUtils.equals(info.title, appLabel);
// Create and add test session without icon or label
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4dd44f4..4beb617 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -80,8 +80,12 @@
assertTrue(message, failed);
}
+ private int pagesPerScreen() {
+ return mLauncher.isTwoPanels() ? 2 : 1;
+ }
+
private boolean isWorkspaceScrollable(Launcher launcher) {
- return launcher.getWorkspace().getPageCount() > 1;
+ return launcher.getWorkspace().getPageCount() > pagesPerScreen();
}
private int getCurrentWorkspacePage(Launcher launcher) {
@@ -195,8 +199,9 @@
workspace.ensureWorkspaceIsScrollable();
executeOnLauncher(
- launcher -> assertEquals("Ensuring workspace scrollable didn't switch to page #1",
- 1, getCurrentWorkspacePage(launcher)));
+ launcher -> assertEquals(
+ "Ensuring workspace scrollable didn't switch to next screen",
+ pagesPerScreen(), getCurrentWorkspacePage(launcher)));
executeOnLauncher(
launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
isWorkspaceScrollable(launcher)));
@@ -212,8 +217,8 @@
workspace.flingForward();
executeOnLauncher(
- launcher -> assertEquals("Flinging forward didn't switch workspace to page #1",
- 1, getCurrentWorkspacePage(launcher)));
+ launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
+ pagesPerScreen(), getCurrentWorkspacePage(launcher)));
assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
// Test starting a workspace app.
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 4978c01..5ea5d65 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -28,13 +28,13 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.Workspace;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ShellCommandRule;
@@ -121,7 +121,7 @@
/**
* Condition for searching widget id
*/
- private class WidgetSearchCondition implements Condition, Workspace.ItemOperator {
+ private class WidgetSearchCondition implements Condition, ItemOperator {
@Override
public boolean isTrue() throws Throwable {
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 745dc22..641e53a 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -30,7 +30,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -40,6 +39,7 @@
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.testcomponent.RequestPinItemActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index dc59bdd..5fbf847 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -61,13 +61,23 @@
out.putNextEntry(new ZipEntry("visible_windows.zip"));
dumpCommand("cmd window dump-visible-window-views", out);
out.closeEntry();
- } catch (IOException ex) { }
+ } catch (IOException ex) {
+ }
Log.e(TAG, "Failed test " + description.getMethodName()
+ ",\nscreenshot will be saved to " + sceenshot
+ ",\nUI dump at: " + hierarchy
+ " (use go/web-hv to open the dump file)", e);
device.takeScreenshot(sceenshot);
+
+ // Dump accessibility hierarchy
+ final File accessibilityHierarchyFile = new File(parentFile,
+ "AccessibilityHierarchy-" + description.getMethodName() + ".uix");
+ try {
+ device.dumpWindowHierarchy(accessibilityHierarchyFile);
+ } catch (IOException ex) {
+ Log.e(TAG, "Failed to save accessibility hierarchy", ex);
+ }
}
private static void dumpStringCommand(String cmd, OutputStream out) throws IOException {
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 6a6ec3e..2093682 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -18,7 +18,7 @@
import android.app.Activity;
import com.android.launcher3.Launcher;
-import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 1cb6b2d..78301e4 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -135,6 +135,7 @@
.collect(Collectors.toList()),
mLauncher.getVisibleBounds(searchBox).bottom
- mLauncher.getVisibleBounds(allAppsContainer).top);
+ verifyActiveContainer();
final int newScroll = getAllAppsScroll();
mLauncher.assertTrue(
"Scrolled in a wrong direction in AllApps: from " + scroll + " to "
@@ -144,7 +145,6 @@
mLauncher.assertTrue(
"Exceeded max scroll attempts: " + MAX_SCROLL_ATTEMPTS,
++attempts <= MAX_SCROLL_ATTEMPTS);
- verifyActiveContainer();
scroll = newScroll;
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 4b1610e..b290bb1 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -68,6 +68,10 @@
}
protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
+ return mLauncher.isTablet();
+ }
+
+ protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
return false;
}
@@ -90,21 +94,32 @@
mLauncher.sendPointer(
downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
- mLauncher.executeAndWaitForLauncherEvent(
- () -> mLauncher.movePointer(
- downTime,
- downTime,
- ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
- start,
- end,
- gestureScope),
- event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(event.getClassName()),
- () -> "Pause wasn't detected", "swiping and holding");
- mLauncher.runToState(
- () -> mLauncher.sendPointer(
- downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end,
- gestureScope),
- OVERVIEW_STATE_ORDINAL, "sending UP event");
+ Runnable swipeAndHold = () -> mLauncher.movePointer(
+ downTime,
+ downTime,
+ ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
+ start,
+ end,
+ gestureScope);
+ String swipeAndHoldAction = "swiping and holding";
+ Runnable up = () -> mLauncher.sendPointer(
+ downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end,
+ gestureScope);
+ String upAction = "sending UP event";
+ if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
+ mLauncher.runToState(swipeAndHold, OVERVIEW_STATE_ORDINAL, swipeAndHoldAction);
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(upAction)) {
+ up.run();
+ }
+ } else {
+ mLauncher.executeAndWaitForLauncherEvent(
+ swipeAndHold,
+ event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
+ event.getClassName()),
+ () -> "Pause wasn't detected",
+ swipeAndHoldAction);
+ mLauncher.runToState(up, OVERVIEW_STATE_ORDINAL, upAction);
+ }
break;
}
@@ -134,9 +149,15 @@
}
case THREE_BUTTON:
+ if (mLauncher.isTablet()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+ LauncherInstrumentation.EVENT_TOUCH_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+ LauncherInstrumentation.EVENT_TOUCH_UP);
+ }
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
mLauncher.runToState(
- () -> mLauncher.waitForSystemUiObject("recent_apps").click(),
+ () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
OVERVIEW_STATE_ORDINAL, "clicking Recents button");
break;
}
@@ -224,11 +245,23 @@
case THREE_BUTTON:
// Double press the recents button.
- UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+ UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
+ if (mLauncher.isTablet()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+ LauncherInstrumentation.EVENT_TOUCH_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+ LauncherInstrumentation.EVENT_TOUCH_UP);
+ }
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL,
"clicking Recents button for the first time");
mLauncher.getOverview();
+ if (mLauncher.isTablet()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+ LauncherInstrumentation.EVENT_TOUCH_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
+ LauncherInstrumentation.EVENT_TOUCH_UP);
+ }
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
mLauncher.executeAndWaitForEvent(
() -> recentsButton.click(),
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index 0060844..ee9dd1a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -63,4 +63,8 @@
return true;
}
+ @Override
+ protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
+ return true;
+ }
}
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index a15131d..7ec5208 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -56,24 +56,29 @@
protected abstract String launchableType();
private Background launch(BySelector selector) {
- LauncherInstrumentation.log("Launchable.launch before click "
- + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
- final String label = mObject.getText();
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to launch an app from " + launchableType())) {
+ LauncherInstrumentation.log("Launchable.launch before click "
+ + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
+ final String label = mObject.getText();
- mLauncher.executeAndWaitForEvent(
- () -> {
- mLauncher.clickLauncherObject(mObject);
- expectActivityStartEvents();
- },
- event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Launching an app didn't open a new window: " + label,
- "clicking " + launchableType());
+ mLauncher.executeAndWaitForEvent(
+ () -> {
+ mLauncher.clickLauncherObject(mObject);
+ expectActivityStartEvents();
+ },
+ event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+ () -> "Launching an app didn't open a new window: " + label,
+ "clicking " + launchableType());
- mLauncher.assertTrue(
- "App didn't start: " + label + " (" + selector + ")",
- TestHelpers.wait(Until.hasObject(selector),
- LauncherInstrumentation.WAIT_TIME_MS));
- return new Background(mLauncher);
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("clicked")) {
+ mLauncher.assertTrue(
+ "App didn't start: " + label + " (" + selector + ")",
+ TestHelpers.wait(Until.hasObject(selector),
+ LauncherInstrumentation.WAIT_TIME_MS));
+ return new Background(mLauncher);
+ }
+ }
}
/**
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 19094f8..93c921e 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -97,8 +97,8 @@
private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
private static final int GESTURE_STEP_MS = 16;
- private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
- private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
+ static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
+ static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
static final Pattern EVENT_START = Pattern.compile("start:");
@@ -207,6 +207,11 @@
public LauncherInstrumentation(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
+ try {
+ mDevice.executeShellCommand("am wait-for-broadcast-idle");
+ } catch (IOException e) {
+ log("Failed to wait for broadcast idle");
+ }
// Launcher should run in test harness so that custom accessibility protocol between
// Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
@@ -283,6 +288,16 @@
.getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ public boolean isTablet() {
+ return getTestInfo(TestProtocol.REQUEST_IS_TABLET)
+ .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
+ public boolean isTwoPanels() {
+ return getTestInfo(TestProtocol.REQUEST_IS_TWO_PANELS)
+ .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
void setActiveContainer(VisibleContainer container) {
sActiveContainer = new WeakReference<>(container);
}
@@ -368,6 +383,14 @@
if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
+ final String visibleApps = mDevice.findObjects(getAnyObjectSelector())
+ .stream()
+ .map(LauncherInstrumentation::getApplicationPackageSafe)
+ .distinct()
+ .filter(pkg -> pkg != null)
+ .collect(Collectors.joining(","));
+ if (SYSTEMUI_PACKAGE.equals(visibleApps)) return "Only System UI views are visible";
+
if (!mDevice.wait(Until.hasObject(getAnyObjectSelector()), WAIT_TIME_MS)) {
return "Screen is empty";
}
@@ -391,12 +414,15 @@
}
private String getVisiblePackages() {
- return mDevice.findObjects(getAnyObjectSelector())
+ final String apps = mDevice.findObjects(getAnyObjectSelector())
.stream()
.map(LauncherInstrumentation::getApplicationPackageSafe)
.distinct()
- .filter(pkg -> pkg != null && !"com.android.systemui".equals(pkg))
+ .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg))
.collect(Collectors.joining(", "));
+ return !apps.isEmpty()
+ ? "active app: " + apps
+ : "the test doesn't see views from any app, including Launcher";
}
private static String getApplicationPackageSafe(UiObject2 object) {
@@ -499,9 +525,8 @@
void fail(String message) {
checkForAnomaly();
Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
- "http://go/tapl test failure:\nContext: " + getContextDescription()
- + " - visible state is " + getVisibleStateMessage()
- + ";\nDetails: " + message, true)));
+ "http://go/tapl test failure: " + message + ";\nContext: " + getContextDescription()
+ + "; now visible state is " + getVisibleStateMessage(), true)));
}
private String getContextDescription() {
@@ -554,29 +579,35 @@
public String getNavigationModeMismatchError(boolean waitForCorrectState) {
final int waitTime = waitForCorrectState ? WAIT_TIME_MS : 0;
final NavigationModel navigationModel = getNavigationModel();
-
+ String resPackage = getNavigationButtonResPackage();
if (navigationModel == NavigationModel.THREE_BUTTON) {
- if (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")), waitTime)) {
+ if (!mDevice.wait(Until.hasObject(By.res(resPackage, "recent_apps")), waitTime)) {
return "Recents button not present in 3-button mode";
}
} else {
- if (!mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "recent_apps")), waitTime)) {
+ if (!mDevice.wait(Until.gone(By.res(resPackage, "recent_apps")), waitTime)) {
return "Recents button is present in non-3-button mode";
}
}
if (navigationModel == NavigationModel.ZERO_BUTTON) {
- if (!mDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "home")), waitTime)) {
+ if (!mDevice.wait(Until.gone(By.res(resPackage, "home")), waitTime)) {
return "Home button is present in gestural mode";
}
} else {
- if (!mDevice.wait(Until.hasObject(By.res(SYSTEMUI_PACKAGE, "home")), waitTime)) {
+ if (!mDevice.wait(Until.hasObject(By.res(resPackage, "home")), waitTime)) {
return "Home button not present in non-gestural mode";
}
}
return null;
}
+ private String getNavigationButtonResPackage() {
+ return isTablet() && getNavigationModel() == NavigationModel.THREE_BUTTON ?
+ getLauncherPackageName() :
+ SYSTEMUI_PACKAGE;
+ }
+
private UiObject2 verifyContainerType(ContainerType containerType) {
waitForLauncherInitialized();
@@ -688,7 +719,8 @@
* @return the Workspace object.
*/
public Workspace pressHome() {
- try (LauncherInstrumentation.Closable e = eventsCheck()) {
+ try (LauncherInstrumentation.Closable e = eventsCheck();
+ LauncherInstrumentation.Closable c = addContextLayer("want to switch to home")) {
waitForLauncherInitialized();
// Click home, then wait for any accessibility event, then wait until accessibility
// events stop.
@@ -708,7 +740,7 @@
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
false, GestureScope.INSIDE_TO_OUTSIDE);
- try (LauncherInstrumentation.Closable c = addContextLayer(
+ try (LauncherInstrumentation.Closable c1 = addContextLayer(
"Swiped up from context menu to home")) {
waitUntilLauncherObjectGone(CONTEXT_MENU_RES_ID);
// Swiping up can temporarily bring Nexus Launcher if the current
@@ -728,7 +760,7 @@
displaySize.x / 2, displaySize.y - 1,
displaySize.x / 2, 0,
ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
- launcherWasVisible
+ launcherWasVisible || isTablet()
? GestureScope.INSIDE_TO_OUTSIDE
: GestureScope.OUTSIDE_WITH_PILFER);
}
@@ -740,16 +772,20 @@
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
}
+ if (isTablet()) {
+ expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
+ expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
+ }
runToState(
- waitForSystemUiObject("home")::click,
+ waitForNavigationUiObject("home")::click,
NORMAL_STATE_ORDINAL,
!hasLauncherObject(WORKSPACE_RES_ID)
&& (hasLauncherObject(APPS_RES_ID)
|| hasLauncherObject(OVERVIEW_RES_ID)),
action);
}
- try (LauncherInstrumentation.Closable c = addContextLayer(
+ try (LauncherInstrumentation.Closable c1 = addContextLayer(
"performed action to switch to Home - " + action)) {
return getWorkspace();
}
@@ -892,6 +928,15 @@
return object;
}
+ @NonNull
+ UiObject2 waitForNavigationUiObject(String resId) {
+ String resPackage = getNavigationButtonResPackage();
+ final UiObject2 object = mDevice.wait(
+ Until.findObject(By.res(resPackage, resId)), WAIT_TIME_MS);
+ assertNotNull("Can't find a navigation UI object with id: " + resId, object);
+ return object;
+ }
+
@Nullable
UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
try {
@@ -930,7 +975,7 @@
void waitForObjectEnabled(UiObject2 object, String waitReason) {
try {
assertTrue("Timed out waiting for object to be enabled for " + waitReason + " "
- + object.getResourceName(),
+ + object.getResourceName(),
object.wait(Until.enabled(true), WAIT_TIME_MS));
} catch (StaleObjectException e) {
fail("The object disappeared from screen");
@@ -1049,7 +1094,7 @@
() -> "Failed to receive an event for the state change: expected ["
+ TestProtocol.stateOrdinalToString(expectedState)
+ "], actual: " + eventListToString(actualEvents),
- actionName);
+ actionName);
}
private boolean isSwitchToStateEvent(
@@ -1176,7 +1221,7 @@
event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
() -> "Didn't receive a scroll end message: " + startX + ", " + startY
+ ", " + endX + ", " + endY,
- "scrolling");
+ "scrolling");
}
// Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 657b74d..71c0abb 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -59,9 +59,14 @@
final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
final int centerX = taskBounds.centerX();
final int centerY = taskBounds.centerY();
- mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
- LauncherInstrumentation.GestureScope.INSIDE);
- mLauncher.waitForIdle();
+ mLauncher.executeAndWaitForLauncherEvent(
+ () -> mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
+ LauncherInstrumentation.GestureScope.INSIDE),
+ event -> TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE.equals(
+ event.getClassName()),
+ () -> "Didn't receive a dismiss animation ends message: " + centerX + ", "
+ + centerY,
+ "swiping to dismiss");
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 43134d9..2acf7b4 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -63,7 +63,7 @@
/**
* Swipes up to All Apps.
*
- * @return the App Apps object.
+ * @return the All Apps object.
*/
@NonNull
public AllApps switchToAllApps() {
@@ -163,7 +163,7 @@
}
private boolean isWorkspaceScrollable(UiObject2 workspace) {
- return workspace.getChildCount() > 1;
+ return workspace.getChildCount() > (mLauncher.isTwoPanels() ? 2 : 1);
}
@NonNull