Merge "Prevent clearing home between display size changes." into sc-v2-dev
diff --git a/go/quickstep/res/layout/overview_actions_container.xml b/go/quickstep/res/layout/overview_actions_container.xml
index b36214b..e7da196 100644
--- a/go/quickstep/res/layout/overview_actions_container.xml
+++ b/go/quickstep/res/layout/overview_actions_container.xml
@@ -29,10 +29,6 @@
android:orientation="horizontal">
<Space
- android:layout_width="@dimen/go_overview_button_width"
- android:layout_height="1dp" />
-
- <Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
@@ -50,9 +46,8 @@
</LinearLayout>
<Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
+ android:layout_width="@dimen/go_overview_button_container_margin"
+ android:layout_height="1dp" />
<LinearLayout
style="@style/GoOverviewActionButtonContainer">
@@ -68,9 +63,8 @@
</LinearLayout>
<Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" />
+ android:layout_width="@dimen/go_overview_button_container_margin"
+ android:layout_height="1dp" />
<LinearLayout
style="@style/GoOverviewActionButtonContainer">
@@ -90,10 +84,6 @@
android:layout_height="1dp"
android:layout_weight="1" />
- <Space
- android:layout_width="@dimen/go_overview_button_width"
- android:layout_height="1dp" />
-
<!-- Will be enabled in a future version. -->
<LinearLayout
style="@style/GoOverviewActionButtonContainer"
diff --git a/go/quickstep/res/layout/overview_panel.xml b/go/quickstep/res/layout/overview_panel.xml
new file mode 100644
index 0000000..241b63d
--- /dev/null
+++ b/go/quickstep/res/layout/overview_panel.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <com.android.quickstep.views.LauncherRecentsView
+ android:id="@+id/overview_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:accessibilityPaneTitle="@string/accessibility_recent_apps"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:background="?attr/overviewBackgroundColor"
+ android:visibility="invisible" />
+
+ <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/darker_gray"
+ android:visibility="gone" />
+
+ <include
+ android:id="@+id/overview_actions_view"
+ layout="@layout/overview_actions_container" />
+
+</merge>
diff --git a/go/quickstep/res/values/attrs.xml b/go/quickstep/res/values/attrs.xml
new file mode 100644
index 0000000..3adf462
--- /dev/null
+++ b/go/quickstep/res/values/attrs.xml
@@ -0,0 +1,23 @@
+<?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>
+ <!-- Attributes used for Overview theming -->
+ <attr name="overviewBackgroundColor" format="color" />
+ <attr name="overviewButtonTextColor" format="color" />
+ <attr name="overviewButtonIconColor" format="color" />
+ <attr name="overviewButtonBackgroundColor" format="color" />
+</resources>
\ No newline at end of file
diff --git a/go/quickstep/res/values/colors.xml b/go/quickstep/res/values/colors.xml
index 9383770..f815f54 100644
--- a/go/quickstep/res/values/colors.xml
+++ b/go/quickstep/res/values/colors.xml
@@ -16,6 +16,10 @@
-->
<resources>
<!-- Overview -->
- <color name="go_overview_button_icon_color">#3C4043</color>
+ <color name="go_overview_background_color">#DADADA</color>
+ <color name="go_overview_background_color_dark">#000000</color>
+ <color name="go_overview_text_color">#3C4043</color>
+ <color name="go_overview_text_color_dark">#F8F9FA</color>
<color name="go_overview_button_color">#70FFFFFF</color>
+ <color name="go_overview_button_color_dark">#303030</color>
</resources>
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
index da684fa..55cd138 100644
--- a/go/quickstep/res/values/dimens.xml
+++ b/go/quickstep/res/values/dimens.xml
@@ -22,6 +22,7 @@
<dimen name="go_overview_button_width">60dp</dimen>
<dimen name="go_overview_button_height">60dp</dimen>
<dimen name="go_overview_button_container_width">80dp</dimen>
+ <dimen name="go_overview_button_container_margin">16dp</dimen>
<dimen name="go_overview_button_caption_margin">8dp</dimen>
<dimen name="overview_actions_height">96dp</dimen>
<dimen name="overview_proactive_row_height">0dp</dimen>
diff --git a/go/quickstep/res/values/styles.xml b/go/quickstep/res/values/styles.xml
index 9b3fe67..59f7377 100644
--- a/go/quickstep/res/values/styles.xml
+++ b/go/quickstep/res/values/styles.xml
@@ -15,9 +15,25 @@
limitations under the License.
-->
<resources>
+ <!-- App themes -->
+ <style name="AppTheme" parent="@style/LauncherTheme">
+ <item name="overviewBackgroundColor">@color/go_overview_background_color</item>
+ <item name="overviewButtonTextColor">@color/go_overview_text_color</item>
+ <item name="overviewButtonIconColor">@color/go_overview_text_color</item>
+ <item name="overviewButtonBackgroundColor">@color/go_overview_button_color</item>
+ </style>
+
+ <style name="AppTheme.Dark" parent="@style/LauncherTheme.Dark">
+ <item name="overviewBackgroundColor">@color/go_overview_background_color_dark</item>
+ <item name="overviewButtonTextColor">@color/go_overview_text_color_dark</item>
+ <item name="overviewButtonIconColor">@color/go_overview_text_color_dark</item>
+ <item name="overviewButtonBackgroundColor">@color/go_overview_button_color_dark</item>
+ </style>
+
+ <!-- Overview -->
<style name="GoOverviewActionButton">
- <item name="android:tint">@color/go_overview_button_icon_color</item>
- <item name="android:backgroundTint">@color/go_overview_button_color</item>
+ <item name="android:tint">?attr/overviewButtonIconColor</item>
+ <item name="android:backgroundTint">?attr/overviewButtonBackgroundColor</item>
<item name="android:background">@drawable/round_rect_button</item>
<item name="android:layout_width">@dimen/go_overview_button_width</item>
<item name="android:layout_height">@dimen/go_overview_button_height</item>
@@ -27,7 +43,7 @@
<style name="GoOverviewActionButtonCaption">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">14dp</item>
- <item name="android:textColor">@color/go_overview_button_icon_color</item>
+ <item name="android:textColor">?attr/overviewButtonTextColor</item>
<item name="android:lineHeight">20dp</item>
<item name="android:textAlignment">center</item>
<item name="android:importantForAccessibility">no</item>
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index 754782b..65cdcf0 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -53,14 +53,17 @@
public static final int ERROR_PERMISSIONS = 1;
private static final String TAG = "TaskOverlayFactoryGo";
- // Empty constructor required for ResourceBasedOverride
- public TaskOverlayFactoryGo(Context context) {}
+ private AssistContentRequester mContentRequester;
+
+ public TaskOverlayFactoryGo(Context context) {
+ mContentRequester = new AssistContentRequester(context);
+ }
/**
* Create a new overlay instance for the given View
*/
public TaskOverlayGo createOverlay(TaskThumbnailView thumbnailView) {
- return new TaskOverlayGo(thumbnailView);
+ return new TaskOverlayGo(thumbnailView, mContentRequester);
}
/**
@@ -72,9 +75,12 @@
private String mTaskPackageName;
private String mWebUrl;
private boolean mAssistPermissionsEnabled;
+ private AssistContentRequester mFactoryContentRequester;
- private TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
+ private TaskOverlayGo(TaskThumbnailView taskThumbnailView,
+ AssistContentRequester assistContentRequester) {
super(taskThumbnailView);
+ mFactoryContentRequester = assistContentRequester;
}
/**
@@ -105,9 +111,7 @@
}
int taskId = task.key.id;
- AssistContentRequester contentRequester =
- new AssistContentRequester(mApplicationContext);
- contentRequester.requestAssistContent(taskId, this::onAssistContentReceived);
+ mFactoryContentRequester.requestAssistContent(taskId, this::onAssistContentReceived);
}
/** Provide Assist Content to the overlay. */
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/task_menu.xml b/quickstep/res/layout/task_menu.xml
index 96a94ba..763e45e 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -25,15 +25,15 @@
<TextView
android:id="@+id/task_name"
- android:background="?android:attr/textColorPrimary"
- android:textColor="?android:attr/textColorPrimaryInverse"
+ android:background="?android:attr/colorPrimary"
+ android:textColor="?android:attr/textColorPrimary"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginBottom="2dp"
- android:paddingTop="@dimen/task_card_menu_option_vertical_padding"
- android:paddingBottom="@dimen/task_card_menu_option_vertical_padding"
- android:textSize="24sp"/>
+ android:paddingTop="@dimen/task_menu_vertical_padding"
+ android:paddingBottom="@dimen/task_menu_vertical_padding"
+ android:textSize="16sp"/>
<LinearLayout
android:id="@+id/menu_option_layout"
diff --git a/quickstep/res/layout/task_view_menu_option.xml b/quickstep/res/layout/task_view_menu_option.xml
index 59c7263..19ca3e3 100644
--- a/quickstep/res/layout/task_view_menu_option.xml
+++ b/quickstep/res/layout/task_view_menu_option.xml
@@ -21,7 +21,7 @@
android:orientation="vertical"
android:paddingTop="@dimen/task_card_menu_option_vertical_padding"
android:paddingBottom="@dimen/task_card_menu_option_vertical_padding"
- android:background="?android:attr/textColorPrimary"
+ android:background="?android:attr/colorPrimary"
android:theme="@style/PopupItem" >
<View
@@ -30,7 +30,7 @@
android:layout_height="@dimen/system_shortcut_icon_size"
android:layout_marginStart="@dimen/task_menu_option_start_margin"
android:layout_gravity="center_horizontal"
- android:backgroundTint="?android:attr/textColorPrimaryInverse"/>
+ android:backgroundTint="?android:attr/textColorPrimary"/>
<TextView
style="@style/BaseIcon"
@@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/task_menu_option_start_margin"
android:textSize="14sp"
- android:textColor="?android:attr/textColorPrimaryInverse"
+ android:textColor="?android:attr/textColorPrimary"
android:focusable="false" />
</LinearLayout>
diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml
index e680233..d32c115 100644
--- a/quickstep/res/layout/taskbar.xml
+++ b/quickstep/res/layout/taskbar.xml
@@ -29,7 +29,7 @@
android:layout_gravity="bottom" >
<LinearLayout
- android:id="@+id/system_button_layout"
+ android:id="@+id/nav_button_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
@@ -44,6 +44,15 @@
android:forceHasOverlappingRendering="false"
android:gravity="center" />
+ <LinearLayout
+ android:id="@+id/contextual_button_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
+ android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
+ android:forceHasOverlappingRendering="false"
+ android:gravity="center" />
+
</com.android.launcher3.taskbar.TaskbarView>
<com.android.launcher3.taskbar.ImeBarView
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index af4e19e..d9c33ae 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -45,8 +45,9 @@
<!-- These speeds are in dp/s -->
<dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
- <dimen name="default_task_dismiss_drag_velocity">1.75dp</dimen>
- <dimen name="default_task_dismiss_drag_velocity_grid">0.75dp</dimen>
+ <dimen name="default_task_dismiss_drag_velocity">1.5dp</dimen>
+ <dimen name="default_task_dismiss_drag_velocity_grid">1dp</dimen>
+ <dimen name="default_task_dismiss_drag_velocity_grid_focus_task">5dp</dimen>
<dimen name="recents_page_spacing">16dp</dimen>
<dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
@@ -64,7 +65,7 @@
<dimen name="quickstep_fling_threshold_speed">0.5dp</dimen>
<!-- Launcher app transition -->
- <dimen name="content_trans_y">50dp</dimen>
+ <item name="content_scale" format="float" type="dimen">0.97</item>
<dimen name="closing_window_trans_y">115dp</dimen>
<dimen name="recents_empty_message_text_size">16sp</dimen>
@@ -74,7 +75,8 @@
<!-- Total space (start + end) between the task card and the edge of the screen
in various configurations -->
- <dimen name="task_card_menu_option_vertical_padding">8dp</dimen>
+ <dimen name="task_card_menu_option_vertical_padding">16dp</dimen>
+ <dimen name="task_menu_vertical_padding">8dp</dimen>
<dimen name="task_card_margin">8dp</dimen>
<dimen name="task_card_menu_shadow_height">3dp</dimen>
<dimen name="task_menu_option_start_margin">12dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 8813891..43d03b3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -23,11 +23,14 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -49,10 +52,12 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -60,12 +65,15 @@
import android.os.SystemProperties;
import android.util.Pair;
import android.util.Size;
+import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -74,10 +82,11 @@
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.Themes;
import com.android.launcher3.views.FloatingIconView;
+import com.android.launcher3.views.ScrimView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.SystemUiProxy;
@@ -90,6 +99,7 @@
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.BlurUtils;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -159,7 +169,8 @@
private static final int CLOSING_TRANSITION_DURATION_MS = 250;
public static final int CONTENT_ALPHA_DURATION = 217;
- protected static final int CONTENT_TRANSLATION_DURATION = 350;
+ protected static final int CONTENT_SCALE_DURATION = 350;
+ protected static final int CONTENT_SCRIM_DURATION = 350;
private static final int MAX_NUM_TASKS = 5;
@@ -172,9 +183,8 @@
private final AlphaProperty mDragLayerAlpha;
final Handler mHandler;
- private final boolean mIsRtl;
- private final float mContentTransY;
+ private final float mContentScale;
private final float mClosingWindowTransY;
private final float mMaxShadowRadius;
@@ -211,11 +221,10 @@
mDragLayer = mLauncher.getDragLayer();
mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
mHandler = new Handler(Looper.getMainLooper());
- mIsRtl = Utilities.isRtl(mLauncher.getResources());
mDeviceProfile = mLauncher.getDeviceProfile();
Resources res = mLauncher.getResources();
- mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
+ mContentScale = res.getFloat(R.dimen.content_scale);
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
@@ -334,8 +343,7 @@
windowTargetBounds, areAllTargetsTranslucent(appTargets), rotationChange));
if (launcherClosing) {
Pair<AnimatorSet, Runnable> launcherContentAnimator =
- getLauncherContentAnimator(true /* isAppOpening */,
- new float[] {0, -mContentTransY});
+ getLauncherContentAnimator(true /* isAppOpening */);
anim.play(launcherContentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -435,10 +443,8 @@
*
* @param isAppOpening True when this is called when an app is opening.
* False when this is called when an app is closing.
- * @param trans Array that contains the start and end translation values for the content.
*/
- private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
- float[] trans) {
+ private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
AnimatorSet launcherAnimator = new AnimatorSet();
Runnable endListener;
@@ -446,13 +452,17 @@
? new float[] {1, 0}
: new float[] {0, 1};
+ float[] scales = isAppOpening
+ ? new float[] {1, mContentScale}
+ : new float[] {mContentScale, 1};
+
if (mLauncher.isInState(ALL_APPS)) {
// All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
final View appsView = mLauncher.getAppsView();
final float startAlpha = appsView.getAlpha();
- final float startY = appsView.getTranslationY();
+ final float startScale = SCALE_PROPERTY.get(appsView);
appsView.setAlpha(alphas[0]);
- appsView.setTranslationY(trans[0]);
+ SCALE_PROPERTY.set(appsView, scales[0]);
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
alpha.setDuration(CONTENT_ALPHA_DURATION);
@@ -464,30 +474,22 @@
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
- ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
- transY.setInterpolator(AGGRESSIVE_EASE);
- transY.setDuration(CONTENT_TRANSLATION_DURATION);
+ ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales);
+ scale.setInterpolator(AGGRESSIVE_EASE);
+ scale.setDuration(CONTENT_SCALE_DURATION);
launcherAnimator.play(alpha);
- launcherAnimator.play(transY);
+ launcherAnimator.play(scale);
endListener = () -> {
appsView.setAlpha(startAlpha);
- appsView.setTranslationY(startY);
+ SCALE_PROPERTY.set(appsView, startScale);
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
};
} else if (mLauncher.isInState(OVERVIEW)) {
- endListener = composeViewContentAnimator(launcherAnimator, alphas, trans);
+ endListener = composeViewContentAnimator(launcherAnimator, alphas, scales);
} else {
- mDragLayerAlpha.setValue(alphas[0]);
- ObjectAnimator alpha =
- ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas);
- alpha.setDuration(CONTENT_ALPHA_DURATION);
- alpha.setInterpolator(LINEAR);
- launcherAnimator.play(alpha);
-
List<View> viewsToAnimate = new ArrayList<>();
-
Workspace workspace = mLauncher.getWorkspace();
workspace.forEachVisiblePage(
view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
@@ -498,18 +500,38 @@
viewsToAnimate.forEach(view -> {
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- launcherAnimator.play(ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, trans));
+
+ ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scales)
+ .setDuration(CONTENT_SCALE_DURATION);
+ scaleAnim.setInterpolator(DEACCEL_1_5);
+ launcherAnimator.play(scaleAnim);
});
+ int scrimColor = Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor);
+ int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0);
+ int[] colors = isAppOpening
+ ? new int[] {scrimColorTrans, scrimColor}
+ : new int[] {scrimColor, scrimColorTrans};
+ ScrimView scrimView = mLauncher.getScrimView();
+ if (scrimView.getBackground() instanceof ColorDrawable) {
+ scrimView.setBackgroundColor(colors[0]);
+
+ ObjectAnimator scrim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR,
+ colors);
+ scrim.setDuration(CONTENT_SCRIM_DURATION);
+ scrim.setInterpolator(DEACCEL_1_5);
+ launcherAnimator.play(scrim);
+ }
+
// Pause page indicator animations as they lead to layer trashing.
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
endListener = () -> {
viewsToAnimate.forEach(view -> {
- view.setTranslationY(0);
+ SCALE_PROPERTY.set(view, 1f);
view.setLayerType(View.LAYER_TYPE_NONE, null);
});
- mDragLayerAlpha.setValue(1f);
+ scrimView.setBackgroundColor(Color.TRANSPARENT);
mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
};
}
@@ -521,11 +543,11 @@
*
* @param anim the animator set to add to
* @param alphas the alphas to animate to over time
- * @param trans the translation Y values to animator to over time
+ * @param scales the scale values to animator to over time
* @return listener to run when the animation ends
*/
protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim,
- float[] alphas, float[] trans) {
+ float[] alphas, float[] scales) {
RecentsView overview = mLauncher.getOverviewPanel();
ObjectAnimator alpha = ObjectAnimator.ofFloat(overview,
RecentsView.CONTENT_ALPHA, alphas);
@@ -534,14 +556,14 @@
anim.play(alpha);
overview.setFreezeViewVisibility(true);
- ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
- transY.setInterpolator(AGGRESSIVE_EASE);
- transY.setDuration(CONTENT_TRANSLATION_DURATION);
- anim.play(transY);
+ ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(overview, SCALE_PROPERTY, scales);
+ scaleAnim.setInterpolator(AGGRESSIVE_EASE);
+ scaleAnim.setDuration(CONTENT_SCALE_DURATION);
+ anim.play(scaleAnim);
return () -> {
overview.setFreezeViewVisibility(false);
- overview.setTranslationY(0);
+ SCALE_PROPERTY.set(overview, 1f);
mLauncher.getStateManager().reapplyState();
};
}
@@ -901,12 +923,39 @@
BACKGROUND_APP.getDepth(mLauncher))
.setDuration(APP_LAUNCH_DURATION);
if (allowBlurringLauncher) {
- depthController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
- appTargets, MODE_OPENING));
+ final SurfaceControl dimLayer;
+ if (BlurUtils.supportsBlursOnWindows()) {
+ // Create a temporary effect layer, that lives on top of launcher, so we can apply
+ // the blur to it. The EffectLayer will be fullscreen, which will help with caching
+ // optimizations on the SurfaceFlinger side:
+ // - Results would be able to be cached as a texture
+ // - There won't be texture allocation overhead, because EffectLayers don't have
+ // buffers
+ ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
+ SurfaceControl parent = viewRootImpl != null
+ ? viewRootImpl.getSurfaceControl()
+ : null;
+ dimLayer = new SurfaceControl.Builder()
+ .setName("Blur layer")
+ .setParent(parent)
+ .setOpaque(false)
+ .setHidden(false)
+ .setEffectLayer()
+ .build();
+ } else {
+ dimLayer = null;
+ }
+
+ depthController.setSurface(dimLayer);
backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- depthController.setSurfaceToApp(null);
+ depthController.setSurface(null);
+ if (dimLayer != null) {
+ new SurfaceControl.Transaction()
+ .remove(dimLayer)
+ .apply();
+ }
}
});
}
@@ -1230,8 +1279,7 @@
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
Pair<AnimatorSet, Runnable> contentAnimator =
- getLauncherContentAnimator(false /* isAppOpening */,
- new float[] {-mContentTransY, 0});
+ getLauncherContentAnimator(false /* isAppOpening */);
contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
anim.play(contentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@@ -1368,14 +1416,14 @@
: APP_LAUNCH_ALPHA_DOWN_DURATION;
iconAlphaStart = hasSplashScreen && !hasDifferentAppIcon ? 0 : 1f;
- // TOOD: Share value from shell when available.
- final float windowIconSize = Utilities.pxFromSp(108, r.getDisplayMetrics());
+ final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size",
+ r, 108);
cropCenterXStart = windowTargetBounds.centerX();
cropCenterYStart = windowTargetBounds.centerY();
- cropWidthStart = (int) windowIconSize;
- cropHeightStart = (int) windowIconSize;
+ cropWidthStart = windowIconSize;
+ cropHeightStart = windowIconSize;
cropWidthEnd = windowTargetBounds.width();
cropHeightEnd = windowTargetBounds.height();
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index e608885..46ef698 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -25,7 +25,9 @@
import android.animation.ObjectAnimator;
import android.os.IBinder;
import android.util.FloatProperty;
+import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import com.android.launcher3.BaseActivity;
@@ -37,9 +39,6 @@
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.systemui.shared.system.BlurUtils;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SurfaceControlCompat;
-import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.WallpaperManagerCompat;
/**
@@ -91,7 +90,8 @@
@Override
public void onDraw() {
View view = mLauncher.getDragLayer();
- setSurface(new SurfaceControlCompat(view));
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+ setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
}
};
@@ -102,7 +102,7 @@
*/
private int mMaxBlurRadius;
private WallpaperManagerCompat mWallpaperManager;
- private SurfaceControlCompat mSurface;
+ private SurfaceControl mSurface;
/**
* Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
* @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
@@ -157,11 +157,7 @@
/**
* Sets the specified app target surface to apply the blur to.
*/
- public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
- setSurface(target == null ? null : target.leash);
- }
-
- private void setSurface(SurfaceControlCompat surface) {
+ public void setSurface(SurfaceControl surface) {
if (mSurface != surface) {
mSurface = surface;
if (surface != null) {
@@ -219,7 +215,7 @@
if (supportsBlur) {
boolean isOpaque = mLauncher.getScrimView().isFullyOpaque();
int blur = isOpaque ? 0 : (int) (mDepth * mMaxBlurRadius);
- new TransactionCompat()
+ new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, blur)
.setOpaque(mSurface, isOpaque)
.apply();
diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
index 540f748..86ac39f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
+++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java
@@ -27,6 +27,7 @@
import com.android.launcher3.R;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.taskbar.contextual.RotationContextButton;
/**
* Creates Buttons for Taskbar for 3 button nav.
@@ -68,6 +69,11 @@
return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH);
}
+ public RotationContextButton getContextualRotation() {
+ // Rotation suggestion button
+ return new RotationContextButton(mContext);
+ }
+
private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) {
ImageView buttonView = new ImageView(mContext);
buttonView.setImageResource(drawableId);
diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
index 287caab..d581302 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java
@@ -25,7 +25,6 @@
public class ImeBarView extends RelativeLayout {
- private ButtonProvider mButtonProvider;
private View mImeView;
public ImeBarView(Context context) {
@@ -41,8 +40,8 @@
}
public void init(ButtonProvider buttonProvider) {
- mButtonProvider = buttonProvider;
-
+ // TODO (b/187966005), maybe need to replace ime switcher button with
+ // RotationContextButton when device rotates
ActivityContext context = getActivityContext();
RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams(
context.getDeviceProfile().iconSizePx,
@@ -56,13 +55,13 @@
downParams.addRule(ALIGN_PARENT_START);
// Down Arrow
- View downView = mButtonProvider.getDown();
+ View downView = buttonProvider.getDown();
downView.setLayoutParams(downParams);
downView.setRotation(-90);
addView(downView);
// IME switcher button
- mImeView = mButtonProvider.getImeSwitcher();
+ mImeView = buttonProvider.getImeSwitcher();
mImeView.setLayoutParams(imeParams);
addView(mImeView);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index ee57dd9..f124de7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -32,7 +32,6 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.states.StateAnimationConfig;
-
/**
* A data source which integrates with a Launcher instance
* TODO: Rename to have Launcher prefix
@@ -51,6 +50,7 @@
private @Nullable Animator mAnimator;
private boolean mIsAnimatingToLauncher;
+ private ContextualRotationNotifier mContextualRotationNotifier;
public LauncherTaskbarUIController(
BaseQuickstepLauncher launcher, TaskbarActivityContext context) {
@@ -67,7 +67,8 @@
}
@Override
- protected void onCreate() {
+ protected void onCreate(ContextualRotationNotifier notifier) {
+ mContextualRotationNotifier = notifier;
mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController);
mTaskbarAnimationController.init();
mHotseatController.init();
@@ -82,6 +83,7 @@
// End this first, in case it relies on properties that are about to be cleaned up.
mAnimator.end();
}
+ mContextualRotationNotifier = null;
mTaskbarStateHandler.setAnimationController(null);
mTaskbarAnimationController.cleanup();
mHotseatController.cleanup();
@@ -105,6 +107,9 @@
@Override
public void updateTaskbarVisibilityAlpha(float alpha) {
mTaskbarView.setAlpha(alpha);
+ if (mContextualRotationNotifier != null) {
+ mContextualRotationNotifier.onTaskbarVisibilityChanged(alpha == 1);
+ }
}
@Override
@@ -272,4 +277,8 @@
void updateTaskbarScale(float scale);
void updateTaskbarTranslationY(float translationY);
}
+
+ public interface ContextualRotationNotifier {
+ void onTaskbarVisibilityChanged(boolean showing);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 10ffefb..d51506c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -51,6 +51,7 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Themes;
@@ -88,7 +89,9 @@
private int mLastRequestedNonFullscreenHeight;
private final SysUINavigationMode.Mode mNavMode;
+ private final SystemTaskbarNotificationManager mSystemTaskbarNotificationManager;
private final TaskbarNavButtonController mNavButtonController;
+ private final RotationButtonController mRotationButtonController;
private final boolean mIsSafeModeEnabled;
@@ -98,12 +101,43 @@
private final View.OnClickListener mOnTaskbarIconClickListener;
private final View.OnLongClickListener mOnTaskbarIconLongClickListener;
+ private final TaskbarManager.SystemTaskbarNotifier mSystemTaskbarNotifier =
+ new TaskbarManager.SystemTaskbarNotifier() {
+ @Override
+ public void updateImeStatus(int displayId, int vis, int backDisposition,
+ boolean showImeSwitcher) {
+ /*
+ * When in 3 button nav, sysui flags don't get called since we prevent
+ * sysui nav bar from instantiating at all, which is what's responsible for
+ * sending sysui state flags over.
+ */
+ mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
+ }
+
+ @Override
+ public void onRotationProposal(int rotation, boolean isValid) {
+ mRotationButtonController.onRotationProposal(rotation, isValid);
+ }
+
+ @Override
+ public void disable(int displayId, int state1, int state2, boolean animate) {
+ mRotationButtonController.onDisable2FlagChanged(state2);
+ }
+
+ @Override
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ mRotationButtonController.onBehaviorChanged(displayId, behavior);
+ }
+ };
+
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
- TaskbarNavButtonController buttonController) {
+ TaskbarNavButtonController buttonController,
+ SystemTaskbarNotificationManager systemTaskbarNotificationManager) {
super(windowContext, Themes.getActivityThemeRes(windowContext));
mDeviceProfile = dp;
mNavButtonController = buttonController;
mNavMode = SysUINavigationMode.getMode(windowContext);
+ mSystemTaskbarNotificationManager = systemTaskbarNotificationManager;
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
@@ -118,7 +152,11 @@
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
mDragLayer = (TaskbarDragLayer) mLayoutInflater
.inflate(R.layout.taskbar, null, false);
- mIconController = new TaskbarIconController(this, mDragLayer);
+
+ mRotationButtonController = new RotationButtonController(this,
+ R.color.popup_color_primary_light, R.color.popup_color_primary_light);
+ mIconController = new TaskbarIconController(this, mDragLayer,
+ mRotationButtonController);
Display display = windowContext.getDisplay();
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
@@ -149,8 +187,13 @@
new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT }
);
- mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener);
+ mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener,
+ mNavMode);
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
+ if (mNavMode == Mode.THREE_BUTTONS) {
+ mSystemTaskbarNotificationManager
+ .registerSystemTaskbarNotifications(mSystemTaskbarNotifier);
+ }
}
public boolean canShowNavButtons() {
@@ -189,7 +232,10 @@
mUIController.onDestroy();
mUIController = uiController;
mIconController.setUIController(mUIController);
- mUIController.onCreate();
+ mUIController.onCreate(mRotationButtonController::onTaskBarVisibilityChange);
+ if (mNavMode == Mode.THREE_BUTTONS) {
+ mRotationButtonController.init();
+ }
}
/**
@@ -199,6 +245,11 @@
setUIController(TaskbarUIController.DEFAULT);
mIconController.onDestroy();
mWindowManager.removeViewImmediate(mDragLayer);
+ if (mNavMode == Mode.THREE_BUTTONS) {
+ mSystemTaskbarNotificationManager.removeSystemTaskbarNotifications(
+ mSystemTaskbarNotifier);
+ mRotationButtonController.cleanup();
+ }
}
void onNavigationButtonClick(@TaskbarButton int buttonType) {
@@ -213,16 +264,6 @@
}
/**
- * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
- * instantiating at all, which is what's responsible for sending sysui state flags over.
- *
- * @param vis IME visibility flag
- */
- public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) {
- mIconController.updateImeStatus(displayId, vis, showImeSwitcher);
- }
-
- /**
* Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size.
*/
public void setTaskbarWindowFullscreen(boolean fullscreen) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
index cf0694b..5d4b8b7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java
@@ -31,6 +31,8 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.AlphaUpdateListener;
+import com.android.launcher3.taskbar.contextual.RotationButtonController;
+import com.android.quickstep.SysUINavigationMode;
import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo;
/**
@@ -45,18 +47,22 @@
private final TaskbarView mTaskbarView;
private final ImeBarView mImeBarView;
+ private final RotationButtonController mRotationButtonController;
@NonNull
private TaskbarUIController mUIController = TaskbarUIController.DEFAULT;
- TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer) {
+ TaskbarIconController(TaskbarActivityContext activity, TaskbarDragLayer dragLayer,
+ RotationButtonController rotationButtonController) {
mActivity = activity;
mDragLayer = dragLayer;
mTaskbarView = mDragLayer.findViewById(R.id.taskbar_view);
mImeBarView = mDragLayer.findViewById(R.id.ime_bar_view);
+ mRotationButtonController = rotationButtonController;
}
- public void init(OnClickListener clickListener, OnLongClickListener longClickListener) {
+ public void init(OnClickListener clickListener, OnLongClickListener longClickListener,
+ SysUINavigationMode.Mode navMode) {
mDragLayer.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) ->
mUIController.alignRealHotseatWithTaskbar());
@@ -67,6 +73,9 @@
mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
mDragLayer.init(new TaskbarDragLayerCallbacks(), mTaskbarView);
+ if (navMode == SysUINavigationMode.Mode.THREE_BUTTONS) {
+ mRotationButtonController.setRotationButton(mTaskbarView.getContextualRotationButton());
+ }
}
public void onDestroy() {
@@ -127,6 +136,12 @@
mTaskbarView.mSystemButtonContainer, mTempRect);
insetsInfo.touchableRegion.set(mTempRect);
}
+ if (mTaskbarView.mContextualButtonContainer.getVisibility() == VISIBLE) {
+ mDragLayer.getDescendantRectRelativeToSelf(
+ mTaskbarView.mContextualButtonContainer, mTempRect);
+ insetsInfo.touchableRegion.union(mTempRect);
+ }
+
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index d026bfb..b9acee8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -40,11 +40,14 @@
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.TouchInteractionService;
+import java.util.ArrayList;
+import java.util.List;
+
/**
- * Class to manager taskbar lifecycle
+ * Class to manage taskbar lifecycle
*/
public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
- SysUINavigationMode.NavigationModeChangeListener {
+ SysUINavigationMode.NavigationModeChangeListener, SystemTaskbarNotificationManager {
private final Context mContext;
private final DisplayController mDisplayController;
@@ -59,6 +62,8 @@
private boolean mUserUnlocked = false;
+ private List<SystemTaskbarNotifier> mSystemTaskbarNotifiers = new ArrayList<>();
+
public TaskbarManager(TouchInteractionService service) {
mDisplayController = DisplayController.INSTANCE.get(service);
mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service);
@@ -124,7 +129,7 @@
return;
}
mTaskbarActivityContext = new TaskbarActivityContext(
- mContext, dp.copy(mContext), mNavButtonController);
+ mContext, dp.copy(mContext), mNavButtonController, this);
mTaskbarActivityContext.init();
if (mLauncher != null) {
mTaskbarActivityContext.setUIController(
@@ -132,6 +137,9 @@
}
}
+ // TODO - I don't think this is the best place for these pass through methods,
+ // maybe directly in TaskbarIconController?
+
/**
* See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
* @param systemUiStateFlags The latest SystemUiStateFlags
@@ -143,6 +151,16 @@
}
}
+ public void registerSystemTaskbarNotifications(SystemTaskbarNotifier notifier) {
+ if (!mSystemTaskbarNotifiers.contains(notifier)) {
+ mSystemTaskbarNotifiers.add(notifier);
+ }
+ }
+
+ public void removeSystemTaskbarNotifications(SystemTaskbarNotifier notifier) {
+ mSystemTaskbarNotifiers.remove(notifier);
+ }
+
/**
* When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from
* instantiating at all, which is what's responsible for sending sysui state flags over.
@@ -153,8 +171,26 @@
*/
public void updateImeStatus(int displayId, int vis, int backDisposition,
boolean showImeSwitcher) {
- if (mTaskbarActivityContext != null) {
- mTaskbarActivityContext.updateImeStatus(displayId, vis, showImeSwitcher);
+ for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
+ notifier.updateImeStatus(displayId, vis, backDisposition, showImeSwitcher);
+ }
+ }
+
+ public void onRotationProposal(int rotation, boolean isValid) {
+ for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
+ notifier.onRotationProposal(rotation, isValid);
+ }
+ }
+
+ public void disable(int displayId, int state1, int state2, boolean animate) {
+ for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
+ notifier.disable(displayId, state1, state2, animate);
+ }
+ }
+
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ for (SystemTaskbarNotifier notifier : mSystemTaskbarNotifiers) {
+ notifier.onSystemBarAttributesChanged(displayId, behavior);
}
}
@@ -166,4 +202,18 @@
mDisplayController.removeChangeListener(this);
mSysUINavigationMode.removeModeChangeListener(this);
}
+
+ public interface SystemTaskbarNotifier {
+ void updateImeStatus(int displayId, int vis, int backDisposition,
+ boolean showImeSwitcher);
+ void onRotationProposal(int rotation, boolean isValid);
+ void disable(int displayId, int state1, int state2, boolean animate);
+ void onSystemBarAttributesChanged(int displayId, int behavior);
+
+ }
}
+
+interface SystemTaskbarNotificationManager {
+ void registerSystemTaskbarNotifications(TaskbarManager.SystemTaskbarNotifier notifier);
+ void removeSystemTaskbarNotifications(TaskbarManager.SystemTaskbarNotifier notifier);
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 50adead..f7a5618 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -27,7 +27,7 @@
*/
public void alignRealHotseatWithTaskbar() { }
- protected void onCreate() { }
+ protected void onCreate(LauncherTaskbarUIController.ContextualRotationNotifier notifier) { }
protected void onDestroy() { }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index ac70358..5c89f8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -42,6 +42,7 @@
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.taskbar.contextual.RotationContextButton;
import com.android.launcher3.views.ActivityContext;
/**
@@ -67,6 +68,7 @@
LinearLayout mSystemButtonContainer;
LinearLayout mHotseatIconsContainer;
+ LinearLayout mContextualButtonContainer;
// Delegate touches to the closest view if within mIconTouchSize.
private boolean mDelegateTargeted;
@@ -79,6 +81,7 @@
/** Provider of buttons added to taskbar in 3 button nav */
private ButtonProvider mButtonProvider;
+ private RotationContextButton mContextualRotationButton;
private boolean mDisableRelayout;
private boolean mAreHolesAllowed;
@@ -112,8 +115,9 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mSystemButtonContainer = findViewById(R.id.system_button_layout);
+ mSystemButtonContainer = findViewById(R.id.nav_button_layout);
mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout);
+ mContextualButtonContainer = findViewById(R.id.contextual_button_layout);
}
protected void init(TaskbarIconController.TaskbarViewCallbacks callbacks,
@@ -132,6 +136,10 @@
int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons;
updateHotseatItems(new ItemInfo[numHotseatIcons]);
+
+ if (mActivityContext.canShowNavButtons()) {
+ createContextualRegion();
+ }
}
/**
@@ -378,6 +386,16 @@
}
}
+ private void createContextualRegion() {
+ mContextualRotationButton = mButtonProvider.getContextualRotation();
+ mContextualRotationButton.setVisibility(GONE);
+ mContextualButtonContainer.addView(mContextualRotationButton);
+ }
+
+ @Nullable
+ public RotationContextButton getContextualRotationButton() {
+ return mContextualRotationButton;
+ }
// FolderIconParent implemented methods.
@Override
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..d421077
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButton.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.contextual;
+
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.view.View;
+
+/**
+ * Interface of a rotation button that interacts {@link RotationButtonController}.
+ * This interface exists because of the two different styles of rotation button in Sysui,
+ * one in contextual for 3 button nav and a floating rotation button for gestural.
+ * Keeping the interface for eventual migration of floating button, so some methods are
+ * pass through to "super" while others are trivially implemented.
+ *
+ * Changes:
+ * * Directly use AnimatedVectorDrawable instead of KeyButtonDrawable
+ */
+public interface RotationButton {
+ void setRotationButtonController(RotationButtonController rotationButtonController);
+ View getCurrentView();
+ boolean show();
+ boolean hide();
+ boolean isVisible();
+ void updateIcon(int lightIconColor, int darkIconColor);
+ void setOnClickListener(View.OnClickListener onClickListener);
+ void setOnHoverListener(View.OnHoverListener onHoverListener);
+ AnimatedVectorDrawable getImageDrawable();
+ void setDarkIntensity(float darkIntensity);
+ default boolean acceptRotationProposal() {
+ return getCurrentView() != null;
+ }
+}
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..6f6abc2
--- /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) throws RemoteException {
+ // We need this to be scheduled as early as possible to beat the redrawing of
+ // window in response to the orientation change.
+ mMainThreadHandler.postAtFrontOfQueue(() -> {
+ // If the screen rotation changes while locked, potentially update lock to flow with
+ // new screen rotation and hide any showing suggestions.
+ if (isRotationLocked()) {
+ if (shouldOverrideUserLockPrefs(rotation)) {
+ setRotationLockedAtAngle(rotation);
+ }
+ setRotateSuggestionButtonState(false /* visible */, true /* forced */);
+ }
+ });
+ }
+ };
+
+ /**
+ * Determines if rotation suggestions disabled2 flag exists in flag
+ * @param disable2Flags see if rotation suggestion flag exists in this flag
+ * @return whether flag exists
+ */
+ static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) {
+ return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0;
+ }
+
+ public RotationButtonController(Context context, @ColorInt int lightIconColor,
+ @ColorInt int darkIconColor) {
+ mContext = context;
+ mLightIconColor = lightIconColor;
+ mDarkIconColor = darkIconColor;
+
+ mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mTaskStackListener = new TaskStackListenerImpl();
+ mDisplayController = DisplayController.INSTANCE.getNoCreate();
+ }
+
+ public void setRotationButton(RotationButton rotationButton) {
+ mRotationButton = rotationButton;
+ mRotationButton.setRotationButtonController(this);
+ mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
+ mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
+ }
+
+ public void init() {
+ registerListeners();
+ if (mDisplayController.getInfo().id != DEFAULT_DISPLAY) {
+ // Currently there is no accelerometer sensor on non-default display, disable fixed
+ // rotation for non-default display
+ onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
+ }
+ }
+
+ public void cleanup() {
+ unregisterListeners();
+ }
+
+ private void registerListeners() {
+ if (mListenersRegistered) {
+ return;
+ }
+
+ mListenersRegistered = true;
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .watchRotation(mRotationWatcher, mDisplayController.getInfo().id);
+ } catch (IllegalArgumentException e) {
+ mListenersRegistered = false;
+ Log.w(TAG, "RegisterListeners for the display failed");
+ } catch (RemoteException e) {
+ Log.e(TAG, "RegisterListeners caught a RemoteException", e);
+ return;
+ }
+
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+ }
+
+ void unregisterListeners() {
+ if (!mListenersRegistered) {
+ return;
+ }
+
+ mListenersRegistered = false;
+ try {
+ WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
+ } catch (RemoteException e) {
+ Log.e(TAG, "UnregisterListeners caught a RemoteException", e);
+ return;
+ }
+
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+ }
+
+ void setRotationLockedAtAngle(int rotationSuggestion) {
+ RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion);
+ }
+
+ public boolean isRotationLocked() {
+ return RotationPolicy.isRotationLocked(mContext);
+ }
+
+ public void setRotateSuggestionButtonState(boolean visible) {
+ setRotateSuggestionButtonState(visible, false /* force */);
+ }
+
+ void setRotateSuggestionButtonState(final boolean visible, final boolean force) {
+ // At any point the button can become invisible because an a11y service became active.
+ // Similarly, a call to make the button visible may be rejected because an a11y service is
+ // active. Must account for this.
+ // Rerun a show animation to indicate change but don't rerun a hide animation
+ if (!visible && !mRotationButton.isVisible()) return;
+
+ final View view = mRotationButton.getCurrentView();
+ if (view == null) return;
+
+ final AnimatedVectorDrawable currentDrawable = mRotationButton.getImageDrawable();
+ if (currentDrawable == null) return;
+
+ // Clear any pending suggestion flag as it has either been nullified or is being shown
+ mPendingRotationSuggestion = false;
+ mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
+
+ // Handle the visibility change and animation
+ if (visible) { // Appear and change (cannot force)
+ // Stop and clear any currently running hide animations
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.cancel();
+ }
+ mRotateHideAnimator = null;
+
+ // Reset the alpha if any has changed due to hide animation
+ view.setAlpha(1f);
+
+ // Run the rotate icon's animation if it has one
+ currentDrawable.reset();
+ currentDrawable.start();
+
+ // TODO(b/187754252): No idea why this doesn't work. If we remove the "false"
+ // we see the animation show the pressed state... but it only shows the first time.
+ if (!isRotateSuggestionIntroduced()) mViewRippler.start(view);
+
+ // Set visibility unless a11y service is active.
+ mRotationButton.show();
+ } else { // Hide
+ mViewRippler.stop(); // Prevent any pending ripples, force hide or not
+
+ if (force) {
+ // If a hide animator is running stop it and make invisible
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) {
+ mRotateHideAnimator.pause();
+ }
+ mRotationButton.hide();
+ return;
+ }
+
+ // Don't start any new hide animations if one is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f);
+ fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS);
+ fadeOut.setInterpolator(LINEAR);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRotationButton.hide();
+ }
+ });
+
+ mRotateHideAnimator = fadeOut;
+ fadeOut.start();
+ }
+ }
+
+ void setDarkIntensity(float darkIntensity) {
+ mRotationButton.setDarkIntensity(darkIntensity);
+ }
+
+ public void onRotationProposal(int rotation, boolean isValid) {
+ int windowRotation = mDisplayController.getInfo().rotation;
+
+ if (!mRotationButton.acceptRotationProposal()) {
+ return;
+ }
+
+ // This method will be called on rotation suggestion changes even if the proposed rotation
+ // is not valid for the top app. Use invalid rotation choices as a signal to remove the
+ // rotate button if shown.
+ if (!isValid) {
+ setRotateSuggestionButtonState(false /* visible */);
+ return;
+ }
+
+ // If window rotation matches suggested rotation, remove any current suggestions
+ if (rotation == windowRotation) {
+ mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
+ setRotateSuggestionButtonState(false /* visible */);
+ return;
+ }
+
+ // Prepare to show the navbar icon by updating the icon style to change anim params
+ mLastRotationSuggestion = rotation; // Remember rotation for click
+ final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation);
+ if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) {
+ mIconResId = rotationCCW
+ ? R.drawable.ic_sysbar_rotate_button_ccw_start_90
+ : R.drawable.ic_sysbar_rotate_button_cw_start_90;
+ } else { // 90 or 270
+ mIconResId = rotationCCW
+ ? R.drawable.ic_sysbar_rotate_button_ccw_start_0
+ : R.drawable.ic_sysbar_rotate_button_ccw_start_0;
+ }
+ mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
+
+ if (canShowRotationButton()) {
+ // The navbar is visible / it's in visual immersive mode, so show the icon right away
+ showAndLogRotationSuggestion();
+ } else {
+ // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
+ // visible given some time limit.
+ mPendingRotationSuggestion = true;
+ mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal);
+ mMainThreadHandler.postDelayed(mCancelPendingRotationProposal,
+ NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS);
+ }
+ }
+
+ public void onDisable2FlagChanged(int state2) {
+ final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2);
+ if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
+ }
+
+ public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
+ if (mDisplayController.getInfo().id != displayId) {
+ return;
+ }
+
+ if (mBehavior != behavior) {
+ mBehavior = behavior;
+ showPendingRotationButtonIfNeeded();
+ }
+ }
+
+ public void onTaskBarVisibilityChange(boolean showing) {
+ if (mIsTaskbarShowing != showing) {
+ mIsTaskbarShowing = showing;
+ showPendingRotationButtonIfNeeded();
+ }
+ }
+
+ private void showPendingRotationButtonIfNeeded() {
+ if (canShowRotationButton() && mPendingRotationSuggestion) {
+ showAndLogRotationSuggestion();
+ }
+ }
+
+ /** Return true when either the task bar is visible or it's in visual immersive mode. */
+ @SuppressLint("InlinedApi")
+ private boolean canShowRotationButton() {
+ return mIsTaskbarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
+ }
+
+ public @DrawableRes
+ int getIconResId() {
+ return mIconResId;
+ }
+
+ public @ColorInt int getLightIconColor() {
+ return mLightIconColor;
+ }
+
+ public @ColorInt int getDarkIconColor() {
+ return mDarkIconColor;
+ }
+
+ private void onRotateSuggestionClick(View v) {
+ mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
+ incrementNumAcceptedRotationSuggestionsIfNeeded();
+ setRotationLockedAtAngle(mLastRotationSuggestion);
+ }
+
+ private boolean onRotateSuggestionHover(View v, MotionEvent event) {
+ final int action = event.getActionMasked();
+ mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleRotationTimeout(true /* reasonHover */);
+ return false; // Must return false so a11y hover events are dispatched correctly.
+ }
+
+ private void onRotationSuggestionsDisabled() {
+ // Immediately hide the rotate button and clear any planned removal
+ setRotateSuggestionButtonState(false /* visible */, true /* force */);
+ mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
+ }
+
+ private void showAndLogRotationSuggestion() {
+ setRotateSuggestionButtonState(true /* visible */);
+ rescheduleRotationTimeout(false /* reasonHover */);
+ mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN);
+ }
+
+ /**
+ * Makes {@link #shouldOverrideUserLockPrefs} always return {@code false} once. It is used to
+ * avoid losing original user rotation when display rotation is changed by entering the fixed
+ * orientation overview.
+ */
+ void setSkipOverrideUserLockPrefsOnce() {
+ mSkipOverrideUserLockPrefsOnce = true;
+ }
+
+ private boolean shouldOverrideUserLockPrefs(final int rotation) {
+ if (mSkipOverrideUserLockPrefsOnce) {
+ mSkipOverrideUserLockPrefsOnce = false;
+ return false;
+ }
+ // Only override user prefs when returning to the natural rotation (normally portrait).
+ // Don't let apps that force landscape or 180 alter user lock.
+ return rotation == NATURAL_ROTATION;
+ }
+
+ private void rescheduleRotationTimeout(final boolean reasonHover) {
+ // May be called due to a new rotation proposal or a change in hover state
+ if (reasonHover) {
+ // Don't reschedule if a hide animator is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+ // Don't reschedule if not visible
+ if (!mRotationButton.isVisible()) return;
+ }
+
+ // Stop any pending removal
+ mMainThreadHandler.removeCallbacks(mRemoveRotationProposal);
+ // Schedule timeout
+ mMainThreadHandler.postDelayed(mRemoveRotationProposal,
+ computeRotationProposalTimeout());
+ }
+
+ private int computeRotationProposalTimeout() {
+ return mAccessibilityManager.getRecommendedTimeoutMillis(
+ mHoveringRotationSuggestion ? 16000 : 5000,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ }
+
+ private boolean isRotateSuggestionIntroduced() {
+ ContentResolver cr = mContext.getContentResolver();
+ return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0)
+ >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION;
+ }
+
+ private void incrementNumAcceptedRotationSuggestionsIfNeeded() {
+ // Get the number of accepted suggestions
+ ContentResolver cr = mContext.getContentResolver();
+ final int numSuggestions = Settings.Secure.getInt(cr,
+ Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0);
+
+ // Increment the number of accepted suggestions only if it would change intro mode
+ if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) {
+ Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED,
+ numSuggestions + 1);
+ }
+ }
+
+ private class TaskStackListenerImpl extends TaskStackChangeListener {
+ // Invalidate any rotation suggestion on task change or activity orientation change
+ // Note: all callbacks happen on main thread
+
+ @Override
+ public void onTaskStackChanged() {
+ setRotateSuggestionButtonState(false /* visible */);
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ setRotateSuggestionButtonState(false /* visible */);
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ setRotateSuggestionButtonState(false /* visible */);
+ }
+
+ @Override
+ public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+ // Only hide the icon if the top task changes its requestedOrientation
+ // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
+ Optional.ofNullable(ActivityManagerWrapper.getInstance())
+ .map(ActivityManagerWrapper::getRunningTask)
+ .ifPresent(a -> {
+ if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
+ });
+ }
+ }
+
+ enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The rotation button was shown")
+ ROTATION_SUGGESTION_SHOWN(206),
+ @UiEvent(doc = "The rotation button was clicked")
+ ROTATION_SUGGESTION_ACCEPTED(207);
+
+ private final int mId;
+ RotationButtonEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+ }
+}
+
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationContextButton.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationContextButton.java
new file mode 100644
index 0000000..7ad3191
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationContextButton.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.contextual;
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.launcher3.R;
+
+/** Containing logic for the rotation button in nav bar. */
+public class RotationContextButton extends ImageView implements RotationButton {
+
+ private AnimatedVectorDrawable mImageDrawable;
+
+ public RotationContextButton(Context context) {
+ super(context);
+ setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
+ }
+
+ @Override
+ public void setRotationButtonController(RotationButtonController rotationButtonController) {
+ // TODO(b/187754252) UI polish, different icons based on light/dark context, etc
+ mImageDrawable = (AnimatedVectorDrawable) getContext()
+ .getDrawable(rotationButtonController.getIconResId());
+ setImageDrawable(mImageDrawable);
+ mImageDrawable.setCallback(this);
+ }
+
+ @Override
+ public View getCurrentView() {
+ return this;
+ }
+
+ @Override
+ public boolean show() {
+ setVisibility(VISIBLE);
+ return true;
+ }
+
+ @Override
+ public boolean hide() {
+ setVisibility(GONE);
+ return true;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return getVisibility() == VISIBLE;
+ }
+
+ @Override
+ public void updateIcon(int lightIconColor, int darkIconColor) {
+ // TODO(b/187754252): UI Polish
+ }
+
+ @Override
+ public void setOnClickListener(View.OnClickListener onClickListener) {
+ super.setOnClickListener(onClickListener);
+ }
+
+ @Override
+ public void setOnHoverListener(View.OnHoverListener onHoverListener) {
+ super.setOnHoverListener(onHoverListener);
+ }
+
+ @Override
+ public AnimatedVectorDrawable getImageDrawable() {
+ return mImageDrawable;
+ }
+
+ @Override
+ public void setDarkIntensity(float darkIntensity) {
+ // TODO(b/187754252) UI polish
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ if (visibility != View.VISIBLE && mImageDrawable != null) {
+ mImageDrawable.clearAnimationCallbacks();
+ mImageDrawable.reset();
+ }
+
+ // Start the rotation animation once it becomes visible
+ if (visibility == View.VISIBLE && mImageDrawable != null) {
+ mImageDrawable.reset();
+ mImageDrawable.start();
+ }
+ }
+
+ @Override
+ public boolean acceptRotationProposal() {
+ return isAttachedToWindow();
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index c6ea953..180af0b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -21,6 +21,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -72,6 +73,7 @@
private float mProgressMultiplier;
private float mEndDisplacement;
private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
+ private Float mOverrideVelocity = null;
private TaskView mTaskBeingDragged;
@@ -268,6 +270,7 @@
mCurrentAnimation.pause();
}
mFlingBlockCheck.unblockFling();
+ mOverrideVelocity = null;
}
@Override
@@ -283,19 +286,36 @@
mFlingBlockCheck.onEvent();
}
- // Once halfway through task dismissal interpolation, switch from reversible dragging-task
- // animation to playing the remaining task translation animations
- if (mCurrentAnimation.getProgressFraction() < ANIMATION_PROGRESS_FRACTION_MIDPOINT) {
- // Halve the value as we are animating the drag across the full length for only the
- // first half of the progress
- mCurrentAnimation.setPlayFraction(
- Utilities.boundToRange(totalDisplacement * mProgressMultiplier / 2, 0, 1));
+ if (isGoingUp) {
+ if (mCurrentAnimation.getProgressFraction() < ANIMATION_PROGRESS_FRACTION_MIDPOINT) {
+ // Halve the value when dismissing, as we are animating the drag across the full
+ // length for only the first half of the progress
+ mCurrentAnimation.setPlayFraction(
+ Utilities.boundToRange(totalDisplacement * mProgressMultiplier / 2, 0, 1));
+ } else {
+ // Set mOverrideVelocity to control task dismiss velocity in onDragEnd
+ int velocityDimenId = R.dimen.default_task_dismiss_drag_velocity;
+ if (mRecentsView.showAsGrid()) {
+ if (mTaskBeingDragged.isFocusedTask()) {
+ velocityDimenId =
+ R.dimen.default_task_dismiss_drag_velocity_grid_focus_task;
+ } else {
+ velocityDimenId = R.dimen.default_task_dismiss_drag_velocity_grid;
+ }
+ }
+ mOverrideVelocity = -mTaskBeingDragged.getResources().getDimension(velocityDimenId);
+
+ // Once halfway through task dismissal interpolation, switch from reversible
+ // dragging-task animation to playing the remaining task translation animations
+ final long now = SystemClock.uptimeMillis();
+ MotionEvent upAction = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_UP, 0.0f, 0.0f, 0);
+ mDetector.onTouchEvent(upAction);
+ upAction.recycle();
+ }
} else {
- float dragVelocity = -mTaskBeingDragged.getResources().getDimension(
- mRecentsView.showAsGrid() ? R.dimen.default_task_dismiss_drag_velocity_grid
- : R.dimen.default_task_dismiss_drag_velocity);
- onDragEnd(dragVelocity);
- return true;
+ mCurrentAnimation.setPlayFraction(
+ Utilities.boundToRange(totalDisplacement * mProgressMultiplier, 0, 1));
}
return true;
@@ -303,6 +323,10 @@
@Override
public void onDragEnd(float velocity) {
+ if (mOverrideVelocity != null) {
+ velocity = mOverrideVelocity;
+ mOverrideVelocity = null;
+ }
// Limit velocity, as very large scalar values make animations play too quickly
float maxTaskDismissDragVelocity = mTaskBeingDragged.getResources().getDimension(
R.dimen.max_task_dismiss_drag_velocity);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 92e8268..be927e0 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -103,10 +103,10 @@
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -202,7 +202,7 @@
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
public static final long MAX_SWIPE_DURATION = 350;
- public static final long HOME_DURATION = WorkspaceRevealAnim.DURATION_MS;
+ public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
private static final float SWIPE_DURATION_MULTIPLIER =
@@ -394,6 +394,9 @@
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return;
}
+ // RecentsView never updates the display rotation until swipe-up, force update
+ // RecentsOrientedState before passing to TaskViewSimulator.
+ mRecentsView.updateRecentsRotation();
mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState());
// If we've already ended the gesture and are going home, don't prepare recents UI,
@@ -1395,6 +1398,10 @@
mLauncherTransitionController.getNormalController().getAnimationPlayer().end();
mLauncherTransitionController = null;
}
+
+ if (mRecentsView != null) {
+ mRecentsView.abortScrollerAnimation();
+ }
}
/**
@@ -1413,7 +1420,7 @@
private void resetStateForAnimationCancel() {
boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
- mActivityInterface.onTransitionCancelled(wasVisible);
+ mActivityInterface.onTransitionCancelled(wasVisible, mGestureState.getEndTarget());
// Leave the pending invisible flag, as it may be used by wallpaper open animation.
if (mActivity != null) {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index ebf422c..803f036 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -86,12 +86,22 @@
mBackgroundState = backgroundState;
}
- public void onTransitionCancelled(boolean activityVisible) {
+ /**
+ * Called when the current gesture transition is cancelled.
+ * @param activityVisible Whether the user can see the changes we make here, so try to animate.
+ * @param endTarget If the gesture ended before we got cancelled, where we were headed.
+ */
+ public void onTransitionCancelled(boolean activityVisible,
+ @Nullable GestureState.GestureEndTarget endTarget) {
ACTIVITY_TYPE activity = getCreatedActivity();
if (activity == null) {
return;
}
STATE_TYPE startState = activity.getStateManager().getRestState();
+ if (endTarget != null) {
+ // We were on our way to this state when we got canceled, end there instead.
+ startState = stateFromGestureEndTarget(endTarget);
+ }
activity.getStateManager().goToState(startState, activityVisible);
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index e52405b..afafce7 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -261,7 +261,7 @@
}
@Override
- public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) {
+ public void onSplitScreenSecondaryBoundsChanged(Rect bounds, Rect insets) {
WindowBounds wb = new WindowBounds(bounds, insets);
MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
}
@@ -269,8 +269,34 @@
@Override
public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
int backDisposition, boolean showImeSwitcher) {
- MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus(
- displayId, vis, backDisposition, showImeSwitcher));
+ executeForTaskbarManager(() -> mTaskbarManager
+ .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher));
+ }
+
+ @Override
+ public void onRotationProposal(int rotation, boolean isValid) {
+ executeForTaskbarManager(() -> mTaskbarManager.onRotationProposal(rotation, isValid));
+ }
+
+ @Override
+ public void disable(int displayId, int state1, int state2, boolean animate) {
+ executeForTaskbarManager(() -> mTaskbarManager
+ .disable(displayId, state1, state2, animate));
+ }
+
+ @Override
+ public void onSystemBarAttributesChanged(int displayId, int behavior) {
+ executeForTaskbarManager(() -> mTaskbarManager
+ .onSystemBarAttributesChanged(displayId, behavior));
+ }
+
+ private void executeForTaskbarManager(final Runnable r) {
+ MAIN_EXECUTOR.execute(() -> {
+ if (mTaskbarManager == null) {
+ return;
+ }
+ r.run();
+ });
}
public TaskbarManager getTaskbarManager() {
diff --git a/quickstep/src/com/android/quickstep/util/AssistContentRequester.java b/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
index 71c6382..b1e38eb 100644
--- a/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
+++ b/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
@@ -54,6 +54,7 @@
private final IActivityTaskManager mActivityTaskManager;
private final String mPackageName;
private final Executor mCallbackExecutor;
+ private final Executor mSystemInteractionExecutor;
// If system loses the callback, our internal cache of original callback will also get cleared.
private final Map<Object, Callback> mPendingCallbacks =
@@ -63,6 +64,7 @@
mActivityTaskManager = ActivityTaskManager.getService();
mPackageName = context.getApplicationContext().getPackageName();
mCallbackExecutor = Executors.MAIN_EXECUTOR;
+ mSystemInteractionExecutor = Executors.UI_HELPER_EXECUTOR;
}
/**
@@ -71,13 +73,16 @@
* @param taskId to query for the content.
* @param callback to call when the content is available, called on the main thread.
*/
- public void requestAssistContent(int taskId, Callback callback) {
- try {
- mActivityTaskManager.requestAssistDataForTask(
- new AssistDataReceiver(callback, this), taskId, mPackageName);
- } catch (RemoteException e) {
- Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
- }
+ public void requestAssistContent(final int taskId, final Callback callback) {
+ // ActivityTaskManager interaction here is synchronous, so call off the main thread.
+ mSystemInteractionExecutor.execute(() -> {
+ try {
+ mActivityTaskManager.requestAssistDataForTask(
+ new AssistDataReceiver(callback, this), taskId, mPackageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
+ }
+ });
}
private void executeOnMainExecutor(Runnable callback) {
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index badb41a..5cf4f0b 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -78,6 +78,10 @@
}
StateAnimationConfig config = new StateAnimationConfig();
+ if (playWorkspaceRevealAnim) {
+ // WorkspaceRevealAnim handles the depth, so don't interfere.
+ config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+ }
config.duration = startState.getTransitionDuration(mLauncher);
AnimatorSet stateAnim = stateManager.createAtomicAnimation(
startState, NORMAL, config);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 4b0c08a..7cd8d60 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -75,7 +75,6 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
-import android.os.VibrationEffect;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -132,7 +131,6 @@
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.TranslateEdgeEffect;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewPool;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.BaseActivityInterface;
@@ -344,17 +342,6 @@
private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f;
private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
- private static final int SNAP_TO_PAGE_VIBRATION_PRIMITIVE =
- Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
- private static final float SNAP_TO_PAGE_VIBRATION_PRIMITIVE_SCALE = 0.4f;
- private static final VibrationEffect SNAP_TO_PAGE_VIBRATION_FALLBACK =
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK);
- private static final int EDGE_IMPACT_VIBRATION_PRIMITIVE =
- Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
- private static final float EDGE_IMPACT_VIBRATION_PRIMITIVE_SCALE = 0.8f;
- private static final VibrationEffect EDGE_IMPACT_VIBRATION_FALLBACK =
- VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK);
-
protected final RecentsOrientedState mOrientationState;
protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy;
protected RecentsAnimationController mRecentsAnimationController;
@@ -979,8 +966,6 @@
@Override
protected void onPageEndTransition() {
super.onPageEndTransition();
- VibratorWrapper.INSTANCE.get(mContext).vibrate(SNAP_TO_PAGE_VIBRATION_PRIMITIVE,
- SNAP_TO_PAGE_VIBRATION_PRIMITIVE_SCALE, SNAP_TO_PAGE_VIBRATION_FALLBACK);
if (isClearAllHidden()) {
mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
}
@@ -1084,13 +1069,6 @@
}
@Override
- protected void onEdgeAbsorbingScroll() {
- super.onEdgeAbsorbingScroll();
- VibratorWrapper.INSTANCE.get(mContext).vibrate(EDGE_IMPACT_VIBRATION_PRIMITIVE,
- EDGE_IMPACT_VIBRATION_PRIMITIVE_SCALE, EDGE_IMPACT_VIBRATION_FALLBACK);
- }
-
- @Override
protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
// Enables swiping to the left or right only if the task overlay is not modal.
if (!isModal()) {
@@ -2702,19 +2680,20 @@
if (LIVE_TILE.get() && mEnableDrawingLiveTile && newConfig.orientation != mOrientation) {
switchToScreenshot(
() -> finishRecentsAnimation(true /* toRecents */,
- this::onConfigurationChangedInternal));
+ this::updateRecentsRotation));
mEnableDrawingLiveTile = false;
} else {
- onConfigurationChangedInternal();
+ updateRecentsRotation();
}
mOrientation = newConfig.orientation;
}
- private void onConfigurationChangedInternal() {
+ /**
+ * Updates {@link RecentsOrientedState}'s cached RecentsView rotation.
+ */
+ public void updateRecentsRotation() {
final int rotation = mActivity.getDisplay().getRotation();
- if (mOrientationState.setRecentsRotation(rotation)) {
- updateOrientationHandler();
- }
+ mOrientationState.setRecentsRotation(rotation);
}
public void setLayoutRotation(int touchRotation, int displayRotation) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index d5816b5..c97225e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -234,7 +234,7 @@
Rect insets = mActivity.getDragLayer().getInsets();
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
int padding = getResources()
- .getDimensionPixelSize(R.dimen.task_card_menu_option_vertical_padding);
+ .getDimensionPixelSize(R.dimen.task_menu_vertical_padding);
params.width = orientationHandler.getTaskMenuWidth(taskView.getThumbnail()) - (2 * padding);
// Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
params.gravity = Gravity.LEFT;
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 1bfd430..df195d7 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -336,12 +336,20 @@
if (mOverlayEnabled != overlayEnabled) {
mOverlayEnabled = overlayEnabled;
- if (mOverlayEnabled) {
- getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
- mPreviewPositionHelper.mIsOrientationChanged);
- } else {
- getTaskOverlay().reset();
- }
+ refreshOverlay();
+ }
+ }
+
+ /**
+ * Potentially re-init the task overlay. Be cautious when calling this as the overlay may
+ * do processing on initialization.
+ */
+ private void refreshOverlay() {
+ if (mOverlayEnabled) {
+ getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
+ mPreviewPositionHelper.mIsOrientationChanged);
+ } else {
+ getTaskOverlay().reset();
}
}
@@ -382,6 +390,8 @@
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateThumbnailMatrix();
+
+ refreshOverlay();
}
private ColorFilter getColorFilter(float dimAmount) {
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/res/color-night-v31/widgets_picker_surface.xml b/res/color-night-v31/surface.xml
similarity index 100%
rename from res/color-night-v31/widgets_picker_surface.xml
rename to res/color-night-v31/surface.xml
diff --git a/res/color-v31/widgets_picker_surface.xml b/res/color-v31/surface.xml
similarity index 100%
rename from res/color-v31/widgets_picker_surface.xml
rename to res/color-v31/surface.xml
diff --git a/res/color/arrow_tip_view_content.xml b/res/color/arrow_tip_view_content.xml
index 87c733e..7d7f98b 100644
--- a/res/color/arrow_tip_view_content.xml
+++ b/res/color/arrow_tip_view_content.xml
@@ -19,5 +19,5 @@
-->
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/white" />
+ <item android:color="?android:attr/textColorPrimaryInverse" />
</selector>
diff --git a/res/color/button_bg.xml b/res/color/button_bg.xml
new file mode 100644
index 0000000..91eed50
--- /dev/null
+++ b/res/color/button_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** 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.
+*/
+-->
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?android:attr/colorAccent" />
+</selector>
diff --git a/res/color/button_text.xml b/res/color/button_text.xml
new file mode 100644
index 0000000..7d7f98b
--- /dev/null
+++ b/res/color/button_text.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** 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.
+*/
+-->
+<selector
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?android:attr/textColorPrimaryInverse" />
+</selector>
diff --git a/res/color/widgets_picker_surface.xml b/res/color/surface.xml
similarity index 100%
rename from res/color/widgets_picker_surface.xml
rename to res/color/surface.xml
diff --git a/res/drawable-v28/widgets_bottom_sheet_background.xml b/res/drawable-v28/widgets_bottom_sheet_background.xml
index c3009c3..7fb8681 100644
--- a/res/drawable-v28/widgets_bottom_sheet_background.xml
+++ b/res/drawable-v28/widgets_bottom_sheet_background.xml
@@ -16,7 +16,7 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="?android:attr/dialogCornerRadius"
diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
index 16a0767..c3a8269 100644
--- a/res/drawable/add_item_dialog_background.xml
+++ b/res/drawable/add_item_dialog_background.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="?android:attr/dialogCornerRadius" />
diff --git a/res/drawable/add_item_dialog_button_background.xml b/res/drawable/add_item_dialog_button_background.xml
deleted file mode 100644
index 1b4591f..0000000
--- a/res/drawable/add_item_dialog_button_background.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<inset
- android:insetLeft="@dimen/pin_widget_button_inset_horizontal"
- android:insetRight="@dimen/pin_widget_button_inset_horizontal"
- android:insetTop="@dimen/pin_widget_button_inset_vertical"
- android:insetBottom="@dimen/pin_widget_button_inset_vertical"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <ripple
- android:color="?android:attr/colorControlHighlight">
- <item>
- <shape android:tint="?android:attr/colorAccent" android:shape="rectangle">
- <corners android:radius="18dp" />
- <solid android:color="#FFFFFF" />
- <padding
- android:left="@dimen/pin_widget_button_padding_horizontal"
- android:top="@dimen/pin_widget_button_padding_vertical"
- android:right="@dimen/pin_widget_button_padding_horizontal"
- android:bottom="@dimen/pin_widget_button_padding_vertical" />
- </shape>
- </item>
- </ripple>
-</inset>
\ No newline at end of file
diff --git a/res/drawable/arrow_toast_rounded_background.xml b/res/drawable/arrow_toast_rounded_background.xml
index f3f2158..1206ddd 100644
--- a/res/drawable/arrow_toast_rounded_background.xml
+++ b/res/drawable/arrow_toast_rounded_background.xml
@@ -15,5 +15,5 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/arrow_tip_view_bg" />
- <corners android:radius="8dp" />
+ <corners android:radius="@dimen/dialogCornerRadius" />
</shape>
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet.xml b/res/drawable/bg_rounded_corner_bottom_sheet.xml
new file mode 100644
index 0000000..aa49bce
--- /dev/null
+++ b/res/drawable/bg_rounded_corner_bottom_sheet.xml
@@ -0,0 +1,23 @@
+<?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" >
+ <solid android:color="@color/surface" />
+ <corners
+ android:topLeftRadius="@dimen/dialogCornerRadius"
+ android:topRightRadius="@dimen/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_widgets_searchbox.xml b/res/drawable/bg_widgets_searchbox.xml
index 3230ac8..dc6d868 100644
--- a/res/drawable/bg_widgets_searchbox.xml
+++ b/res/drawable/bg_widgets_searchbox.xml
@@ -14,6 +14,6 @@
limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners android:radius="24dp" />
</shape>
\ No newline at end of file
diff --git a/res/drawable/button_bottom_rounded_colored_ripple.xml b/res/drawable/button_bottom_rounded_colored_ripple.xml
new file mode 100644
index 0000000..95f5234
--- /dev/null
+++ b/res/drawable/button_bottom_rounded_colored_ripple.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="4dp"
+ android:topRightRadius="4dp"
+ android:bottomLeftRadius="12dp"
+ android:bottomRightRadius="12dp" />
+ <solid android:color="@color/button_bg"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/button_rounded_colored_ripple.xml b/res/drawable/button_rounded_colored_ripple.xml
new file mode 100644
index 0000000..f6d689f
--- /dev/null
+++ b/res/drawable/button_rounded_colored_ripple.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.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="12dp"/>
+ <solid android:color="@color/button_bg"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/button_top_rounded_bordered_ripple.xml b/res/drawable/button_top_rounded_bordered_ripple.xml
new file mode 100644
index 0000000..f15a4a0
--- /dev/null
+++ b/res/drawable/button_top_rounded_bordered_ripple.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="12dp"
+ android:topRightRadius="12dp"
+ android:bottomLeftRadius="4dp"
+ android:bottomRightRadius="4dp" />
+ <solid android:color="@color/surface"/>
+ <stroke
+ android:width="2dp"
+ android:color="@color/button_bg"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/full_rounded_colored_ripple.xml b/res/drawable/full_rounded_colored_ripple.xml
new file mode 100644
index 0000000..d89537c
--- /dev/null
+++ b/res/drawable/full_rounded_colored_ripple.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.
+-->
+
+<inset
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <ripple
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/button_bg"/>
+ <corners android:radius="28dp"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/widgets_bottom_sheet_background.xml b/res/drawable/widgets_bottom_sheet_background.xml
index 2460767..b877546 100644
--- a/res/drawable/widgets_bottom_sheet_background.xml
+++ b/res/drawable/widgets_bottom_sheet_background.xml
@@ -16,7 +16,7 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/default_dialog_corner_radius"
android:topRightRadius="@dimen/default_dialog_corner_radius"
diff --git a/res/drawable/widgets_list_bottom_ripple.xml b/res/drawable/widgets_list_bottom_ripple.xml
index 971d6f3..c2debb1 100644
--- a/res/drawable/widgets_list_bottom_ripple.xml
+++ b/res/drawable/widgets_list_bottom_ripple.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_content_corner_radius"
android:topRightRadius="@dimen/widget_list_content_corner_radius"
@@ -31,7 +31,7 @@
</item>
<item android:id="@android:id/background">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_content_corner_radius"
android:topRightRadius="@dimen/widget_list_content_corner_radius"
diff --git a/res/drawable/widgets_list_middle_ripple.xml b/res/drawable/widgets_list_middle_ripple.xml
index 2b77d4d..83f96a0 100644
--- a/res/drawable/widgets_list_middle_ripple.xml
+++ b/res/drawable/widgets_list_middle_ripple.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_content_corner_radius"
android:topRightRadius="@dimen/widget_list_content_corner_radius"
@@ -32,7 +32,7 @@
<item android:id="@android:id/background">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_content_corner_radius"
android:topRightRadius="@dimen/widget_list_content_corner_radius"
diff --git a/res/drawable/widgets_list_single_item_ripple.xml b/res/drawable/widgets_list_single_item_ripple.xml
index c09944d..a4223a8 100644
--- a/res/drawable/widgets_list_single_item_ripple.xml
+++ b/res/drawable/widgets_list_single_item_ripple.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
@@ -31,7 +31,7 @@
</item>
<item android:id="@android:id/background">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
diff --git a/res/drawable/widgets_list_top_ripple.xml b/res/drawable/widgets_list_top_ripple.xml
index f79ab72..bc0876e 100644
--- a/res/drawable/widgets_list_top_ripple.xml
+++ b/res/drawable/widgets_list_top_ripple.xml
@@ -21,7 +21,7 @@
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
@@ -32,7 +32,7 @@
<item android:id="@android:id/background">
<shape android:shape="rectangle">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners
android:topLeftRadius="@dimen/widget_list_top_bottom_corner_radius"
android:topRightRadius="@dimen/widget_list_top_bottom_corner_radius"
diff --git a/res/drawable/widgets_recommendation_background.xml b/res/drawable/widgets_recommendation_background.xml
index b59de27..0550a34 100644
--- a/res/drawable/widgets_recommendation_background.xml
+++ b/res/drawable/widgets_recommendation_background.xml
@@ -19,6 +19,6 @@
-->
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/widgets_picker_surface" />
+ <solid android:color="@color/surface" />
<corners android:radius="@dimen/widget_list_top_bottom_corner_radius"/>
</shape>
\ 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 0a3fbbc..ddc9815 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -53,7 +53,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:paddingVertical="8dp"
+ android:paddingTop="8dp"
android:text="@string/add_item_request_drag_hint"
android:textSize="14sp"
android:textColor="?android:attr/textColorSecondary"
@@ -70,28 +70,33 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="end"
- android:padding="8dp"
+ android:gravity="center_vertical|end"
+ android:paddingHorizontal="24dp"
+ android:paddingVertical="8dp"
android:orientation="horizontal">
<Button
- style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
+ style="@style/Button.FullRounded.Colored"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="36dp"
android:paddingHorizontal="16dp"
- android:onClick="onCancelClick"
- android:text="@android:string/cancel" />
+ android:textSize="14sp"
+ android:textColor="@color/button_text"
+ android:text="@android:string/cancel"
+ android:onClick="onCancelClick"/>
<Space
- android:layout_width="4dp"
+ android:layout_width="8dp"
android:layout_height="wrap_content" />
<Button
- style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
+ style="@style/Button.FullRounded.Colored"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="36dp"
android:paddingHorizontal="16dp"
- android:onClick="onPlaceAutomaticallyClick"
- android:text="@string/add_to_home_screen"/>
+ android:textSize="14sp"
+ android:textColor="@color/button_text"
+ android:text="@string/add_to_home_screen"
+ android:onClick="onPlaceAutomaticallyClick"/>
</LinearLayout>
</com.android.launcher3.widget.AddItemWidgetsBottomSheet>
diff --git a/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
index c071bec..aee00a9 100644
--- a/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -14,46 +14,22 @@
limitations under the License.
-->
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content">
- <LinearLayout
+ <TextView
+ android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingStart="24dp"
- android:paddingEnd="4dp"
- android:background="@drawable/arrow_toast_rounded_background"
android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:padding="16dp"
+ android:background="@drawable/arrow_toast_rounded_background"
android:elevation="2dp"
- android:orientation="horizontal">
-
- <TextView
- android:id="@+id/text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:paddingTop="5dp"
- android:paddingBottom="5dp"
- android:gravity="center"
- android:layout_gravity="center_vertical"
- android:textColor="@color/arrow_tip_view_content"
- android:textSize="16sp" />
-
- <ImageView
- android:id="@+id/dismiss"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:layout_gravity="center_vertical"
- android:padding="10dp"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:alpha="0.7"
- android:src="@drawable/ic_remove_no_shadow"
- android:tint="@color/arrow_tip_view_content"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:contentDescription="@string/accessibility_close" />
- </LinearLayout>
+ android:textColor="@color/arrow_tip_view_content"
+ android:textSize="14sp"/>
<View
android:id="@+id/arrow"
diff --git a/res/layout/settings_activity.xml b/res/layout/settings_activity.xml
new file mode 100644
index 0000000..c70d5bf
--- /dev/null
+++ b/res/layout/settings_activity.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <Toolbar
+ android:id="@+id/action_bar"
+ style="?android:attr/actionBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/actionBarTheme" />
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/widgets_edu.xml b/res/layout/widgets_edu.xml
new file mode 100644
index 0000000..280c095
--- /dev/null
+++ b/res/layout/widgets_edu.xml
@@ -0,0 +1,66 @@
+<?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.
+-->
+
+<com.android.launcher3.views.WidgetsEduView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:gravity="bottom"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/edu_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/bg_rounded_corner_bottom_sheet"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/bottom_sheet_edu_padding"
+ android:paddingTop="@dimen/bottom_sheet_edu_padding">
+
+ <TextView
+ style="@style/TextHeadline"
+ android:id="@+id/edu_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:text="@string/widget_education_header"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"
+ android:layout_marginBottom="16dp"/>
+
+ <TextView
+ android:id="@+id/edu_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:text="@string/widget_education_content"
+ android:textSize="14sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:layout_marginBottom="24dp"/>
+
+ <Button
+ android:id="@+id/edu_close_button"
+ style="@style/Button.Rounded.Colored"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:text="@string/widget_education_close_button"
+ android:textSize="16sp"
+ android:textColor="@color/button_text"
+ android:layout_marginBottom="8dp"/>
+ </LinearLayout>
+</com.android.launcher3.views.WidgetsEduView>
\ No newline at end of file
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index a01aa2c..dca3e79 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -25,8 +25,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?android:attr/colorBackground"
- android:elevation="4dp">
+ android:background="?android:attr/colorBackground">
<TextView
android:id="@+id/no_widgets_text"
diff --git a/res/values-v28/dimens.xml b/res/values-v28/dimens.xml
new file mode 100644
index 0000000..ffa8cc4
--- /dev/null
+++ b/res/values-v28/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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>
+ <dimen name="dialogCornerRadius">?android:attr/dialogCornerRadius</dimen>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c5f36ce..d6a6f43 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -124,6 +124,11 @@
<dimen name="recommended_widgets_table_vertical_padding">8dp</dimen>
+ <!-- Bottom margin for the search and recommended widgets container without work profile -->
+ <dimen name="search_and_recommended_widgets_container_bottom_margin">16dp</dimen>
+ <!-- Bottom margin for the search and recommended widgets container with work profile -->
+ <dimen name="search_and_recommended_widgets_container_small_bottom_margin">10dp</dimen>
+
<dimen name="widget_list_top_bottom_corner_radius">28dp</dimen>
<dimen name="widget_list_content_corner_radius">4dp</dimen>
@@ -144,10 +149,10 @@
<dimen name="widget_row_padding">8dp</dimen>
<dimen name="widget_row_divider">2dp</dimen>
- <dimen name="widget_picker_education_tip_width">120dp</dimen>
+ <dimen name="widget_picker_education_tip_max_width">308dp</dimen>
<dimen name="widget_picker_education_tip_min_margin">4dp</dimen>
- <dimen name="widget_picker_view_pager_top_padding">16dp</dimen>
+ <dimen name="widget_picker_view_pager_top_padding">10dp</dimen>
<!-- Padding applied to shortcut previews -->
<dimen name="shortcut_preview_padding_left">0dp</dimen>
@@ -283,6 +288,7 @@
<!-- Theming related -->
<dimen name="default_dialog_corner_radius">8dp</dimen>
+ <dimen name="dialogCornerRadius">@dimen/default_dialog_corner_radius</dimen>
<!-- Onboarding bottomsheet related -->
<dimen name="bottom_sheet_edu_padding">24dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index eae32b7..c851cf8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -99,6 +99,15 @@
<!-- A widget category label for grouping widgets related to conversations. [CHAR_LIMIT=30] -->
<string name="widget_category_conversations">Conversations</string>
+ <!-- Title of a dialog. This dialog lets a user know how they can use widgets on their phone.
+ [CHAR_LIMIT=NONE] -->
+ <string name="widget_education_header">Useful info at your fingertips</string>
+ <!-- Dialog text. This dialog lets a user know how they can use widgets on their phone.
+ [CHAR_LIMIT=NONE] -->
+ <string name="widget_education_content">To get info without opening apps, you can add widgets to your Home screen</string>
+ <!-- Text on the button that closes the education dialog about widgets. [CHAR_LIMIT=50] -->
+ <string name="widget_education_close_button">Got it</string>
+
<!-- All Apps -->
<!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
<string name="all_apps_search_bar_hint">Search apps</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index df617ea..92824ad 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -48,7 +48,7 @@
<item name="workspaceStatusBarScrim">@drawable/workspace_bg</item>
<item name="widgetsTheme">@style/WidgetContainerTheme</item>
<item name="folderDotColor">?android:attr/colorPrimary</item>
- <item name="folderFillColor">?android:attr/colorBackground</item>
+ <item name="folderFillColor">?android:attr/colorBackgroundFloating</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="folderTextColor">?android:attr/textColorPrimary</item>
<item name="isFolderDarkText">true</item>
@@ -71,7 +71,7 @@
</style>
<style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
- <item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
+ <item name="folderFillColor">?android:attr/colorBackgroundFloating</item>
<item name="folderTextColor">?attr/workspaceTextColor</item>
<item name="isFolderDarkText">?attr/isWorkspaceDarkText</item>
<item name="folderHintColor">@color/folder_hint_text_color_dark</item>
@@ -87,7 +87,7 @@
<item name="isWorkspaceDarkText">true</item>
<item name="workspaceStatusBarScrim">@null</item>
<item name="folderDotColor">#FF464646</item>
- <item name="folderFillColor">#CDFFFFFF</item>
+ <item name="folderFillColor">?android:attr/colorBackgroundFloating</item>
<item name="folderIconBorderColor">#FF80868B</item>
<item name="folderTextColor">?attr/workspaceTextColor</item>
<item name="isFolderDarkText">true</item>
@@ -109,7 +109,7 @@
<item name="popupColorTertiary">@color/popup_color_tertiary_dark</item>
<item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
<item name="folderDotColor">?android:attr/colorPrimary</item>
- <item name="folderFillColor">?android:attr/colorBackground</item>
+ <item name="folderFillColor">?android:attr/colorBackgroundFloating</item>
<item name="folderIconBorderColor">?android:attr/colorPrimary</item>
<item name="folderTextColor">?android:attr/textColorPrimary</item>
<item name="isFolderDarkText">false</item>
@@ -123,7 +123,7 @@
</style>
<style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
- <item name="folderFillColor">#FF3C4043</item> <!-- 100% GM2 800 -->
+ <item name="folderFillColor">?android:attr/colorBackgroundFloating</item>
<item name="folderTextColor">@android:color/white</item>
<item name="isFolderDarkText">false</item>
<item name="folderHintColor">@color/folder_hint_text_color_light</item>
@@ -132,7 +132,7 @@
<style name="LauncherTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark">
<item name="android:colorControlHighlight">#19212121</item>
- <item name="folderFillColor">#CDFFFFFF</item>
+ <item name="folderFillColor">?android:attr/colorBackgroundFloating</item>
<item name="folderTextColor">?attr/workspaceTextColor</item>
<item name="isFolderDarkText">?attr/isWorkspaceDarkText</item>
<item name="folderHintColor">@color/folder_hint_text_color_dark</item>
@@ -156,6 +156,8 @@
<style name="HomeSettingsTheme" 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>
</style>
@@ -240,7 +242,7 @@
<!-- Icon displayed on the workspace -->
- <style name="BaseIcon.Workspace" >
+ <style name="BaseIcon.Workspace.Shadows" parent="BaseIcon">
<item name="android:shadowRadius">2.0</item>
<item name="android:shadowColor">?attr/workspaceShadowColor</item>
<item name="ambientShadowColor">?attr/workspaceAmbientShadowColor</item>
@@ -251,6 +253,10 @@
<item name="keyShadowOffsetY">.5dp</item>
</style>
+ <!-- Intentionally empty so we can override -->
+ <style name="BaseIcon.Workspace" parent="BaseIcon.Workspace.Shadows">
+ </style>
+
<!-- Theme for the popup container -->
<style name="PopupItem">
<item name="android:colorControlHighlight">?attr/popupColorTertiary</item>
@@ -287,8 +293,24 @@
<item name="android:colorForeground">@color/all_apps_bg_hand_fill_dark</item>
</style>
- <style name="Widget.DeviceDefault.Button.Rounded.Colored" parent="@android:style/Widget.DeviceDefault.Button.Colored">
- <item name="android:background">@drawable/add_item_dialog_button_background</item>
+ <style name="Button.TopRounded.Bordered" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/button_top_rounded_bordered_ripple</item>
+ <item name="android:stateListAnimator">@null</item>
+ </style>
+
+ <style name="Button.BottomRounded.Colored" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/button_bottom_rounded_colored_ripple</item>
+ <item name="android:stateListAnimator">@null</item>
+ </style>
+
+ <style name="Button.Rounded.Colored" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/button_rounded_colored_ripple</item>
+ <item name="android:stateListAnimator">@null</item>
+ </style>
+
+ <style name="Button.FullRounded.Colored" parent="@android:style/Widget.Material.Button">
+ <item name="android:background">@drawable/full_rounded_colored_ripple</item>
+ <item name="android:stateListAnimator">@null</item>
</style>
<style name="AddItemActivityTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
diff --git a/res/xml/size_limits_80x104.xml b/res/xml/size_limits_80x104.xml
index f375549..f5ca757 100644
--- a/res/xml/size_limits_80x104.xml
+++ b/res/xml/size_limits_80x104.xml
@@ -62,10 +62,10 @@
<device-padding
launcher:maxEmptySpace="9999dp">
<workspaceTopPadding
- launcher:a="0.38"
+ launcher:a="0.40"
launcher:c="36dp"/>
<workspaceBottomPadding
- launcher:a="0.62"
+ launcher:a="0.60"
launcher:c="36dp"/>
<hotseatBottomPadding
launcher:a="0"
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 9100947..4979b40 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -63,7 +63,8 @@
TYPE_TASK_MENU,
TYPE_OPTIONS_POPUP,
TYPE_ICON_SURFACE,
- TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
+ TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP,
+ TYPE_WIDGETS_EDUCATION_DIALOG
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
@@ -85,17 +86,19 @@
public static final int TYPE_ICON_SURFACE = 1 << 13;
public static final int TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP = 1 << 14;
+ public static final int TYPE_WIDGETS_EDUCATION_DIALOG = 1 << 15;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
- | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP;
+ | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
+ | TYPE_WIDGETS_EDUCATION_DIALOG;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
- | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE;
+ | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_WIDGETS_EDUCATION_DIALOG;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index d977620..97a44c1 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -253,6 +253,15 @@
forceFinishScroller(true);
}
+ /**
+ * Immediately finishes any overscroll effect and jumps to the end of the scroller animation.
+ */
+ public void abortScrollerAnimation() {
+ mEdgeGlowLeft.finish();
+ mEdgeGlowRight.finish();
+ abortScrollerAnimation(true);
+ }
+
private void abortScrollerAnimation(boolean resetNextPage) {
mScroller.abortAnimation();
// We need to clean up the next page here to avoid computeScrollHelper from
@@ -506,11 +515,9 @@
if (newPos < mMinScroll && oldPos >= mMinScroll) {
mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
- onEdgeAbsorbingScroll();
} else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
- onEdgeAbsorbingScroll();
}
}
@@ -1361,13 +1368,6 @@
protected void onNotSnappingToPageInFreeScroll() { }
- /**
- * Called when the view edges absorb part of the scroll. Subclasses can override this
- * to provide custom behavior during animation.
- */
- protected void onEdgeAbsorbingScroll() {
- }
-
protected boolean shouldFlingForVelocity(int velocity) {
float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
return Math.abs(velocity) > threshold;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 78e8048..9a8f3dd 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -106,6 +106,7 @@
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.OverlayEdgeEffect;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
import com.android.launcher3.widget.LauncherAppWidgetHost;
@@ -429,10 +430,9 @@
// When a accessible drag is started by the folder, we only allow rearranging withing the
// folder.
boolean addNewPage = !(options.isAccessibleDrag && dragObject.dragSource != this);
-
if (addNewPage) {
mDeferRemoveExtraEmptyScreen = false;
- addExtraEmptyScreenOnDrag();
+ addExtraEmptyScreenOnDrag(dragObject);
if (dragObject.dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
&& dragObject.dragSource != this) {
@@ -640,12 +640,19 @@
return newScreen;
}
- public void addExtraEmptyScreenOnDrag() {
+ private void addExtraEmptyScreenOnDrag(DragObject dragObject) {
boolean lastChildOnScreen = false;
boolean childOnFinalScreen = false;
if (mDragSourceInternal != null) {
- if (mDragSourceInternal.getChildCount() == 1) {
+ // When the drag view content is a LauncherAppWidgetHostView, we should increment the
+ // drag source child count by 1 because the widget in drag has been detached from its
+ // original parent, ShortcutAndWidgetContainer, and reattached to the DragView.
+ int dragSourceChildCount =
+ dragObject.dragView.getContentView() instanceof LauncherAppWidgetHostView
+ ? mDragSourceInternal.getChildCount() + 1
+ : mDragSourceInternal.getChildCount();
+ if (dragSourceChildCount == 1) {
lastChildOnScreen = true;
}
CellLayout cl = (CellLayout) mDragSourceInternal.getParent();
@@ -1925,10 +1932,16 @@
if (droppedOnOriginalCellDuringTransition) {
// Animate the item to its original position, while simultaneously exiting
// spring-loaded mode so the page meets the icon where it was picked up.
+ final RunnableList callbackList = new RunnableList();
+ final Runnable onCompleteCallback = onCompleteRunnable;
mLauncher.getDragController().animateDragViewToOriginalPosition(
- onCompleteRunnable, cell,
+ /* onComplete= */ callbackList::executeAllAndDestroy, cell,
SPRING_LOADED.getTransitionDuration(mLauncher));
- mLauncher.getStateManager().goToState(NORMAL);
+ mLauncher.getStateManager().goToState(NORMAL, /* delay= */ 0,
+ onCompleteCallback == null
+ ? null
+ : forSuccessCallback(
+ () -> callbackList.add(onCompleteCallback)));
mLauncher.getDropTargetBar().onDragEnd();
parent.onDropChild(cell);
return;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 0336c50..cebdc1f 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -238,7 +238,7 @@
"Sends a notification whenever launcher encounters an uncaught exception.");
public static final BooleanFlag PROTOTYPE_APP_CLOSE = getDebugFlag(
- "PROTOTYPE_APP_CLOSE", true, "Enables new app close");
+ "PROTOTYPE_APP_CLOSE", false, "Enables new app close");
public static final BooleanFlag ENABLE_WALLPAPER_SCRIM = getDebugFlag(
"ENABLE_WALLPAPER_SCRIM", false,
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 5ddf84f..4eab63e 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -89,8 +89,8 @@
private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
// Expressed on a scale from 0 to 255.
- private static final int BG_OPACITY = 160;
- private static final int MAX_BG_OPACITY = 225;
+ private static final int BG_OPACITY = 255;
+ private static final int MAX_BG_OPACITY = 255;
private static final int SHADOW_OPACITY = 40;
private ValueAnimator mScaleAnimator;
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 8ca157b..e4f5539 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -1,6 +1,7 @@
package com.android.launcher3.graphics;
import static com.android.launcher3.Utilities.getPrefs;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS;
import static com.android.launcher3.util.Themes.isThemedIconEnabled;
@@ -18,7 +19,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
-import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.util.ArrayMap;
@@ -217,7 +217,8 @@
Bundle result = new Bundle();
result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage());
- Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(), observer));
+ Messenger messenger =
+ new Messenger(new Handler(UI_HELPER_EXECUTOR.getLooper(), observer));
Message msg = Message.obtain();
msg.replyTo = messenger;
result.putParcelable(KEY_CALLBACK, msg);
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 85c2492..05927ef 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -80,6 +80,8 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setContentView(R.layout.settings_activity);
+ setActionBar(findViewById(R.id.action_bar));
if (savedInstanceState == null) {
Intent intent = getIntent();
@@ -98,7 +100,7 @@
getPreferenceFragment());
f.setArguments(args);
// Display the fragment as the main content.
- fm.beginTransaction().replace(android.R.id.content, f).commit();
+ fm.beginTransaction().replace(R.id.content_frame, f).commit();
}
Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this);
}
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index 14bf6c2..b0defd4 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -21,19 +21,15 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
-import android.media.AudioAttributes;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
-import com.android.launcher3.Utilities;
-
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
*/
@@ -43,11 +39,6 @@
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
new MainThreadInitializedObject<>(VibratorWrapper::new);
- public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build();
-
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
@@ -90,24 +81,4 @@
UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
}
}
-
- /**
- * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
- * vibrates if haptic feedback is available and enabled.
- */
- @SuppressLint("NewApi")
- public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) {
- if (mHasVibrator && mIsHapticFeedbackEnabled) {
- UI_HELPER_EXECUTOR.execute(() -> {
- if (Utilities.ATLEAST_R && primitiveId >= 0
- && mVibrator.areAllPrimitivesSupported(primitiveId)) {
- mVibrator.vibrate(VibrationEffect.startComposition()
- .addPrimitive(primitiveId, primitiveScale)
- .compose(), VIBRATION_ATTRS);
- } else {
- mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS);
- }
- });
- }
- }
}
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index ef3df5f..07d3776 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -105,10 +105,6 @@
private void init(Context context) {
inflate(context, R.layout.arrow_toast, this);
setOrientation(LinearLayout.VERTICAL);
- View dismissButton = findViewById(R.id.dismiss);
- dismissButton.setOnClickListener(view -> {
- handleClose(true);
- });
View arrowView = findViewById(R.id.arrow);
ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
@@ -194,18 +190,18 @@
public ArrowTipView showAtLocation(String text, int arrowXCoord, int yCoord) {
ViewGroup parent = mActivity.getDragLayer();
@Px int parentViewWidth = parent.getWidth();
- @Px int textViewWidth = getContext().getResources()
- .getDimensionPixelSize(R.dimen.widget_picker_education_tip_width);
+ @Px int maxTextViewWidth = getContext().getResources()
+ .getDimensionPixelSize(R.dimen.widget_picker_education_tip_max_width);
@Px int minViewMargin = getContext().getResources()
.getDimensionPixelSize(R.dimen.widget_picker_education_tip_min_margin);
- if (parentViewWidth < textViewWidth + 2 * minViewMargin) {
+ if (parentViewWidth < maxTextViewWidth + 2 * minViewMargin) {
Log.w(TAG, "Cannot display tip on a small screen of size: " + parentViewWidth);
return null;
}
TextView textView = findViewById(R.id.text);
textView.setText(text);
- textView.setWidth(textViewWidth);
+ textView.setMaxWidth(maxTextViewWidth);
parent.addView(this);
requestLayout();
diff --git a/src/com/android/launcher3/views/WidgetsEduView.java b/src/com/android/launcher3/views/WidgetsEduView.java
new file mode 100644
index 0000000..e69cb5b
--- /dev/null
+++ b/src/com/android/launcher3/views/WidgetsEduView.java
@@ -0,0 +1,118 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+
+/**
+ * Education view about widgets.
+ */
+public class WidgetsEduView extends AbstractSlideInView<Launcher> implements Insettable {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+
+ protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
+
+ private Rect mInsets = new Rect();
+ private View mEduView;
+
+
+ public WidgetsEduView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public WidgetsEduView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContent = this;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ handleClose(true, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_WIDGETS_EDUCATION_DIALOG) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mEduView = findViewById(R.id.edu_view);
+ findViewById(R.id.edu_close_button)
+ .setOnClickListener(v -> close(/* animate= */ true));
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mEduView.setPaddingRelative(mEduView.getPaddingStart(),
+ mEduView.getPaddingTop(), mEduView.getPaddingEnd(), bottomInset);
+ }
+
+ private void show() {
+ attachToContainer();
+ animateOpen();
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return FINAL_SCRIM_BG_COLOR;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ setTranslationShift(mTranslationShift);
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+
+ /** Shows widget education dialog. */
+ public static WidgetsEduView showEducationDialog(Launcher launcher) {
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ WidgetsEduView v = (WidgetsEduView) layoutInflater.inflate(
+ R.layout.widgets_edu, launcher.getDragLayer(), false);
+ v.show();
+ return v;
+ }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index fb6ac0e..92f84da 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -49,11 +49,14 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.TopRoundedCornerView;
+import com.android.launcher3.views.WidgetsEduView;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListener;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
@@ -79,12 +82,16 @@
private static final long DEFAULT_OPEN_DURATION = 267;
private static final long FADE_IN_DURATION = 150;
private static final long EDUCATION_TIP_DELAY_MS = 200;
+ private static final long EDUCATION_DIALOG_DELAY_MS = 500;
private static final float VERTICAL_START_POSITION = 0.3f;
// The widget recommendation table can easily take over the entire screen on devices with small
// resolution or landscape on phone. This ratio defines the max percentage of content area that
// the table can display.
private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
+ private static final String KEY_WIDGETS_EDUCATION_DIALOG_SEEN =
+ "launcher.widgets_education_dialog_seen";
+
private final Rect mInsets = new Rect();
private final boolean mHasWorkProfile;
private final SparseArray<AdapterHolder> mAdapters = new SparseArray();
@@ -93,6 +100,7 @@
entry -> mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
mPrimaryWidgetsFilter.negate();
+ @Nullable private ArrowTipView mLatestEducationalTip;
private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
new OnLayoutChangeListener() {
@Override
@@ -116,13 +124,15 @@
removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
return;
}
- View viewForTip = getViewToShowEducationTip();
- if (showEducationTipOnViewIfPossible(viewForTip) != null) {
+ mLatestEducationalTip = showEducationTipOnViewIfPossible(getViewToShowEducationTip());
+ if (mLatestEducationalTip != null) {
removeOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
}
};
+
private final int mTabsHeight;
private final int mViewPagerTopPadding;
+ private final int mSearchAndRecommendationContainerBottomMargin;
private final int mWidgetCellHorizontalPadding;
@Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
@@ -149,6 +159,10 @@
? getContext().getResources()
.getDimensionPixelSize(R.dimen.widget_picker_view_pager_top_padding)
: 0;
+ mSearchAndRecommendationContainerBottomMargin = getContext().getResources()
+ .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(
R.dimen.widget_cell_horizontal_padding);
}
@@ -192,6 +206,11 @@
mNoWidgetsView = findViewById(R.id.no_widgets_text);
mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder(
findViewById(R.id.search_and_recommendations_container));
+ TopRoundedCornerView.LayoutParams layoutParams =
+ (TopRoundedCornerView.LayoutParams)
+ mSearchAndRecommendationViewHolder.mContainer.getLayoutParams();
+ layoutParams.bottomMargin = mSearchAndRecommendationContainerBottomMargin;
+ mSearchAndRecommendationViewHolder.mContainer.setLayoutParams(layoutParams);
mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController(
mHasWorkProfile,
mTabsHeight,
@@ -211,9 +230,7 @@
mSearchAndRecommendationViewHolder.mSearchBar.initialize(
mActivityContext.getPopupDataProvider(), /* searchModeListener= */ this);
- if (!hasSeenEducationTip()) {
- addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
- }
+ setUpEducationViewsIfNeeded();
}
@Override
@@ -598,6 +615,10 @@
@Override
protected void onCloseComplete() {
super.onCloseComplete();
+ removeCallbacks(mShowEducationTipTask);
+ if (mLatestEducationalTip != null) {
+ mLatestEducationalTip.close(false);
+ }
AccessibilityManagerCompat.sendStateEventToTest(getContext(), NORMAL_STATE_ORDINAL);
}
@@ -670,6 +691,38 @@
return null;
}
+ /** Shows education dialog for widgets. */
+ private WidgetsEduView showEducationDialog() {
+ mActivityContext.getSharedPrefs().edit()
+ .putBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, true).apply();
+ return WidgetsEduView.showEducationDialog(mActivityContext);
+ }
+
+ /** Returns {@code true} if education dialog has previously been shown. */
+ protected boolean hasSeenEducationDialog() {
+ return mActivityContext.getSharedPrefs()
+ .getBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, false)
+ || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+ }
+
+ private void setUpEducationViewsIfNeeded() {
+ if (!hasSeenEducationDialog()) {
+ postDelayed(() -> {
+ WidgetsEduView eduDialog = showEducationDialog();
+ eduDialog.addOnCloseListener(() -> {
+ if (!hasSeenEducationTip()) {
+ addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
+ // Call #requestLayout() to trigger layout change listener in order to show
+ // arrow tip immediately if there is a widget to show it on.
+ requestLayout();
+ }
+ });
+ }, EDUCATION_DIALOG_DELAY_MS);
+ } else if (!hasSeenEducationTip()) {
+ addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
+ }
+ }
+
/** A holder class for holding adapters & their corresponding recycler view. */
private final class AdapterHolder {
static final int PRIMARY = 0;
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 4cf52f0..c99a81f 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -105,7 +105,7 @@
static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
private final String mLauncherPackage;
- private final boolean mIsLauncher3;
+ private Boolean mIsLauncher3;
private long mTestStartTime = -1;
// Types for launcher containers that the user is interacting with. "Background" is a
@@ -206,7 +206,6 @@
public LauncherInstrumentation(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
mDevice = UiDevice.getInstance(instrumentation);
- mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName());
// 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
@@ -1422,6 +1421,9 @@
}
boolean isLauncher3() {
+ if (mIsLauncher3 == null) {
+ mIsLauncher3 = "com.android.launcher3".equals(getLauncherPackageName());
+ }
return mIsLauncher3;
}