Snap for 7682106 from 5025ecb8b6e9d37ef2114299249c6e8c71efca01 to sc-v2-release
Change-Id: I19ddf34141694e4c8f95e5251442f6a1303cf5b1
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 4eecf29..eee6db5 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -30,7 +30,6 @@
with some minor changed based on the derivative app.
-->
- <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
new file mode 100644
index 0000000..47cbd9f
--- /dev/null
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<ripple android:color="?android:attr/colorControlHighlight"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item>
+ <shape android:shape="rectangle"
+ android:tint="?colorButtonNormal">
+ <corners android:radius="18dp" />
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/quickstep/res/drawable/taskbar_edu_splitscreen.png b/quickstep/res/drawable/taskbar_edu_splitscreen.png
new file mode 100644
index 0000000..f9d2a63
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_edu_splitscreen.png
Binary files differ
diff --git a/quickstep/res/drawable/taskbar_edu_splitscreen.webp b/quickstep/res/drawable/taskbar_edu_splitscreen.webp
deleted file mode 100644
index 2f4402f..0000000
--- a/quickstep/res/drawable/taskbar_edu_splitscreen.webp
+++ /dev/null
Binary files differ
diff --git a/quickstep/res/drawable/taskbar_edu_stashing.png b/quickstep/res/drawable/taskbar_edu_stashing.png
new file mode 100644
index 0000000..f9d2a63
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_edu_stashing.png
Binary files differ
diff --git a/quickstep/res/drawable/taskbar_edu_switch_apps.png b/quickstep/res/drawable/taskbar_edu_switch_apps.png
new file mode 100644
index 0000000..f9d2a63
--- /dev/null
+++ b/quickstep/res/drawable/taskbar_edu_switch_apps.png
Binary files differ
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 1ee726e..1dea57e 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -16,7 +16,7 @@
-->
<com.android.quickstep.views.ClearAllButton
xmlns:android="http://schemas.android.com/apk/res/android"
- style="@android:style/Widget.DeviceDefault.Button.Borderless"
+ style="@style/OverviewClearAllButton"
android:id="@+id/clear_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/quickstep/res/layout/taskbar_edu.xml b/quickstep/res/layout/taskbar_edu.xml
index ef57a53..3796ff9 100644
--- a/quickstep/res/layout/taskbar_edu.xml
+++ b/quickstep/res/layout/taskbar_edu.xml
@@ -43,6 +43,26 @@
launcher:pageIndicator="@+id/content_page_indicator">
<LinearLayout
+ android:id="@+id/page_switch_apps"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.TaskbarEdu.Title"
+ android:text="@string/taskbar_edu_switch_apps"/>
+
+ <ImageView
+ android:layout_width="322dp"
+ android:layout_height="282dp"
+ android:layout_marginTop="16dp"
+ android:src="@drawable/taskbar_edu_switch_apps"/>
+ </LinearLayout>
+
+ <LinearLayout
android:id="@+id/page_splitscreen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -52,14 +72,8 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="16dp"
- android:gravity="center_horizontal"
- style="@style/TextHeadline"
- android:text="@string/taskbar_edu_splitscreen"
- android:fontFamily="google-sans"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"
- android:maxLines="2"/>
+ style="@style/TextAppearance.TaskbarEdu.Title"
+ android:text="@string/taskbar_edu_splitscreen"/>
<ImageView
android:layout_width="322dp"
@@ -69,7 +83,7 @@
</LinearLayout>
<LinearLayout
- android:id="@+id/page_customize"
+ android:id="@+id/page_stashing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -78,46 +92,14 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginHorizontal="16dp"
- android:gravity="center_horizontal"
- style="@style/TextHeadline"
- android:text="@string/taskbar_edu_customize"
- android:fontFamily="google-sans"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"
- android:maxLines="2"/>
+ style="@style/TextAppearance.TaskbarEdu.Title"
+ android:text="@string/taskbar_edu_stashing"/>
<ImageView
android:layout_width="322dp"
android:layout_height="282dp"
android:layout_marginTop="16dp"
- android:src="@drawable/taskbar_edu_splitscreen"/>
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/page_dock"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center_horizontal">
-
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="16dp"
- android:gravity="center_horizontal"
- style="@style/TextHeadline"
- android:text="@string/taskbar_edu_dock"
- android:fontFamily="google-sans"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"
- android:maxLines="2"/>
-
- <ImageView
- android:layout_width="322dp"
- android:layout_height="282dp"
- android:layout_marginTop="16dp"
- android:src="@drawable/taskbar_edu_splitscreen"/>
+ android:src="@drawable/taskbar_edu_stashing"/>
</LinearLayout>
</com.android.launcher3.taskbar.TaskbarEduPagedView>
@@ -126,6 +108,7 @@
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_marginBottom="92dp"
+ android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -146,10 +129,9 @@
<Button
android:id="@+id/edu_end_button"
android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_marginBottom="92dp"
- app:layout_constraintTop_toBottomOf="@id/content"
- app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="@id/edu_start_button"
+ app:layout_constraintBottom_toBottomOf="@id/edu_start_button"
app:layout_constraintEnd_toEndOf="parent"
android:text="@string/taskbar_edu_next"
style="@style/TaskbarEdu.Button.Next"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f1f23c4..1ec5bb8 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -166,5 +166,6 @@
<dimen name="taskbar_stashed_size">24dp</dimen>
<dimen name="taskbar_stashed_handle_width">220dp</dimen>
<dimen name="taskbar_stashed_handle_height">6dp</dimen>
- <dimen name="taskbar_edu_bg_corner_radius">28dp</dimen>
+ <dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
+ <dimen name="taskbar_edu_wave_anim_trans_y_return_overshoot">4dp</dimen>
</resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index a7ec2ea..7158287 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -211,15 +211,15 @@
<string name="taskbar_edu_opened">Taskbar education appeared</string>
<!-- Accessibility text spoken when the taskbar education panel disappears [CHAR_LIMIT=NONE] -->
<string name="taskbar_edu_closed">Taskbar education closed</string>
- <!-- Text in dialog that lets a user know how they can use the taskbar to use multiple apps on their device.
+ <!-- Text in dialog that lets a user know how they can use the taskbar to switch apps on their device.
[CHAR_LIMIT=60] -->
- <string name="taskbar_edu_splitscreen" translatable="false">Use 2 apps at once and switch apps with the taskbar</string>
- <!-- Text in dialog that lets a user know how they can customize the taskbar on their device.
+ <string name="taskbar_edu_switch_apps" translatable="false">Use the taskbar to switch apps</string>
+ <!-- Text in dialog that lets a user know how they can use the taskbar to use multiple apps at once on their device.
[CHAR_LIMIT=60] -->
- <string name="taskbar_edu_customize" translatable="false">Add your favorite apps and get automatic suggestions</string>
+ <string name="taskbar_edu_splitscreen" translatable="false">Drag to the side to use two apps at once</string>
<!-- Text in dialog that lets a user know how they can hide the taskbar on their device.
[CHAR_LIMIT=60] -->
- <string name="taskbar_edu_dock">Touch & hold to hide the taskbar anytime</string>
+ <string name="taskbar_edu_stashing">Touch & hold to hide the taskbar</string>
<!-- Text on button to go to the next screen of a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_next">Next</string>
<!-- Text on button to go to the previous screen of a tutorial [CHAR_LIMIT=16] -->
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index ac70279..b5444b5 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -135,6 +135,13 @@
<item name="android:textAllCaps">false</item>
</style>
+ <style name="OverviewClearAllButton" parent="@android:style/Widget.DeviceDefault.Button">
+ <item name="android:background">@drawable/bg_overview_clear_all_button</item>
+ <item name="android:minWidth">85dp</item>
+ <item name="android:minHeight">36dp</item>
+ <item name="android:stateListAnimator">@null</item>
+ </style>
+
<!-- Icon displayed on the taskbar -->
<style name="BaseIcon.Workspace.Taskbar" >
<item name="iconDisplay">taskbar</item>
@@ -153,4 +160,14 @@
<item name="android:textSize">16sp</item>
<item name="android:padding">4dp</item>
</style>
+
+ <style name="TextAppearance.TaskbarEdu.Title"
+ parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" >
+ <item name="android:layout_marginHorizontal">16dp</item>
+ <item name="android:gravity">center_horizontal</item>
+ <item name="android:fontFamily">google-sans</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">24sp</item>
+ <item name="android:lines">2</item>
+ </style>
</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index b40a1d5..13baf56 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -200,6 +200,7 @@
}
int predictionIndex = 0;
+ int numViewsAnimated = 0;
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
// make sure predicted icon removal and filling predictions don't step on each other
if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
@@ -233,7 +234,11 @@
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
if (isPredictedIcon(child) && child.isEnabled()) {
PredictedAppIcon icon = (PredictedAppIcon) child;
- icon.applyFromWorkspaceItem(predictedItem);
+ boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
+ icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
+ if (animateIconChange) {
+ numViewsAnimated++;
+ }
icon.finishBinding(mPredictionLongClickListener);
} else {
newItems.add(predictedItem);
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index a9c2a5e..5769f0b 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -44,7 +44,6 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
@@ -68,7 +67,7 @@
/**
* Model delegate which loads prediction items
*/
-public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
+public class QuickstepModelDelegate extends ModelDelegate {
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
@@ -93,7 +92,6 @@
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
- mIDP.addOnChangeListener(this);
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
}
@@ -179,7 +177,6 @@
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
destroyPredictors();
- mIDP.removeOnChangeListener(this);
}
private void destroyPredictors() {
@@ -250,12 +247,6 @@
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
}
- @Override
- public void onIdpChanged(InvariantDeviceProfile profile) {
- // Reinitialize everything
- Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
- }
-
private void onAppTargetEvent(AppTargetEvent event, int client) {
PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState;
if (state.predictor != null) {
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 68159fa..ab88b2b 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -296,13 +296,10 @@
}
if (supportsBlur) {
- // We cannot mark the window as opaque in overview because there will be an app window
- // below the launcher layer, and we need to draw it -- without blurs.
- boolean isOverview = mLauncher.isInState(LauncherState.OVERVIEW);
- boolean opaque = mLauncher.getScrimView().isFullyOpaque() && !isOverview;
+ boolean opaque = mLauncher.getScrimView().isFullyOpaque();
- int blur = opaque || isOverview || !mCrossWindowBlursEnabled
- || mBlurDisabledForAppLaunch ? 0 : (int) (depth * mMaxBlurRadius);
+ int blur = !mCrossWindowBlursEnabled || mBlurDisabledForAppLaunch
+ ? 0 : (int) (depth * mMaxBlurRadius);
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, blur)
.setOpaque(mSurface, opaque);
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index da10bfb..acabb0d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -34,6 +34,7 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.OnboardingPrefs;
@@ -45,6 +46,9 @@
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
/**
* A data source which integrates with a Launcher instance
*/
@@ -268,6 +272,11 @@
mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
}
+ @Override
+ public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
+ return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
+ }
+
/**
* Starts the taskbar education flow, if the user hasn't seen it yet.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index be9368f..046ee6f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -38,6 +38,7 @@
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.provider.Settings;
import android.util.Property;
import android.view.View;
import android.view.View.OnClickListener;
@@ -54,6 +55,7 @@
import com.android.launcher3.taskbar.contextual.RotationButton;
import com.android.launcher3.taskbar.contextual.RotationButtonController;
import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.AnimatedFloat;
import java.util.ArrayList;
@@ -145,7 +147,10 @@
.getKeyguardBgTaskbar(),
flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
- if (mContext.isThreeButtonNav()) {
+ // Force nav buttons (specifically back button) to be visible during setup wizard.
+ boolean areButtonsForcedVisible = !SettingsCache.INSTANCE.get(mContext).getValue(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
+ if (mContext.isThreeButtonNav() || areButtonsForcedVisible) {
initButtons(mNavButtonContainer, mEndContextualContainer,
mControllers.navButtonController);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index b32a41e..1197543 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -78,6 +78,7 @@
taskbarKeyguardController.init(navbarButtonsViewController);
stashedHandleViewController.init(this);
taskbarStashController.init(this);
+ taskbarEduController.init(this);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
index ae9592d..fd5c2ea 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduController.java
@@ -15,16 +15,72 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.view.View;
+
import com.android.launcher3.R;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
/** Handles the Taskbar Education flow. */
public class TaskbarEduController {
+ private static final long WAVE_ANIM_DELAY = 250;
+ private static final long WAVE_ANIM_STAGGER = 50;
+ private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
+ private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
+ // The fraction of each icon's animation at which we reach the top point of the wave.
+ private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
+ // The fraction of each icon's animation at which we reach the bottom, before overshooting.
+ private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
+ private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
+ private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
+ private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
+ private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
+ private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
+ // How many icons to cycle through in the slot machine (+ the original icon at each end).
+ private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;
+
private final TaskbarActivityContext mActivity;
+ private final float mWaveAnimTranslationY;
+ private final float mWaveAnimTranslationYReturnOvershoot;
+
+ // Initialized in init.
+ TaskbarControllers mControllers;
+
private TaskbarEduView mTaskbarEduView;
+ private Animator mAnim;
public TaskbarEduController(TaskbarActivityContext activity) {
mActivity = activity;
+
+ final Resources resources = activity.getResources();
+ mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
+ mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
+ R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
+ }
+
+ public void init(TaskbarControllers controllers) {
+ mControllers = controllers;
}
void showEdu() {
@@ -35,6 +91,7 @@
mTaskbarEduView.init(new TaskbarEduCallbacks());
mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
mTaskbarEduView.show();
+ startAnim(createWaveAnim());
});
}
@@ -44,6 +101,90 @@
}
}
+ /**
+ * Starts the given animation, ending the previous animation first if it's still playing.
+ */
+ private void startAnim(Animator anim) {
+ if (mAnim != null) {
+ mAnim.end();
+ }
+ mAnim = anim;
+ mAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnim = null;
+ }
+ });
+ mAnim.start();
+ }
+
+ /**
+ * Creates a staggered "wave" animation where each icon translates and scales up in succession.
+ */
+ private Animator createWaveAnim() {
+ AnimatorSet waveAnim = new AnimatorSet();
+ View[] icons = mControllers.taskbarViewController.getIconViews();
+ for (int i = 0; i < icons.length; i++) {
+ View icon = icons[i];
+ AnimatorSet iconAnim = new AnimatorSet();
+
+ Keyframe[] scaleKeyframes = new Keyframe[] {
+ Keyframe.ofFloat(0, 1f),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
+ Keyframe.ofFloat(1f, 1f)
+ };
+ scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
+ scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
+
+ Keyframe[] translationYKeyframes = new Keyframe[] {
+ Keyframe.ofFloat(0, 0f),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
+ Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
+ // Half of the remaining fraction overshoots, then the other half returns to 0.
+ Keyframe.ofFloat(
+ WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
+ mWaveAnimTranslationYReturnOvershoot),
+ Keyframe.ofFloat(1f, 0f)
+ };
+ translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
+ translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
+ translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
+ translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);
+
+ iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
+ PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
+ .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
+ iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
+ PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
+ .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
+
+ if (icon instanceof PredictedAppIcon) {
+ // Play slot machine animation through random icons from AllAppsList.
+ PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
+ ItemInfo itemInfo = (ItemInfo) icon.getTag();
+ List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
+ .filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
+ .map(appInfo -> appInfo.bitmap)
+ .filter(bitmap -> !bitmap.isNullOrLowRes())
+ .collect(Collectors.toList());
+ // Pick n icons at random.
+ Collections.shuffle(iconsToAnimate);
+ if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
+ iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
+ }
+ Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
+ if (slotMachineAnim != null) {
+ iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
+ }
+ }
+
+ iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
+ waveAnim.play(iconAnim);
+ }
+ waveAnim.setStartDelay(WAVE_ANIM_DELAY);
+ return waveAnim;
+ }
/**
* Callbacks for {@link TaskbarEduView} to interact with its controller.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
index 9c4e844..8525427 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduView.java
@@ -15,7 +15,7 @@
*/
package com.android.launcher3.taskbar;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import android.animation.PropertyValuesHolder;
import android.content.Context;
@@ -33,6 +33,7 @@
public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
implements Insettable {
+ private static final int DEFAULT_OPEN_DURATION = 500;
private static final int DEFAULT_CLOSE_DURATION = 200;
private final Rect mInsets = new Rect();
@@ -129,8 +130,8 @@
mIsOpen = true;
mOpenCloseAnimator.setValues(
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.start();
+ mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
+ mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
}
void snapToPage(int page) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 45eabed..4ed83f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -24,6 +24,8 @@
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.net.Uri;
+import android.provider.Settings;
import android.view.Display;
import androidx.annotation.NonNull;
@@ -35,6 +37,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.SettingsCache;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SystemUiProxy;
@@ -46,10 +49,14 @@
public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
SysUINavigationMode.NavigationModeChangeListener {
+ private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
+ Settings.Secure.USER_SETUP_COMPLETE);
+
private final Context mContext;
private final DisplayController mDisplayController;
private final SysUINavigationMode mSysUINavigationMode;
private final TaskbarNavButtonController mNavButtonController;
+ private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
private TaskbarActivityContext mTaskbarActivityContext;
private BaseQuickstepLauncher mLauncher;
@@ -71,9 +78,13 @@
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null);
mNavButtonController = new TaskbarNavButtonController(service);
+ mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
mDisplayController.addChangeListener(this);
mSysUINavigationMode.addModeChangeListener(this);
+ SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
+ mUserSetupCompleteListener);
+
recreateTaskbar();
}
@@ -188,6 +199,8 @@
destroyExistingTaskbar();
mDisplayController.removeChangeListener(this);
mSysUINavigationMode.removeModeChangeListener(this);
+ SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
+ mUserSetupCompleteListener);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index df88e02..c0312a0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -17,6 +17,10 @@
import android.graphics.Rect;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+
+import java.util.stream.Stream;
+
/**
* Base class for providing different taskbar UI
*/
@@ -35,4 +39,8 @@
protected void updateContentInsets(Rect outContentInsets) { }
protected void onStashedInAppChanged() { }
+
+ public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
+ return Stream.empty();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 2280c49..5144d9a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -118,6 +118,7 @@
*/
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
int nextViewIndex = 0;
+ int numViewsAnimated = 0;
for (int i = 0; i < hotseatItemInfos.length; i++) {
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
@@ -173,8 +174,14 @@
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
if (hotseatView instanceof BubbleTextView
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
- ((BubbleTextView) hotseatView).applyFromWorkspaceItem(
- (WorkspaceItemInfo) hotseatItemInfo);
+ BubbleTextView btv = (BubbleTextView) hotseatView;
+ WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo;
+
+ boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
+ btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
+ if (animate) {
+ numViewsAnimated++;
+ }
}
setClickAndLongClickListenersForIcon(hotseatView);
nextViewIndex++;
@@ -259,6 +266,18 @@
return mIconLayoutBounds;
}
+ /**
+ * Returns the app icons currently shown in the taskbar.
+ */
+ public View[] getIconViews() {
+ final int count = getChildCount();
+ View[] icons = new View[count];
+ for (int i = 0; i < count; i++) {
+ icons[i] = getChildAt(i);
+ }
+ return icons;
+ }
+
// FolderIconParent implemented methods.
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index a4b2e50..40b0e18 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -121,6 +121,10 @@
return mTaskbarView.getIconLayoutBounds();
}
+ public View[] getIconViews() {
+ return mTaskbarView.getIconViews();
+ }
+
public AnimatedFloat getTaskbarIconScaleForStash() {
return mTaskbarIconScaleForStash;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
index 99dc282..c776f16 100644
--- a/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/contextual/RotationButtonController.java
@@ -149,7 +149,7 @@
public void init() {
registerListeners();
- if (mDisplayController.getInfo().id != DEFAULT_DISPLAY) {
+ if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) {
// Currently there is no accelerometer sensor on non-default display, disable fixed
// rotation for non-default display
onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
@@ -168,7 +168,7 @@
mListenersRegistered = true;
try {
WindowManagerGlobal.getWindowManagerService()
- .watchRotation(mRotationWatcher, mDisplayController.getInfo().id);
+ .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
} catch (IllegalArgumentException e) {
mListenersRegistered = false;
Log.w(TAG, "RegisterListeners for the display failed");
@@ -335,7 +335,7 @@
}
public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
- if (mDisplayController.getInfo().id != displayId) {
+ if (DEFAULT_DISPLAY != displayId) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 85943b6..8181a84 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -44,6 +44,13 @@
}
/**
+ * Returns a unique ID representing the display
+ */
+ public static String getUniqueId(Display display) {
+ return display.getUniqueId();
+ }
+
+ /**
* Returns the minimum space that should be left empty at the end of hotseat
*/
public static int getHotseatEndOffset(Context context) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index d839a36..ee6e8ce 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -15,6 +15,16 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.Keyframe;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
@@ -23,8 +33,10 @@
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Process;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -35,6 +47,8 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
@@ -45,6 +59,10 @@
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.DoubleShadowBubbleTextView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* A BubbleTextView with a ring around it's drawable
*/
@@ -53,6 +71,9 @@
private static final int RING_SHADOW_COLOR = 0x99000000;
private static final float RING_EFFECT_RATIO = 0.095f;
+ private static final long ICON_CHANGE_ANIM_DURATION = 360;
+ private static final long ICON_CHANGE_ANIM_STAGGER = 50;
+
boolean mIsDrawingDot = false;
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -67,6 +88,25 @@
private int mPlateColor;
boolean mDrawForDrag = false;
+ // Used for the "slot-machine" education animation.
+ private List<Drawable> mSlotMachineIcons;
+ private Animator mSlotMachineAnim;
+ private float mSlotMachineIconTranslationY;
+
+ private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
+ new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
+ @Override
+ public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
+ predictedAppIcon.mSlotMachineIconTranslationY = transY;
+ predictedAppIcon.invalidate();
+ }
+
+ @Override
+ public Float get(PredictedAppIcon predictedAppIcon) {
+ return predictedAppIcon.mSlotMachineIconTranslationY;
+ }
+ };
+
public PredictedAppIcon(Context context) {
this(context, null, 0);
}
@@ -88,15 +128,38 @@
@Override
public void onDraw(Canvas canvas) {
int count = canvas.save();
+ boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
if (!mIsPinned) {
drawEffect(canvas);
+ if (isSlotMachineAnimRunning) {
+ // Clip to to outside of the ring during the slot machine animation.
+ canvas.clipPath(mRingPath);
+ }
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
}
- super.onDraw(canvas);
+ if (isSlotMachineAnimRunning) {
+ drawSlotMachineIcons(canvas);
+ } else {
+ super.onDraw(canvas);
+ }
canvas.restoreToCount(count);
}
+ private void drawSlotMachineIcons(Canvas canvas) {
+ canvas.translate((getWidth() - getIconSize()) / 2f,
+ (getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
+ for (Drawable icon : mSlotMachineIcons) {
+ icon.setBounds(0, 0, getIconSize(), getIconSize());
+ icon.draw(canvas);
+ canvas.translate(0, getSlotMachineIconPlusSpacingSize());
+ }
+ }
+
+ private float getSlotMachineIconPlusSpacingSize() {
+ return getIconSize() + getOutlineOffsetY();
+ }
+
@Override
protected void drawDotIfNecessary(Canvas canvas) {
mIsDrawingDot = true;
@@ -109,9 +172,17 @@
}
@Override
- public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
- super.applyFromWorkspaceItem(info);
- mPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
+ // Create the slot machine animation first, since it uses the current icon to start.
+ Animator slotMachineAnim = animate
+ ? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
+ : null;
+ super.applyFromWorkspaceItem(info, animate, staggerIndex);
+ int oldPlateColor = mPlateColor;
+ int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
+ if (!animate) {
+ mPlateColor = newPlateColor;
+ }
if (mIsPinned) {
setContentDescription(info.contentDescription);
} else {
@@ -119,6 +190,76 @@
getContext().getString(R.string.hotseat_prediction_content_description,
info.contentDescription));
}
+
+ if (animate) {
+ ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
+ oldPlateColor, newPlateColor);
+ plateColorAnim.addUpdateListener(valueAnimator -> {
+ mPlateColor = (int) valueAnimator.getAnimatedValue();
+ invalidate();
+ });
+ AnimatorSet changeIconAnim = new AnimatorSet();
+ if (slotMachineAnim != null) {
+ changeIconAnim.play(slotMachineAnim);
+ }
+ changeIconAnim.play(plateColorAnim);
+ changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
+ changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
+ }
+ }
+
+ /**
+ * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
+ * and ending with the original icon.
+ */
+ public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
+ return createSlotMachineAnim(iconsToAnimate, true);
+ }
+
+ /**
+ * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
+ * with the original icon, then cycling through the given icons, optionally ending back with
+ * the original icon.
+ * @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
+ * than the last item in iconsToAnimate.
+ */
+ public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
+ boolean endWithOriginalIcon) {
+ if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
+ return null;
+ }
+ if (mSlotMachineAnim != null) {
+ mSlotMachineAnim.end();
+ }
+
+ // Bookend the other animating icons with the original icon on both ends.
+ mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
+ mSlotMachineIcons.add(getIcon());
+ iconsToAnimate.stream()
+ .map(iconInfo -> iconInfo.newThemedIcon(mContext))
+ .forEach(mSlotMachineIcons::add);
+ if (endWithOriginalIcon) {
+ mSlotMachineIcons.add(getIcon());
+ }
+
+ float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
+ Keyframe[] keyframes = new Keyframe[] {
+ Keyframe.ofFloat(0f, 0f),
+ Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
+ Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
+ };
+ keyframes[1].setInterpolator(ACCEL_DEACCEL);
+ keyframes[2].setInterpolator(ACCEL_DEACCEL);
+
+ mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+ PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
+ mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
+ mSlotMachineIcons = null;
+ mSlotMachineAnim = null;
+ mSlotMachineIconTranslationY = 0;
+ invalidate();
+ }));
+ return mSlotMachineAnim;
}
/**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 444d77a..8a9bf7c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -59,7 +60,6 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
@@ -67,7 +67,6 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
@@ -147,7 +146,7 @@
mContext = context;
mDisplayController = DisplayController.INSTANCE.get(context);
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
- mDisplayId = mDisplayController.getInfo().id;
+ mDisplayId = DEFAULT_DISPLAY;
mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
runOnDestroy(() -> mDisplayController.removeChangeListener(this));
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 678b176..2a422cc 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
@@ -25,7 +26,6 @@
import android.content.Context;
import android.content.res.Resources;
-import android.util.Log;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
@@ -35,7 +35,6 @@
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.quickstep.util.RecentsOrientedState;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -146,7 +145,7 @@
mDisplayController = DisplayController.INSTANCE.get(mContext);
Resources resources = mContext.getResources();
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(mContext);
- mDisplayId = mDisplayController.getInfo().id;
+ mDisplayId = DEFAULT_DISPLAY;
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> QuickStepContract.getWindowCornerRadius(resources));
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 2e42392..20eff34 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -146,7 +146,7 @@
private static final int SYSTEM_ACTION_ID_ALL_APPS = 14;
public static final boolean ENABLE_PER_WINDOW_INPUT_ROTATION =
- SystemProperties.getBoolean("persist.debug.per_window_input_rotation", true);
+ SystemProperties.getBoolean("persist.debug.per_window_input_rotation", false);
private int mBackGestureNotificationCounter = -1;
@Nullable
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 646312e..18387dc 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1278,7 +1278,8 @@
while (getTaskViewCount() > requiredTaskViewCount) {
removeView(getChildAt(getChildCount() - 1));
}
- while (requiredGroupTaskViews > 0) {
+ int groupedTaskViewCount = getGroupedTaskViewCount();
+ while (requiredGroupTaskViews > groupedTaskViewCount) {
// Add to front of list
addView(getTaskViewFromPool(true), 0);
requiredGroupTaskViews--;
@@ -1405,6 +1406,16 @@
return taskViewCount;
}
+ public int getGroupedTaskViewCount() {
+ int groupViewCount = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i) instanceof GroupedTaskView) {
+ groupViewCount++;
+ }
+ }
+ return groupViewCount;
+ }
+
protected void onTaskStackUpdated() {
// Lazily update the empty message only when the task stack is reapplied
updateEmptyMessage();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 353e52b..02ec5e8 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -256,9 +256,27 @@
@UiThread
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
+ applyFromWorkspaceItem(info, /* animate = */ false, /* staggerIndex = */ 0);
+ }
+
+ @UiThread
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
applyFromWorkspaceItem(info, false);
}
+ /**
+ * Returns whether the newInfo differs from the current getTag().
+ */
+ public boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
+ WorkspaceItemInfo oldInfo = getTag() instanceof WorkspaceItemInfo
+ ? (WorkspaceItemInfo) getTag()
+ : null;
+ boolean changedIcons = oldInfo != null && oldInfo.getTargetComponent() != null
+ && newInfo.getTargetComponent() != null
+ && !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
+ return changedIcons && isShown();
+ }
+
@Override
public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
if (delegate instanceof LauncherAccessibilityDelegate) {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2e14823..a2189c9 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -57,6 +57,7 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -158,38 +159,6 @@
@VisibleForTesting
public InvariantDeviceProfile() {}
- private InvariantDeviceProfile(InvariantDeviceProfile p) {
- numRows = p.numRows;
- numColumns = p.numColumns;
- numFolderRows = p.numFolderRows;
- numFolderColumns = p.numFolderColumns;
- iconSize = p.iconSize;
- landscapeIconSize = p.landscapeIconSize;
- twoPanelPortraitIconSize = p.twoPanelPortraitIconSize;
- twoPanelLandscapeIconSize = p.twoPanelLandscapeIconSize;
- iconBitmapSize = p.iconBitmapSize;
- iconTextSize = p.iconTextSize;
- landscapeIconTextSize = p.landscapeIconTextSize;
- twoPanelPortraitIconTextSize = p.twoPanelPortraitIconTextSize;
- twoPanelLandscapeIconTextSize = p.twoPanelLandscapeIconTextSize;
- numShownHotseatIcons = p.numShownHotseatIcons;
- numDatabaseHotseatIcons = p.numDatabaseHotseatIcons;
- numAllAppsColumns = p.numAllAppsColumns;
- numDatabaseAllAppsColumns = p.numDatabaseAllAppsColumns;
- isScalable = p.isScalable;
- devicePaddingId = p.devicePaddingId;
- minCellHeight = p.minCellHeight;
- minCellWidth = p.minCellWidth;
- borderSpacing = p.borderSpacing;
- dbFile = p.dbFile;
- allAppsIconSize = p.allAppsIconSize;
- allAppsIconTextSize = p.allAppsIconTextSize;
- defaultLayoutId = p.defaultLayoutId;
- demoModeLayoutId = p.demoModeLayoutId;
- mExtraAttrs = p.mExtraAttrs;
- devicePaddings = p.devicePaddings;
- }
-
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
String gridName = getCurrentGridName(context);
@@ -236,13 +205,13 @@
DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
.add(myDisplayOption);
- result.iconSize = defaultDisplayOption.iconSize;
- result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
- if (defaultDisplayOption.allAppsIconSize < myDisplayOption.allAppsIconSize) {
- result.allAppsIconSize = defaultDisplayOption.allAppsIconSize;
- } else {
- result.allAppsIconSize = myDisplayOption.allAppsIconSize;
+ result.iconSizes[DisplayOption.INDEX_DEFAULT] =
+ defaultDisplayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
+ for (int i = 1; i < DisplayOption.COUNT_TOTAL; i++) {
+ result.iconSizes[i] = Math.min(
+ defaultDisplayOption.iconSizes[i], myDisplayOption.iconSizes[i]);
}
+
result.minCellHeight = defaultDisplayOption.minCellHeight;
result.minCellWidth = defaultDisplayOption.minCellWidth;
result.borderSpacing = defaultDisplayOption.borderSpacing;
@@ -288,17 +257,21 @@
mExtraAttrs = closestProfile.extraAttrs;
- iconSize = displayOption.iconSize;
- landscapeIconSize = displayOption.landscapeIconSize;
- twoPanelPortraitIconSize = displayOption.twoPanelPortraitIconSize;
- twoPanelLandscapeIconSize = displayOption.twoPanelLandscapeIconSize;
+ iconSize = displayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
+ landscapeIconSize = displayOption.iconSizes[DisplayOption.INDEX_LANDSCAPE];
+ twoPanelPortraitIconSize = displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
+ twoPanelLandscapeIconSize =
+ displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
- iconTextSize = displayOption.iconTextSize;
- landscapeIconTextSize = displayOption.landscapeIconTextSize;
- twoPanelPortraitIconTextSize = displayOption.twoPanelPortraitIconTextSize;
- twoPanelLandscapeIconTextSize = displayOption.twoPanelLandscapeIconTextSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
+ iconTextSize = displayOption.textSizes[DisplayOption.INDEX_DEFAULT];
+ landscapeIconTextSize = displayOption.textSizes[DisplayOption.INDEX_LANDSCAPE];
+ twoPanelPortraitIconTextSize =
+ displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
+ twoPanelLandscapeIconTextSize =
+ displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
+
minCellHeight = displayOption.minCellHeight;
minCellWidth = displayOption.minCellWidth;
borderSpacing = displayOption.borderSpacing;
@@ -312,8 +285,8 @@
? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
if (Utilities.isGridOptionsEnabled(context)) {
- allAppsIconSize = displayOption.allAppsIconSize;
- allAppsIconTextSize = displayOption.allAppsIconTextSize;
+ allAppsIconSize = displayOption.iconSizes[DisplayOption.INDEX_ALL_APPS];
+ allAppsIconTextSize = displayOption.textSizes[DisplayOption.INDEX_ALL_APPS];
} else {
allAppsIconSize = iconSize;
allAppsIconTextSize = iconTextSize;
@@ -374,13 +347,22 @@
MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
}
+ private Object[] toModelState() {
+ return new Object[] {
+ numColumns, numRows, numDatabaseHotseatIcons, iconBitmapSize, fillResIconDpi,
+ numDatabaseAllAppsColumns, dbFile};
+ }
+
private void onConfigChanged(Context context) {
+ Object[] oldState = toModelState();
+
// Re-init grid
String gridName = getCurrentGridName(context);
initGrid(context, gridName);
+ boolean modelPropsChanged = !Arrays.equals(oldState, toModelState());
for (OnIDPChangeListener listener : mChangeListeners) {
- listener.onIdpChanged(this);
+ listener.onIdpChanged(modelPropsChanged);
}
}
@@ -533,22 +515,33 @@
Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
dist(width, height, b.minWidthDps, b.minHeightDps)));
- GridOption closestOption = points.get(0).grid;
+ DisplayOption closestPoint = points.get(0);
+ GridOption closestOption = closestPoint.grid;
float weights = 0;
- DisplayOption p = points.get(0);
- if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
- return p;
+ if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) {
+ return closestPoint;
}
DisplayOption out = new DisplayOption(closestOption);
for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
- p = points.get(i);
+ DisplayOption p = points.get(i);
float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
weights += w;
out.add(new DisplayOption().add(p).multiply(w));
}
- return out.multiply(1.0f / weights);
+ out.multiply(1.0f / weights);
+
+ // Since the bitmaps are persisted, ensure that the default bitmap size is same as
+ // predefined size to avoid cache invalidation
+ out.iconSizes[DisplayOption.INDEX_DEFAULT] =
+ closestPoint.iconSizes[DisplayOption.INDEX_DEFAULT];
+ for (int i = DisplayOption.INDEX_DEFAULT + 1; i < DisplayOption.COUNT_TOTAL; i++) {
+ out.iconSizes[i] = Math.min(out.iconSizes[i],
+ out.iconSizes[DisplayOption.INDEX_DEFAULT]);
+ }
+
+ return out;
}
public DeviceProfile getDeviceProfile(Context context) {
@@ -614,7 +607,7 @@
/**
* Called when the device provide changes
*/
- void onIdpChanged(InvariantDeviceProfile profile);
+ void onIdpChanged(boolean modelPropertiesChanged);
}
@@ -695,6 +688,14 @@
@VisibleForTesting
static final class DisplayOption {
+ static final int INDEX_DEFAULT = 0;
+ static final int INDEX_LANDSCAPE = 1;
+ static final int INDEX_ALL_APPS = 2;
+ static final int INDEX_TWO_PANEL_PORTRAIT = 3;
+ static final int INDEX_TWO_PANEL_LANDSCAPE = 4;
+
+ static final int COUNT_TOTAL = 5;
+
public final GridOption grid;
private final float minWidthDps;
@@ -705,16 +706,8 @@
private float minCellWidth;
private float borderSpacing;
- private float iconSize;
- private float iconTextSize;
- private float landscapeIconSize;
- private float twoPanelPortraitIconSize;
- private float twoPanelLandscapeIconSize;
- private float landscapeIconTextSize;
- private float twoPanelPortraitIconTextSize;
- private float twoPanelLandscapeIconTextSize;
- private float allAppsIconSize;
- private float allAppsIconTextSize;
+ private final float[] iconSizes = new float[COUNT_TOTAL];
+ private final float[] textSizes = new float[COUNT_TOTAL];
DisplayOption(GridOption grid, Context context, AttributeSet attrs, int defaultFlagValue) {
this.grid = grid;
@@ -732,27 +725,36 @@
minCellWidth = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthDps, 0);
borderSpacing = a.getFloat(R.styleable.ProfileDisplayOption_borderSpacingDps, 0);
- iconSize = a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
- landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
- iconSize);
- twoPanelPortraitIconSize = a.getFloat(
- R.styleable.ProfileDisplayOption_twoPanelPortraitIconSize, iconSize);
- twoPanelLandscapeIconSize = a.getFloat(
- R.styleable.ProfileDisplayOption_twoPanelLandscapeIconSize,
- landscapeIconSize);
- iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
- landscapeIconTextSize = a.getFloat(
- R.styleable.ProfileDisplayOption_landscapeIconTextSize, iconTextSize);
- twoPanelPortraitIconTextSize = a.getFloat(
- R.styleable.ProfileDisplayOption_twoPanelPortraitIconTextSize, iconTextSize);
- twoPanelLandscapeIconTextSize = a.getFloat(
- R.styleable.ProfileDisplayOption_twoPanelLandscapeIconTextSize,
- landscapeIconTextSize);
+ iconSizes[INDEX_DEFAULT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
+ iconSizes[INDEX_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_ALL_APPS] =
+ a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_TWO_PANEL_PORTRAIT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconSize,
+ iconSizes[INDEX_DEFAULT]);
+ iconSizes[INDEX_TWO_PANEL_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconSize,
+ iconSizes[INDEX_LANDSCAPE]);
- allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
- iconSize);
- allAppsIconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
- iconTextSize);
+ textSizes[INDEX_DEFAULT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
+ textSizes[INDEX_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconTextSize,
+ textSizes[INDEX_DEFAULT]);
+ textSizes[INDEX_ALL_APPS] =
+ a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
+ textSizes[INDEX_DEFAULT]);
+ textSizes[INDEX_TWO_PANEL_PORTRAIT] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconTextSize,
+ textSizes[INDEX_DEFAULT]);
+ textSizes[INDEX_TWO_PANEL_LANDSCAPE] =
+ a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconTextSize,
+ textSizes[INDEX_LANDSCAPE]);
+
a.recycle();
}
@@ -771,16 +773,10 @@
}
private DisplayOption multiply(float w) {
- iconSize *= w;
- landscapeIconSize *= w;
- twoPanelPortraitIconSize *= w;
- twoPanelLandscapeIconSize *= w;
- allAppsIconSize *= w;
- iconTextSize *= w;
- landscapeIconTextSize *= w;
- twoPanelPortraitIconTextSize *= w;
- twoPanelLandscapeIconTextSize *= w;
- allAppsIconTextSize *= w;
+ for (int i = 0; i < COUNT_TOTAL; i++) {
+ iconSizes[i] *= w;
+ textSizes[i] *= w;
+ }
minCellHeight *= w;
minCellWidth *= w;
borderSpacing *= w;
@@ -788,16 +784,10 @@
}
private DisplayOption add(DisplayOption p) {
- iconSize += p.iconSize;
- landscapeIconSize += p.landscapeIconSize;
- twoPanelPortraitIconSize += p.twoPanelPortraitIconSize;
- twoPanelLandscapeIconSize += p.twoPanelLandscapeIconSize;
- allAppsIconSize += p.allAppsIconSize;
- iconTextSize += p.iconTextSize;
- landscapeIconTextSize += p.landscapeIconTextSize;
- twoPanelPortraitIconTextSize += p.twoPanelPortraitIconTextSize;
- twoPanelLandscapeIconTextSize += p.twoPanelLandscapeIconTextSize;
- allAppsIconTextSize += p.allAppsIconTextSize;
+ for (int i = 0; i < COUNT_TOTAL; i++) {
+ iconSizes[i] += p.iconSizes[i];
+ textSizes[i] += p.textSizes[i];
+ }
minCellHeight += p.minCellHeight;
minCellWidth += p.minCellWidth;
borderSpacing += p.borderSpacing;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 6ea7b17..3754dc1 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -558,7 +558,7 @@
public void onConfigurationChanged(Configuration newConfig) {
int diff = newConfig.diff(mOldConfig);
if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
- onIdpChanged(mDeviceProfile.inv);
+ onIdpChanged(false);
}
mOldConfig.setTo(newConfig);
@@ -566,8 +566,8 @@
}
@Override
- public void onIdpChanged(InvariantDeviceProfile idp) {
- initDeviceProfile(idp);
+ public void onIdpChanged(boolean modelPropertiesChanged) {
+ initDeviceProfile(mDeviceProfile.inv);
dispatchDeviceProfileChanged();
reapplyUi();
mDragLayer.recreateControllers();
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 702b73a..8adbcd9 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -83,7 +83,11 @@
Log.v(Launcher.TAG, "LauncherAppState initiated");
Preconditions.assertUIThread();
- mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher());
+ mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
+ if (modelPropertiesChanged) {
+ refreshAndReloadLauncher();
+ }
+ });
mContext.getSystemService(LauncherApps.class).registerCallback(mModel);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index d2c71b2..f420ec2 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -499,8 +499,10 @@
private void replaceRVContainer(boolean showTabs) {
for (int i = 0; i < mAH.length; i++) {
- if (mAH[i].recyclerView != null) {
- mAH[i].recyclerView.setLayoutManager(null);
+ AllAppsRecyclerView rv = mAH[i].recyclerView;
+ if (rv != null) {
+ rv.setLayoutManager(null);
+ rv.setAdapter(null);
}
}
View oldView = getRecyclerViewContainer();
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 2c84a3d..bddbbd0 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -63,6 +63,13 @@
private final SparseIntArray mCachedScrollPositions = new SparseIntArray();
private final AllAppsFastScrollHelper mFastScrollHelper;
+
+ private final AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
+ public void onChanged() {
+ mCachedScrollPositions.clear();
+ }
+ };
+
// The empty-search result background
private AllAppsBackgroundDrawable mEmptySearchBackground;
private int mEmptySearchBackgroundTopOffset;
@@ -247,12 +254,13 @@
@Override
public void setAdapter(Adapter adapter) {
+ if (getAdapter() != null) {
+ getAdapter().unregisterAdapterDataObserver(mObserver);
+ }
super.setAdapter(adapter);
- adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
- public void onChanged() {
- mCachedScrollPositions.clear();
- }
- });
+ if (adapter != null) {
+ adapter.registerAdapterDataObserver(mObserver);
+ }
}
@Override
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index f4d1e61..2f3d5d8 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -41,7 +41,6 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
@@ -149,7 +148,8 @@
inflationContext = new ContextThemeWrapper(context,
Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints()));
} else {
- inflationContext = new ContextThemeWrapper(mContext, R.style.AppTheme);
+ inflationContext = new ContextThemeWrapper(mContext,
+ Themes.getActivityThemeRes(mContext));
}
if (migrated) {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index e2c0a32..7446181 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
+import static java.util.Collections.emptyMap;
+
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ComponentCallbacks;
@@ -34,10 +36,11 @@
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
-import android.view.WindowMetrics;
import androidx.annotation.AnyThread;
import androidx.annotation.UiThread;
@@ -47,7 +50,7 @@
import com.android.launcher3.uioverrides.ApiWrapper;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -76,8 +79,8 @@
// Null for SDK < S
private final Context mWindowContext;
-
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+
private Info mInfo;
private DisplayController(Context context) {
@@ -95,19 +98,24 @@
mContext.registerReceiver(configChangeReceiver,
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
}
+ mInfo = new Info(getDisplayInfoContext(display), display,
+ getInternalDisplays(mDM), emptyMap());
+ mDM.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+ }
- // Create a single holder for all internal displays. External display holders created
- // lazily.
- Set<PortraitSize> extraInternalDisplays = new ArraySet<>();
- for (Display d : mDM.getDisplays()) {
- if (ApiWrapper.isInternalDisplay(display) && d.getDisplayId() != DEFAULT_DISPLAY) {
+ private static ArrayMap<String, PortraitSize> getInternalDisplays(
+ DisplayManager displayManager) {
+ Display[] displays = displayManager.getDisplays();
+ ArrayMap<String, PortraitSize> internalDisplays = new ArrayMap<>();
+ for (Display display : displays) {
+ if (ApiWrapper.isInternalDisplay(display)) {
Point size = new Point();
- d.getRealSize(size);
- extraInternalDisplays.add(new PortraitSize(size.x, size.y));
+ display.getRealSize(size);
+ internalDisplays.put(ApiWrapper.getUniqueId(display),
+ new PortraitSize(size.x, size.y));
}
}
- mInfo = new Info(getDisplayInfoContext(display), display, extraInternalDisplays);
- mDM.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+ return internalDisplays;
}
@Override
@@ -203,11 +211,16 @@
@AnyThread
private void handleInfoChange(Display display) {
Info oldInfo = mInfo;
- Set<PortraitSize> extraDisplaysSizes = oldInfo.mAllSizes.size() > 1
- ? oldInfo.mAllSizes : Collections.emptySet();
Context displayContext = getDisplayInfoContext(display);
- Info newInfo = new Info(displayContext, display, extraDisplaysSizes);
+ Info newInfo = new Info(displayContext, display,
+ oldInfo.mInternalDisplays, oldInfo.mPerDisplayBounds);
+
+ if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale) {
+ // Cache may not be valid anymore, recreate without cache
+ newInfo = new Info(displayContext, display, getInternalDisplays(mDM), emptyMap());
+ }
+
int change = 0;
if (!newInfo.mScreenSizeDp.equals(oldInfo.mScreenSizeDp)) {
change |= CHANGE_ACTIVE_SCREEN;
@@ -240,7 +253,6 @@
public static class Info {
- public final int id;
public final int singleFrameMs;
// Configuration properties
@@ -249,19 +261,21 @@
public final int densityDpi;
private final PortraitSize mScreenSizeDp;
- private final Set<PortraitSize> mAllSizes;
public final Point currentSize;
public final Set<WindowBounds> supportedBounds = new ArraySet<>();
+ private final Map<String, Set<WindowBounds>> mPerDisplayBounds = new ArrayMap<>();
+ private final ArrayMap<String, PortraitSize> mInternalDisplays;
public Info(Context context, Display display) {
- this(context, display, Collections.emptySet());
+ this(context, display, new ArrayMap<>(), emptyMap());
}
- private Info(Context context, Display display, Set<PortraitSize> extraDisplaysSizes) {
- id = display.getDisplayId();
-
+ private Info(Context context, Display display,
+ ArrayMap<String, PortraitSize> internalDisplays,
+ Map<String, Set<WindowBounds>> perDisplayBoundsCache) {
+ mInternalDisplays = internalDisplays;
rotation = display.getRotation();
Configuration config = context.getResources().getConfiguration();
@@ -271,32 +285,51 @@
singleFrameMs = getSingleFrameMs(display);
currentSize = new Point();
-
display.getRealSize(currentSize);
- if (extraDisplaysSizes.isEmpty() || !Utilities.ATLEAST_S) {
- Point smallestSize = new Point();
- Point largestSize = new Point();
- display.getCurrentSizeRange(smallestSize, largestSize);
+ String myDisplayId = ApiWrapper.getUniqueId(display);
+ Set<WindowBounds> currentSupportedBounds =
+ getSupportedBoundsForDisplay(display, currentSize);
+ mPerDisplayBounds.put(myDisplayId, currentSupportedBounds);
+ supportedBounds.addAll(currentSupportedBounds);
- int portraitWidth = Math.min(currentSize.x, currentSize.y);
- int portraitHeight = Math.max(currentSize.x, currentSize.y);
+ if (ApiWrapper.isInternalDisplay(display) && internalDisplays.size() > 1) {
+ int displayCount = internalDisplays.size();
+ for (int i = 0; i < displayCount; i++) {
+ String displayKey = internalDisplays.keyAt(i);
+ if (TextUtils.equals(myDisplayId, displayKey)) {
+ continue;
+ }
- supportedBounds.add(new WindowBounds(portraitWidth, portraitHeight,
- smallestSize.x, largestSize.y));
- supportedBounds.add(new WindowBounds(portraitHeight, portraitWidth,
- largestSize.x, smallestSize.y));
- mAllSizes = Collections.singleton(new PortraitSize(currentSize.x, currentSize.y));
- } else {
- mAllSizes = new ArraySet<>(extraDisplaysSizes);
- mAllSizes.add(new PortraitSize(currentSize.x, currentSize.y));
- Set<WindowMetrics> metrics = WindowManagerCompat.getDisplayProfiles(
- context, mAllSizes, densityDpi,
- ApiWrapper.TASKBAR_DRAWN_IN_PROCESS);
- metrics.forEach(wm -> supportedBounds.add(WindowBounds.fromWindowMetrics(wm)));
+ Set<WindowBounds> displayBounds = perDisplayBoundsCache.get(displayKey);
+ if (displayBounds == null) {
+ // We assume densityDpi is the same across all internal displays
+ displayBounds = WindowManagerCompat.estimateDisplayProfiles(
+ context, internalDisplays.valueAt(i), densityDpi,
+ ApiWrapper.TASKBAR_DRAWN_IN_PROCESS);
+ }
+
+ supportedBounds.addAll(displayBounds);
+ mPerDisplayBounds.put(displayKey, displayBounds);
+ }
}
}
+ private static Set<WindowBounds> getSupportedBoundsForDisplay(Display display, Point size) {
+ Point smallestSize = new Point();
+ Point largestSize = new Point();
+ display.getCurrentSizeRange(smallestSize, largestSize);
+
+ int portraitWidth = Math.min(size.x, size.y);
+ int portraitHeight = Math.max(size.x, size.y);
+ Set<WindowBounds> result = new ArraySet<>();
+ result.add(new WindowBounds(portraitWidth, portraitHeight,
+ smallestSize.x, largestSize.y));
+ result.add(new WindowBounds(portraitHeight, portraitWidth,
+ largestSize.x, smallestSize.y));
+ return result;
+ }
+
/**
* Returns true if the bounds represent a tablet
*/
diff --git a/src/com/android/launcher3/util/WindowManagerCompat.java b/src/com/android/launcher3/util/WindowManagerCompat.java
index 38a63de..bfdf1e4 100644
--- a/src/com/android/launcher3/util/WindowManagerCompat.java
+++ b/src/com/android/launcher3/util/WindowManagerCompat.java
@@ -24,6 +24,7 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Build;
+import android.util.ArraySet;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
@@ -31,14 +32,14 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.Utilities;
import com.android.launcher3.util.DisplayController.PortraitSize;
-import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
/**
- * Utility class to simulate window manager APIs until proper APIs are available
+ * Utility class to estimate window manager values
*/
@TargetApi(Build.VERSION_CODES.S)
public class WindowManagerCompat {
@@ -46,51 +47,51 @@
public static final int MIN_TABLET_WIDTH = 600;
/**
- * Returns a set of supported render sizes for a set of internal displays.
- * This is a temporary workaround which assumes only nav-bar insets change across displays
+ * Returns a set of supported render sizes for a internal display.
+ * This is a temporary workaround which assumes only nav-bar insets change across displays, and
+ * is only used until we eventually get the real values
* @param consumeTaskBar if true, it assumes that task bar is part of the app window
* and ignores any insets because of task bar.
*/
- public static Set<WindowMetrics> getDisplayProfiles(
- Context windowContext, Collection<PortraitSize> allDisplaySizes,
- int densityDpi, boolean consumeTaskBar) {
- WindowInsets metrics = windowContext.getSystemService(WindowManager.class)
+ public static Set<WindowBounds> estimateDisplayProfiles(
+ Context windowContext, PortraitSize size, int densityDpi, boolean consumeTaskBar) {
+ if (!Utilities.ATLEAST_S) {
+ return Collections.emptySet();
+ }
+ WindowInsets defaultInsets = windowContext.getSystemService(WindowManager.class)
.getMaximumWindowMetrics().getWindowInsets();
boolean hasNavbar = ResourceUtils.getIntegerByName(
"config_navBarInteractionMode",
windowContext.getResources(),
INVALID_RESOURCE_HANDLE) != 0;
- WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(metrics);
+ WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(defaultInsets);
+ Set<WindowBounds> result = new ArraySet<>();
+ int swDP = (int) dpiFromPx(size.width, densityDpi);
+ boolean isTablet = swDP >= MIN_TABLET_WIDTH;
- Set<WindowMetrics> result = new HashSet<>();
- for (PortraitSize size : allDisplaySizes) {
- int swDP = (int) dpiFromPx(size.width, densityDpi);
- boolean isTablet = swDP >= MIN_TABLET_WIDTH;
-
- final Insets portraitNav, landscapeNav;
- if (isTablet && !consumeTaskBar) {
- portraitNav = landscapeNav = Insets.of(0, 0, 0, windowContext.getResources()
- .getDimensionPixelSize(R.dimen.taskbar_size));
- } else if (hasNavbar) {
- portraitNav = Insets.of(0, 0, 0,
- getSystemResource(windowContext, "navigation_bar_height", swDP));
- landscapeNav = isTablet
- ? Insets.of(0, 0, 0, getSystemResource(windowContext,
- "navigation_bar_height_landscape", swDP))
- : Insets.of(0, 0, getSystemResource(windowContext,
- "navigation_bar_width", swDP), 0);
- } else {
- portraitNav = landscapeNav = Insets.of(0, 0, 0, 0);
- }
-
- result.add(new WindowMetrics(
- new Rect(0, 0, size.width, size.height),
- insetsBuilder.setInsets(Type.navigationBars(), portraitNav).build()));
- result.add(new WindowMetrics(
- new Rect(0, 0, size.height, size.width),
- insetsBuilder.setInsets(Type.navigationBars(), landscapeNav).build()));
+ final Insets portraitNav, landscapeNav;
+ if (isTablet && !consumeTaskBar) {
+ portraitNav = landscapeNav = Insets.of(0, 0, 0, windowContext.getResources()
+ .getDimensionPixelSize(R.dimen.taskbar_size));
+ } else if (hasNavbar) {
+ portraitNav = Insets.of(0, 0, 0,
+ getSystemResource(windowContext, "navigation_bar_height", swDP));
+ landscapeNav = isTablet
+ ? Insets.of(0, 0, 0, getSystemResource(windowContext,
+ "navigation_bar_height_landscape", swDP))
+ : Insets.of(0, 0, getSystemResource(windowContext,
+ "navigation_bar_width", swDP), 0);
+ } else {
+ portraitNav = landscapeNav = Insets.of(0, 0, 0, 0);
}
+
+ result.add(WindowBounds.fromWindowMetrics(new WindowMetrics(
+ new Rect(0, 0, size.width, size.height),
+ insetsBuilder.setInsets(Type.navigationBars(), portraitNav).build())));
+ result.add(WindowBounds.fromWindowMetrics(new WindowMetrics(
+ new Rect(0, 0, size.height, size.width),
+ insetsBuilder.setInsets(Type.navigationBars(), landscapeNav).build())));
return result;
}
diff --git a/src_plugins/com/android/systemui/plugins/OneSearch.java b/src_plugins/com/android/systemui/plugins/OneSearch.java
new file mode 100644
index 0000000..59ee4f4
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/OneSearch.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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.systemui.plugins;
+
+import android.graphics.Bitmap;
+import android.text.Spanned;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.util.ArrayList;
+
+/**
+ * Implement this interface to get suggest for one search.
+ */
+@ProvidesInterface(action = OneSearch.ACTION, version = OneSearch.VERSION)
+public interface OneSearch extends Plugin {
+ String ACTION = "com.android.systemui.action.PLUGIN_ONE_SEARCH";
+ int VERSION = 1;
+
+ /**
+ * Get the content provider warmed up.
+ */
+ void warmUp();
+
+ /**
+ * Get the suggests for the query.
+ * @param query The query to get the search suggests for.
+ */
+ ArrayList<Spanned> getSuggests(String query);
+
+ /**
+ * Get the image bitmap for the suggest.
+ * @param suggest The suggest to get the image bitmap for.
+ */
+ Bitmap getImageBitmap(Spanned suggest);
+
+ /**
+ * Get the subtitle for the suggest.
+ * @param suggest The suggest to get the subtitle for.
+ */
+ String getSubtitle(Spanned suggest);
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index cc90e6c..81e3f98 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -39,6 +39,13 @@
}
/**
+ * Returns a unique ID representing the display
+ */
+ public static String getUniqueId(Display display) {
+ return Integer.toString(display.getDisplayId());
+ }
+
+ /**
* Returns the minimum space that should be left empty at the end of hotseat
*/
public static int getHotseatEndOffset(Context context) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index f1fe508..e3f35e1 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1550,7 +1550,7 @@
try {
return object.getVisibleBounds();
} catch (StaleObjectException e) {
- fail("Object " + object + " disappeared from screen");
+ fail("Object disappeared from screen");
return null;
} catch (Throwable t) {
fail(t.toString());