Merge "Add the KeyboardQuickSwitchView (2/2)" into tm-qpr-dev am: 25656568e3
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/21087436 Change-Id: I2f769bd01dc85fbb36e0424e8aa6ecf2d18c8d91 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
74ffb0ba89
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?androidprv:attr/colorSurfaceVariant" />
|
||||
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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="@android:color/transparent" />
|
||||
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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="?attr/overviewScrimColor" />
|
||||
<corners android:radius="@dimen/keyboard_quick_switch_view_radius" />
|
||||
</shape>
|
||||
52
quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
Normal file
52
quickstep/res/layout-land/keyboard_quick_switch_taskview.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.taskbar.KeyboardQuickSwitchTaskView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
|
||||
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
|
||||
android:importantForAccessibility="yes"
|
||||
android:background="@drawable/keyboard_quick_switch_task_view_background"
|
||||
android:clipToOutline="true"
|
||||
launcher:borderColor="?androidprv:attr/colorAccentSecondaryVariant">
|
||||
|
||||
<include
|
||||
layout="@layout/keyboard_quick_switch_thumbnail"
|
||||
android:id="@+id/thumbnail1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/thumbnail2"/>
|
||||
|
||||
<include
|
||||
layout="@layout/keyboard_quick_switch_thumbnail"
|
||||
android:id="@+id/thumbnail2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
|
||||
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/thumbnail1"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
|
||||
54
quickstep/res/layout/keyboard_quick_switch_overview.xml
Normal file
54
quickstep/res/layout/keyboard_quick_switch_overview.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.taskbar.KeyboardQuickSwitchTaskView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
|
||||
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
|
||||
android:background="@drawable/keyboard_quick_switch_overview_button_background"
|
||||
android:clipToOutline="true"
|
||||
android:importantForAccessibility="yes"
|
||||
launcher:borderColor="?androidprv:attr/colorAccentSecondaryVariant">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
|
||||
android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:src="@drawable/ic_empty_recents"
|
||||
|
||||
app:tint="?android:attr/textColorPrimary"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<TextView
|
||||
style="@style/KeyboardQuickSwitchOverview"
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
|
||||
app:layout_constraintTop_toBottomOf="@id/icon"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
|
||||
52
quickstep/res/layout/keyboard_quick_switch_taskview.xml
Normal file
52
quickstep/res/layout/keyboard_quick_switch_taskview.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.taskbar.KeyboardQuickSwitchTaskView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
|
||||
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
|
||||
android:importantForAccessibility="yes"
|
||||
android:background="@drawable/keyboard_quick_switch_task_view_background"
|
||||
android:clipToOutline="true"
|
||||
launcher:borderColor="?androidprv:attr/colorAccentSecondaryVariant">
|
||||
|
||||
<include
|
||||
layout="@layout/keyboard_quick_switch_thumbnail"
|
||||
android:id="@+id/thumbnail1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/thumbnail2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<include
|
||||
layout="@layout/keyboard_quick_switch_thumbnail"
|
||||
android:id="@+id/thumbnail2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="@dimen/keyboard_quick_switch_split_view_spacing"
|
||||
|
||||
app:layout_constraintTop_toBottomOf="@id/thumbnail1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
|
||||
22
quickstep/res/layout/keyboard_quick_switch_thumbnail.xml
Normal file
22
quickstep/res/layout/keyboard_quick_switch_thumbnail.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="@drawable/keyboard_quick_switch_task_view_background"
|
||||
android:clipToOutline="true"/>
|
||||
50
quickstep/res/layout/keyboard_quick_switch_view.xml
Normal file
50
quickstep/res/layout/keyboard_quick_switch_view.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 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.taskbar.KeyboardQuickSwitchView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing"
|
||||
android:layout_marginTop="@dimen/keyboard_quick_switch_margin_top"
|
||||
android:layout_marginHorizontal="@dimen/keyboard_quick_switch_margin_ends"
|
||||
android:background="@drawable/keyboard_quick_switch_view_background"
|
||||
android:clipToOutline="true"
|
||||
android:alpha="0"
|
||||
android:visibility="invisible"
|
||||
android:focusableInTouchMode="true">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
android:scrollbars="none"
|
||||
android:alpha="0"
|
||||
android:visibility="invisible"
|
||||
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
</com.android.launcher3.taskbar.KeyboardQuickSwitchView>
|
||||
@@ -82,4 +82,6 @@
|
||||
<dimen name="taskbar_suw_frame">96dp</dimen>
|
||||
<dimen name="taskbar_suw_insets">24dp</dimen>
|
||||
|
||||
</resources>
|
||||
<dimen name="keyboard_quick_switch_taskview_width">205dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_taskview_height">119dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -331,4 +331,13 @@
|
||||
|
||||
<!-- Keyboard Quick Switch -->
|
||||
<dimen name="keyboard_quick_switch_border_width">4dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_taskview_width">104dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_taskview_height">134dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_recents_icon_size">20dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_margin_top">56dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_margin_ends">16dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_view_spacing">16dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_split_view_spacing">2dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_view_radius">28dp</dimen>
|
||||
<dimen name="keyboard_quick_switch_task_view_radius">16dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -282,4 +282,12 @@
|
||||
<string name="move_drop_target_top_or_left">Move to top/left</string>
|
||||
<!-- Label for moving drop target to the bottom or right side of the screen, depending on orientation (from the Taskbar only). -->
|
||||
<string name="move_drop_target_bottom_or_right">Move to bottom/right</string>
|
||||
|
||||
<!-- Label for quick switch tile showing how many more apps are available [CHAR LIMIT=NONE] -->
|
||||
<string name="quick_switch_overflow">{count, plural,
|
||||
=1{Show # more app.}
|
||||
other{Show # more apps.}
|
||||
}</string>
|
||||
<!-- Accessibility label for quick switch tiles showing split tasks [CHAR LIMIT=NONE] -->
|
||||
<string name="quick_switch_split_task"><xliff:g id="app_name_1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app_name_2" example="Gmail">%2$s</xliff:g></string>
|
||||
</resources>
|
||||
|
||||
@@ -223,4 +223,11 @@
|
||||
<item name="android:fontFamily">google-sans-text</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
<style name="KeyboardQuickSwitchOverview">
|
||||
<item name="fontFamily">google-sans-text</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||
<item name="lineHeight">20sp</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.ActivityInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles initialization of the {@link KeyboardQuickSwitchViewController}.
|
||||
*/
|
||||
public final class KeyboardQuickSwitchController implements
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
|
||||
static final int MAX_TASKS = 6;
|
||||
|
||||
@NonNull private final ControllerCallbacks mControllerCallbacks = new ControllerCallbacks();
|
||||
|
||||
// Initialized on init
|
||||
@Nullable private RecentsModel mModel;
|
||||
|
||||
// Used to keep track of the last requested task list id, so that we do not request to load the
|
||||
// tasks again if we have already requested it and the task list has not changed
|
||||
private int mTaskListChangeId = -1;
|
||||
// Only empty before the recent tasks list has been loaded the first time
|
||||
@NonNull private List<GroupTask> mTasks = new ArrayList<>();
|
||||
private int mNumHiddenTasks = 0;
|
||||
|
||||
// Initialized in init
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
@Nullable private KeyboardQuickSwitchViewController mQuickSwitchViewController;
|
||||
|
||||
/** Initialize the controller. */
|
||||
public void init(@NonNull TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mModel = RecentsModel.INSTANCE.get(controllers.taskbarActivityContext);
|
||||
}
|
||||
|
||||
void onConfigurationChanged(@ActivityInfo.Config int configChanges) {
|
||||
if (mQuickSwitchViewController == null) {
|
||||
return;
|
||||
}
|
||||
if ((configChanges & (ActivityInfo.CONFIG_KEYBOARD
|
||||
| ActivityInfo.CONFIG_KEYBOARD_HIDDEN)) != 0) {
|
||||
mQuickSwitchViewController.closeQuickSwitchView(true);
|
||||
return;
|
||||
}
|
||||
int currentFocusedIndex = mQuickSwitchViewController.getCurrentFocusedIndex();
|
||||
onDestroy();
|
||||
if (currentFocusedIndex != -1) {
|
||||
mControllers.taskbarActivityContext.getMainThreadHandler().post(
|
||||
() -> openQuickSwitchView(currentFocusedIndex));
|
||||
}
|
||||
}
|
||||
|
||||
void openQuickSwitchView() {
|
||||
openQuickSwitchView(-1);
|
||||
}
|
||||
|
||||
private void openQuickSwitchView(int currentFocusedIndex) {
|
||||
if (mQuickSwitchViewController != null) {
|
||||
return;
|
||||
}
|
||||
TaskbarOverlayContext overlayContext =
|
||||
mControllers.taskbarOverlayController.requestWindow();
|
||||
KeyboardQuickSwitchView keyboardQuickSwitchView =
|
||||
(KeyboardQuickSwitchView) overlayContext.getLayoutInflater()
|
||||
.inflate(
|
||||
R.layout.keyboard_quick_switch_view,
|
||||
overlayContext.getDragLayer(),
|
||||
/* attachToRoot= */ false);
|
||||
mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
|
||||
mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
|
||||
|
||||
if (mModel.isTaskListValid(mTaskListChangeId)) {
|
||||
mQuickSwitchViewController.openQuickSwitchView(
|
||||
mTasks, mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex);
|
||||
return;
|
||||
}
|
||||
mTaskListChangeId = mModel.getTasks((tasks) -> {
|
||||
// Only store MAX_TASK tasks, from most to least recent
|
||||
Collections.reverse(tasks);
|
||||
mTasks = tasks.stream().limit(MAX_TASKS).collect(Collectors.toList());
|
||||
mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
|
||||
mQuickSwitchViewController.openQuickSwitchView(
|
||||
mTasks, mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex);
|
||||
});
|
||||
}
|
||||
|
||||
void closeQuickSwitchView() {
|
||||
if (mQuickSwitchViewController == null) {
|
||||
return;
|
||||
}
|
||||
mQuickSwitchViewController.closeQuickSwitchView(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link TaskbarUIController#launchFocusedTask()}
|
||||
*/
|
||||
int launchFocusedTask() {
|
||||
// Return -1 so that the RecentsView is not incorrectly opened when the user closes the
|
||||
// quick switch view by tapping the screen.
|
||||
return mQuickSwitchViewController == null
|
||||
? -1 : mQuickSwitchViewController.launchFocusedTask();
|
||||
}
|
||||
|
||||
void onDestroy() {
|
||||
if (mQuickSwitchViewController != null) {
|
||||
mQuickSwitchViewController.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
pw.println(prefix + "KeyboardQuickSwitchController:");
|
||||
|
||||
pw.println(prefix + "\tisOpen=" + (mQuickSwitchViewController != null));
|
||||
pw.println(prefix + "\tmNumHiddenTasks=" + mNumHiddenTasks);
|
||||
pw.println(prefix + "\tmTaskListChangeId=" + mTaskListChangeId);
|
||||
pw.println(prefix + "\tmTasks=[");
|
||||
for (GroupTask task : mTasks) {
|
||||
Task task1 = task.task1;
|
||||
Task task2 = task.task2;
|
||||
ComponentName cn1 = task1.getTopComponent();
|
||||
ComponentName cn2 = task2 != null ? task2.getTopComponent() : null;
|
||||
pw.println(prefix + "\t\tt1: (id=" + task1.key.id
|
||||
+ "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
|
||||
+ " t2: (id=" + (task2 != null ? task2.key.id : "-1")
|
||||
+ "; package=" + (cn2 != null ? cn2.getPackageName() + ")"
|
||||
: "no package)"));
|
||||
}
|
||||
pw.println(prefix + "\t]");
|
||||
|
||||
if (mQuickSwitchViewController != null) {
|
||||
mQuickSwitchViewController.dumpLogs(prefix + '\t', pw);
|
||||
}
|
||||
}
|
||||
|
||||
class ControllerCallbacks {
|
||||
|
||||
int getTaskCount() {
|
||||
return mNumHiddenTasks == 0 ? mTasks.size() : MAX_TASKS + 1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
GroupTask getTaskAt(int index) {
|
||||
return index < 0 || index >= mTasks.size() ? null : mTasks.get(index);
|
||||
}
|
||||
|
||||
void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) {
|
||||
mModel.getThumbnailCache().updateThumbnailInBackground(task, callback);
|
||||
}
|
||||
|
||||
void updateTitleInBackground(Task task, Consumer<Task> callback) {
|
||||
mModel.getIconCache().updateIconInBackground(task, callback);
|
||||
}
|
||||
|
||||
void onCloseComplete() {
|
||||
mQuickSwitchViewController = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.quickstep.util.BorderAnimator;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A view that displays a recent task during a keyboard quick switch.
|
||||
*/
|
||||
public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
|
||||
@NonNull private final BorderAnimator mBorderAnimator;
|
||||
|
||||
@Nullable private ImageView mThumbnailView1;
|
||||
@Nullable private ImageView mThumbnailView2;
|
||||
|
||||
public KeyboardQuickSwitchTaskView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public KeyboardQuickSwitchTaskView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public KeyboardQuickSwitchTaskView(
|
||||
@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public KeyboardQuickSwitchTaskView(
|
||||
@NonNull Context context,
|
||||
@Nullable AttributeSet attrs,
|
||||
int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
setWillNotDraw(false);
|
||||
Resources resources = context.getResources();
|
||||
mBorderAnimator = new BorderAnimator(
|
||||
/* borderBoundsBuilder= */ bounds -> bounds.set(0, 0, getWidth(), getHeight()),
|
||||
/* borderWidthPx= */ resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_border_width),
|
||||
/* borderRadiusPx= */ resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_task_view_radius),
|
||||
/* borderColor= */ attrs == null
|
||||
? DEFAULT_BORDER_COLOR
|
||||
: context.getTheme()
|
||||
.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.TaskView,
|
||||
defStyleAttr,
|
||||
defStyleRes)
|
||||
.getColor(
|
||||
R.styleable.TaskView_borderColor,
|
||||
DEFAULT_BORDER_COLOR),
|
||||
/* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
mThumbnailView1 = findViewById(R.id.thumbnail1);
|
||||
mThumbnailView2 = findViewById(R.id.thumbnail2);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected Animator getFocusAnimator(boolean focused) {
|
||||
return mBorderAnimator.buildAnimator(focused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
mBorderAnimator.drawBorder(canvas);
|
||||
}
|
||||
|
||||
protected void setThumbnails(
|
||||
@NonNull Task task1,
|
||||
@Nullable Task task2,
|
||||
@Nullable ThumbnailUpdateFunction thumbnailUpdateFunction,
|
||||
@Nullable TitleUpdateFunction titleUpdateFunction) {
|
||||
applyThumbnail(mThumbnailView1, task1, thumbnailUpdateFunction);
|
||||
applyThumbnail(mThumbnailView2, task2, thumbnailUpdateFunction);
|
||||
|
||||
if (titleUpdateFunction == null) {
|
||||
setContentDescription(task2 == null
|
||||
? task1.titleDescription
|
||||
: getContext().getString(
|
||||
R.string.quick_switch_split_task,
|
||||
task1.titleDescription,
|
||||
task2.titleDescription));
|
||||
return;
|
||||
}
|
||||
titleUpdateFunction.updateTitleInBackground(task1, t ->
|
||||
setContentDescription(task1.titleDescription));
|
||||
if (task2 == null) {
|
||||
return;
|
||||
}
|
||||
titleUpdateFunction.updateTitleInBackground(task2, t ->
|
||||
setContentDescription(getContext().getString(
|
||||
R.string.quick_switch_split_task,
|
||||
task1.titleDescription,
|
||||
task2.titleDescription)));
|
||||
}
|
||||
|
||||
private void applyThumbnail(
|
||||
@Nullable ImageView thumbnailView,
|
||||
@Nullable Task task,
|
||||
@Nullable ThumbnailUpdateFunction updateFunction) {
|
||||
if (thumbnailView == null) {
|
||||
return;
|
||||
}
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
if (updateFunction == null) {
|
||||
applyThumbnail(thumbnailView, task.thumbnail);
|
||||
return;
|
||||
}
|
||||
updateFunction.updateThumbnailInBackground(
|
||||
task, thumbnailData -> applyThumbnail(thumbnailView, thumbnailData));
|
||||
}
|
||||
|
||||
private void applyThumbnail(
|
||||
@NonNull ImageView thumbnailView, ThumbnailData thumbnailData) {
|
||||
Bitmap bm = thumbnailData == null ? null : thumbnailData.thumbnail;
|
||||
|
||||
thumbnailView.setVisibility(VISIBLE);
|
||||
thumbnailView.setImageBitmap(bm);
|
||||
}
|
||||
|
||||
protected interface ThumbnailUpdateFunction {
|
||||
|
||||
void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback);
|
||||
}
|
||||
|
||||
protected interface TitleUpdateFunction {
|
||||
|
||||
void updateTitleInBackground(Task task, Consumer<Task> callback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
|
||||
import static com.android.launcher3.taskbar.KeyboardQuickSwitchController.MAX_TASKS;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.icu.text.MessageFormat;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* View that allows quick switching between recent tasks through keyboard alt-tab and alt-shift-tab
|
||||
* commands.
|
||||
*/
|
||||
public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
|
||||
private static final long OUTLINE_ANIMATION_DURATION_MS = 333;
|
||||
private static final float OUTLINE_START_HEIGHT_FACTOR = 0.45f;
|
||||
private static final float OUTLINE_START_RADIUS_FACTOR = 0.25f;
|
||||
private static final Interpolator OPEN_OUTLINE_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_OUTLINE_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_ACCELERATE;
|
||||
|
||||
private static final long ALPHA_ANIMATION_DURATION_MS = 83;
|
||||
private static final long ALPHA_ANIMATION_START_DELAY_MS = 67;
|
||||
|
||||
private static final long CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS = 500;
|
||||
private static final long CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS = 333;
|
||||
private static final float CONTENT_START_TRANSLATION_X_DP = 32;
|
||||
private static final float CONTENT_START_TRANSLATION_Y_DP = 40;
|
||||
private static final Interpolator OPEN_TRANSLATION_X_INTERPOLATOR = Interpolators.EMPHASIZED;
|
||||
private static final Interpolator OPEN_TRANSLATION_Y_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_TRANSLATION_Y_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_ACCELERATE;
|
||||
|
||||
private static final long CONTENT_ALPHA_ANIMATION_DURATION_MS = 83;
|
||||
private static final long CONTENT_ALPHA_ANIMATION_START_DELAY_MS = 83;
|
||||
|
||||
private final AnimatedFloat mOutlineAnimationProgress = new AnimatedFloat(
|
||||
this::invalidateOutline);
|
||||
|
||||
private HorizontalScrollView mScrollView;
|
||||
private ConstraintLayout mContent;
|
||||
|
||||
private int mTaskViewHeight;
|
||||
private int mSpacing;
|
||||
private int mOutlineRadius;
|
||||
private boolean mIsRtl;
|
||||
|
||||
@Nullable private AnimatorSet mOpenAnimation;
|
||||
|
||||
@Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
|
||||
|
||||
public KeyboardQuickSwitchView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public KeyboardQuickSwitchView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public KeyboardQuickSwitchView(@NonNull Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public KeyboardQuickSwitchView(@NonNull Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mScrollView = findViewById(R.id.scroll_view);
|
||||
mContent = findViewById(R.id.content);
|
||||
|
||||
Resources resources = getResources();
|
||||
mTaskViewHeight = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_taskview_height);
|
||||
mSpacing = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_spacing);
|
||||
mOutlineRadius = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_radius);
|
||||
mIsRtl = Utilities.isRtl(resources);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private KeyboardQuickSwitchTaskView createAndAddTaskView(
|
||||
int index,
|
||||
int width,
|
||||
boolean isFinalView,
|
||||
boolean updateTasks,
|
||||
@NonNull LayoutInflater layoutInflater,
|
||||
@Nullable View previousView,
|
||||
@NonNull List<GroupTask> groupTasks) {
|
||||
KeyboardQuickSwitchTaskView taskView = (KeyboardQuickSwitchTaskView) layoutInflater.inflate(
|
||||
R.layout.keyboard_quick_switch_taskview, mContent, false);
|
||||
taskView.setId(View.generateViewId());
|
||||
taskView.setOnClickListener(v -> mViewCallbacks.launchTappedTask(index));
|
||||
|
||||
LayoutParams lp = new LayoutParams(width, mTaskViewHeight);
|
||||
// Create a right-to-left ordering of views (or left-to-right in RTL locales)
|
||||
if (previousView != null) {
|
||||
lp.endToStart = previousView.getId();
|
||||
} else {
|
||||
lp.endToEnd = PARENT_ID;
|
||||
}
|
||||
lp.topToTop = PARENT_ID;
|
||||
lp.bottomToBottom = PARENT_ID;
|
||||
// Add spacing between views
|
||||
lp.setMarginEnd(mSpacing);
|
||||
if (isFinalView) {
|
||||
// Add spacing to the start of the final view so that scrolling ends with some padding.
|
||||
lp.startToStart = PARENT_ID;
|
||||
lp.setMarginStart(mSpacing);
|
||||
lp.horizontalBias = 1f;
|
||||
}
|
||||
|
||||
GroupTask groupTask = groupTasks.get(index);
|
||||
taskView.setThumbnails(
|
||||
groupTask.task1,
|
||||
groupTask.task2,
|
||||
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
|
||||
updateTasks ? mViewCallbacks::updateTitleInBackground : null);
|
||||
|
||||
mContent.addView(taskView, lp);
|
||||
return taskView;
|
||||
}
|
||||
|
||||
private void createAndAddOverviewButton(
|
||||
int width,
|
||||
@NonNull LayoutInflater layoutInflater,
|
||||
@Nullable View previousView,
|
||||
@NonNull String overflowString) {
|
||||
KeyboardQuickSwitchTaskView overviewButton =
|
||||
(KeyboardQuickSwitchTaskView) layoutInflater.inflate(
|
||||
R.layout.keyboard_quick_switch_overview, this, false);
|
||||
overviewButton.setOnClickListener(v -> mViewCallbacks.launchTappedTask(MAX_TASKS));
|
||||
|
||||
overviewButton.<TextView>findViewById(R.id.text).setText(overflowString);
|
||||
|
||||
ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(
|
||||
width, mTaskViewHeight);
|
||||
lp.startToStart = PARENT_ID;
|
||||
lp.endToStart = previousView.getId();
|
||||
lp.topToTop = PARENT_ID;
|
||||
lp.bottomToBottom = PARENT_ID;
|
||||
lp.setMarginEnd(mSpacing);
|
||||
lp.setMarginStart(mSpacing);
|
||||
|
||||
mContent.addView(overviewButton, lp);
|
||||
}
|
||||
|
||||
protected void applyLoadPlan(
|
||||
@NonNull Context context,
|
||||
@NonNull List<GroupTask> groupTasks,
|
||||
int numHiddenTasks,
|
||||
boolean updateTasks,
|
||||
int currentFocusIndexOverride,
|
||||
@NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks) {
|
||||
if (groupTasks.isEmpty()) {
|
||||
// Do not show the quick switch view.
|
||||
return;
|
||||
}
|
||||
mViewCallbacks = viewCallbacks;
|
||||
Resources resources = context.getResources();
|
||||
int width = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_taskview_width);
|
||||
View previousView = null;
|
||||
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(context);
|
||||
int tasksToDisplay = Math.min(MAX_TASKS, groupTasks.size());
|
||||
for (int i = 0; i < tasksToDisplay; i++) {
|
||||
previousView = createAndAddTaskView(
|
||||
i,
|
||||
width,
|
||||
/* isFinalView= */ i == tasksToDisplay - 1 && numHiddenTasks == 0,
|
||||
updateTasks,
|
||||
layoutInflater,
|
||||
previousView,
|
||||
groupTasks);
|
||||
}
|
||||
|
||||
if (numHiddenTasks > 0) {
|
||||
HashMap<String, Integer> args = new HashMap<>();
|
||||
args.put("count", numHiddenTasks);
|
||||
createAndAddOverviewButton(
|
||||
width,
|
||||
layoutInflater,
|
||||
previousView,
|
||||
new MessageFormat(
|
||||
resources.getString(R.string.quick_switch_overflow),
|
||||
Locale.getDefault()).format(args));
|
||||
}
|
||||
|
||||
getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
animateOpen(currentFocusIndexOverride);
|
||||
|
||||
getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Animator getCloseAnimation() {
|
||||
AnimatorSet closeAnimation = new AnimatorSet();
|
||||
|
||||
Animator outlineAnimation = mOutlineAnimationProgress.animateToValue(0f);
|
||||
outlineAnimation.setDuration(OUTLINE_ANIMATION_DURATION_MS);
|
||||
outlineAnimation.setInterpolator(CLOSE_OUTLINE_INTERPOLATOR);
|
||||
closeAnimation.play(outlineAnimation);
|
||||
|
||||
Animator alphaAnimation = ObjectAnimator.ofFloat(this, ALPHA, 1f, 0f);
|
||||
alphaAnimation.setStartDelay(ALPHA_ANIMATION_START_DELAY_MS);
|
||||
alphaAnimation.setDuration(ALPHA_ANIMATION_DURATION_MS);
|
||||
closeAnimation.play(alphaAnimation);
|
||||
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
mScrollView, TRANSLATION_Y, 0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
|
||||
closeAnimation.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(mScrollView, ALPHA, 1f, 0f);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
closeAnimation.play(contentAlphaAnimation);
|
||||
|
||||
closeAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
super.onAnimationStart(animation);
|
||||
if (mOpenAnimation != null) {
|
||||
mOpenAnimation.cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return closeAnimation;
|
||||
}
|
||||
|
||||
private void animateOpen(int currentFocusIndexOverride) {
|
||||
if (mOpenAnimation != null) {
|
||||
// Restart animation since currentFocusIndexOverride can change the initial scroll.
|
||||
mOpenAnimation.cancel();
|
||||
}
|
||||
mOpenAnimation = new AnimatorSet();
|
||||
|
||||
Animator outlineAnimation = mOutlineAnimationProgress.animateToValue(1f);
|
||||
outlineAnimation.setDuration(OUTLINE_ANIMATION_DURATION_MS);
|
||||
mOpenAnimation.play(outlineAnimation);
|
||||
|
||||
Animator alphaAnimation = ObjectAnimator.ofFloat(this, ALPHA, 0f, 1f);
|
||||
alphaAnimation.setDuration(ALPHA_ANIMATION_DURATION_MS);
|
||||
mOpenAnimation.play(alphaAnimation);
|
||||
|
||||
Animator translationXAnimation = ObjectAnimator.ofFloat(
|
||||
mScrollView, TRANSLATION_X, -Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
|
||||
translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
|
||||
translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
|
||||
mOpenAnimation.play(translationXAnimation);
|
||||
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
mScrollView, TRANSLATION_Y, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
|
||||
mOpenAnimation.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(mScrollView, ALPHA, 0f, 1f);
|
||||
contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
mOpenAnimation.play(contentAlphaAnimation);
|
||||
|
||||
ViewOutlineProvider outlineProvider = getOutlineProvider();
|
||||
mOpenAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
super.onAnimationStart(animation);
|
||||
setClipToPadding(false);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setRoundRect(
|
||||
/* rect= */ new Rect(
|
||||
/* left= */ 0,
|
||||
/* top= */ 0,
|
||||
/* right= */ getWidth(),
|
||||
/* bottom= */
|
||||
(int) (getHeight() * Utilities.mapBoundToRange(
|
||||
mOutlineAnimationProgress.value,
|
||||
/* lowerBound= */ 0f,
|
||||
/* upperBound= */ 1f,
|
||||
/* toMin= */ OUTLINE_START_HEIGHT_FACTOR,
|
||||
/* toMax= */ 1f,
|
||||
OPEN_OUTLINE_INTERPOLATOR))),
|
||||
/* radius= */ mOutlineRadius * Utilities.mapBoundToRange(
|
||||
mOutlineAnimationProgress.value,
|
||||
/* lowerBound= */ 0f,
|
||||
/* upperBound= */ 1f,
|
||||
/* toMin= */ OUTLINE_START_RADIUS_FACTOR,
|
||||
/* toMax= */ 1f,
|
||||
OPEN_OUTLINE_INTERPOLATOR));
|
||||
}
|
||||
});
|
||||
if (currentFocusIndexOverride == -1) {
|
||||
initializeScroll(/* index= */ 0, /* shouldTruncateTarget= */ false);
|
||||
} else {
|
||||
animateFocusMove(-1, currentFocusIndexOverride);
|
||||
}
|
||||
mScrollView.setVisibility(VISIBLE);
|
||||
setVisibility(VISIBLE);
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
setClipToPadding(true);
|
||||
setOutlineProvider(outlineProvider);
|
||||
invalidateOutline();
|
||||
mOpenAnimation = null;
|
||||
}
|
||||
});
|
||||
|
||||
mOpenAnimation.start();
|
||||
}
|
||||
|
||||
protected void animateFocusMove(int fromIndex, int toIndex) {
|
||||
KeyboardQuickSwitchTaskView focusedTask = getTaskAt(toIndex);
|
||||
if (focusedTask == null) {
|
||||
return;
|
||||
}
|
||||
AnimatorSet focusAnimation = new AnimatorSet();
|
||||
focusAnimation.play(focusedTask.getFocusAnimator(true));
|
||||
|
||||
KeyboardQuickSwitchTaskView previouslyFocusedTask = getTaskAt(fromIndex);
|
||||
if (previouslyFocusedTask != null) {
|
||||
focusAnimation.play(previouslyFocusedTask.getFocusAnimator(false));
|
||||
}
|
||||
|
||||
focusAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
super.onAnimationStart(animation);
|
||||
focusedTask.requestAccessibilityFocus();
|
||||
if (fromIndex == -1) {
|
||||
int firstVisibleTaskIndex = toIndex == 0
|
||||
? toIndex
|
||||
: getTaskAt(toIndex - 1) == null
|
||||
? toIndex : toIndex - 1;
|
||||
// Scroll so that the previous task view is truncated as a visual hint that
|
||||
// there are more tasks
|
||||
initializeScroll(
|
||||
firstVisibleTaskIndex,
|
||||
/* shouldTruncateTarget= */ firstVisibleTaskIndex != toIndex);
|
||||
} else if (toIndex > fromIndex || toIndex == 0) {
|
||||
// Scrolling to next task view
|
||||
if (mIsRtl) {
|
||||
scrollRightTo(focusedTask);
|
||||
} else {
|
||||
scrollLeftTo(focusedTask);
|
||||
}
|
||||
} else {
|
||||
// Scrolling to previous task view
|
||||
if (mIsRtl) {
|
||||
scrollLeftTo(focusedTask);
|
||||
} else {
|
||||
scrollRightTo(focusedTask);
|
||||
}
|
||||
}
|
||||
if (mViewCallbacks != null) {
|
||||
mViewCallbacks.updateCurrentFocusIndex(toIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
focusAnimation.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
return (mViewCallbacks != null && mViewCallbacks.onKeyUp(keyCode, event))
|
||||
|| super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
private void initializeScroll(int index, boolean shouldTruncateTarget) {
|
||||
View task = getTaskAt(index);
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
if (mIsRtl) {
|
||||
scrollRightTo(
|
||||
task, shouldTruncateTarget, /* smoothScroll= */ false);
|
||||
} else {
|
||||
scrollLeftTo(
|
||||
task, shouldTruncateTarget, /* smoothScroll= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollRightTo(@NonNull View targetTask) {
|
||||
scrollRightTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
|
||||
}
|
||||
|
||||
private void scrollRightTo(
|
||||
@NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
|
||||
if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
|
||||
return;
|
||||
}
|
||||
int scrollTo = targetTask.getLeft() - mSpacing
|
||||
+ (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
|
||||
// Scroll so that the focused task is to the left of the list
|
||||
if (smoothScroll) {
|
||||
mScrollView.smoothScrollTo(scrollTo, 0);
|
||||
} else {
|
||||
mScrollView.scrollTo(scrollTo, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollLeftTo(@NonNull View targetTask) {
|
||||
scrollLeftTo(targetTask, /* shouldTruncateTarget= */ false, /* smoothScroll= */ true);
|
||||
}
|
||||
|
||||
private void scrollLeftTo(
|
||||
@NonNull View targetTask, boolean shouldTruncateTarget, boolean smoothScroll) {
|
||||
if (smoothScroll && !shouldScroll(targetTask, shouldTruncateTarget)) {
|
||||
return;
|
||||
}
|
||||
int scrollTo = targetTask.getRight() + mSpacing - mScrollView.getWidth()
|
||||
- (shouldTruncateTarget ? targetTask.getWidth() / 2 : 0);
|
||||
// Scroll so that the focused task is to the right of the list
|
||||
if (smoothScroll) {
|
||||
mScrollView.smoothScrollTo(scrollTo, 0);
|
||||
} else {
|
||||
mScrollView.scrollTo(scrollTo, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldScroll(@NonNull View targetTask, boolean shouldTruncateTarget) {
|
||||
boolean isTargetTruncated =
|
||||
targetTask.getRight() + mSpacing > mScrollView.getScrollX() + mScrollView.getWidth()
|
||||
|| Math.max(0, targetTask.getLeft() - mSpacing) < mScrollView.getScrollX();
|
||||
|
||||
return isTargetTruncated && !shouldTruncateTarget;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
|
||||
return index < 0 || index >= mContent.getChildCount()
|
||||
? null : (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Handles initialization of the {@link KeyboardQuickSwitchView} and supplies it with the list of
|
||||
* tasks.
|
||||
*/
|
||||
public class KeyboardQuickSwitchViewController {
|
||||
|
||||
@NonNull private final ViewCallbacks mViewCallbacks = new ViewCallbacks();
|
||||
@NonNull private final TaskbarControllers mControllers;
|
||||
@NonNull private final TaskbarOverlayContext mOverlayContext;
|
||||
@NonNull private final KeyboardQuickSwitchView mKeyboardQuickSwitchView;
|
||||
@NonNull private final KeyboardQuickSwitchController.ControllerCallbacks mControllerCallbacks;
|
||||
|
||||
@Nullable private Animator mCloseAnimation;
|
||||
|
||||
private int mCurrentFocusIndex = -1;
|
||||
|
||||
protected KeyboardQuickSwitchViewController(
|
||||
@NonNull TaskbarControllers controllers,
|
||||
@NonNull TaskbarOverlayContext overlayContext,
|
||||
@NonNull KeyboardQuickSwitchView keyboardQuickSwitchView,
|
||||
@NonNull KeyboardQuickSwitchController.ControllerCallbacks controllerCallbacks) {
|
||||
mControllers = controllers;
|
||||
mOverlayContext = overlayContext;
|
||||
mKeyboardQuickSwitchView = keyboardQuickSwitchView;
|
||||
mControllerCallbacks = controllerCallbacks;
|
||||
}
|
||||
|
||||
protected int getCurrentFocusedIndex() {
|
||||
return mCurrentFocusIndex;
|
||||
}
|
||||
|
||||
protected void openQuickSwitchView(
|
||||
@NonNull List<GroupTask> tasks,
|
||||
int numHiddenTasks,
|
||||
boolean updateTasks,
|
||||
int currentFocusIndexOverride) {
|
||||
TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer();
|
||||
dragLayer.addView(mKeyboardQuickSwitchView);
|
||||
dragLayer.runOnClickOnce(v -> closeQuickSwitchView(true));
|
||||
|
||||
mKeyboardQuickSwitchView.applyLoadPlan(
|
||||
mOverlayContext,
|
||||
tasks,
|
||||
numHiddenTasks,
|
||||
updateTasks,
|
||||
currentFocusIndexOverride,
|
||||
mViewCallbacks);
|
||||
}
|
||||
|
||||
protected void closeQuickSwitchView(boolean animate) {
|
||||
if (mCloseAnimation != null) {
|
||||
if (animate) {
|
||||
// Let currently-running animation finish.
|
||||
return;
|
||||
} else {
|
||||
mCloseAnimation.cancel();
|
||||
}
|
||||
}
|
||||
if (!animate) {
|
||||
mCloseAnimation = null;
|
||||
onCloseComplete();
|
||||
return;
|
||||
}
|
||||
mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation();
|
||||
|
||||
mCloseAnimation.addListener(new AnimationSuccessListener() {
|
||||
@Override
|
||||
public void onAnimationSuccess(Animator animator) {
|
||||
mCloseAnimation = null;
|
||||
onCloseComplete();
|
||||
}
|
||||
});
|
||||
mCloseAnimation.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launched the currently-focused task.
|
||||
*
|
||||
* Returns index -1 iff the RecentsView shouldn't be opened.
|
||||
*
|
||||
* If the index is not -1, then the {@link com.android.quickstep.views.TaskView} at the returned
|
||||
* index will be focused.
|
||||
*/
|
||||
protected int launchFocusedTask() {
|
||||
// Launch the second-most recent task if the user quick switches too quickly, if possible.
|
||||
return launchTaskAt(mCurrentFocusIndex == -1
|
||||
? (mControllerCallbacks.getTaskCount() > 1 ? 1 : 0) : mCurrentFocusIndex);
|
||||
}
|
||||
|
||||
private int launchTaskAt(int index) {
|
||||
KeyboardQuickSwitchTaskView taskView = mKeyboardQuickSwitchView.getTaskAt(index);
|
||||
GroupTask task = mControllerCallbacks.getTaskAt(index);
|
||||
if (taskView == null || task == null) {
|
||||
return Math.max(0, index);
|
||||
} else if (task.task2 == null) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(
|
||||
task.task1.key,
|
||||
mControllers.taskbarActivityContext.getActivityLaunchOptions(
|
||||
taskView, null).options));
|
||||
} else {
|
||||
mControllers.uiController.launchSplitTasks(taskView, task);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void onCloseComplete() {
|
||||
mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
|
||||
mControllerCallbacks.onCloseComplete();
|
||||
}
|
||||
|
||||
protected void onDestroy() {
|
||||
closeQuickSwitchView(false);
|
||||
}
|
||||
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
pw.println(prefix + "KeyboardQuickSwitchViewController:");
|
||||
|
||||
pw.println(prefix + "\thasFocus=" + mKeyboardQuickSwitchView.hasFocus());
|
||||
pw.println(prefix + "\tcloseAnimationRunning=" + (mCloseAnimation != null));
|
||||
pw.println(prefix + "\tmCurrentFocusIndex=" + mCurrentFocusIndex);
|
||||
}
|
||||
|
||||
class ViewCallbacks {
|
||||
|
||||
boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (keyCode != KeyEvent.KEYCODE_TAB) {
|
||||
return false;
|
||||
}
|
||||
int taskCount = mControllerCallbacks.getTaskCount();
|
||||
int toIndex = mCurrentFocusIndex == -1
|
||||
// Focus the second-most recent app if possible
|
||||
? (taskCount > 1 ? 1 : 0)
|
||||
: (event.isShiftPressed()
|
||||
// focus a more recent task or loop back to the opposite end
|
||||
? Math.max(0, mCurrentFocusIndex == 0
|
||||
? taskCount - 1 : mCurrentFocusIndex - 1)
|
||||
// focus a less recent app or loop back to the opposite end
|
||||
: ((mCurrentFocusIndex + 1) % taskCount));
|
||||
|
||||
mKeyboardQuickSwitchView.animateFocusMove(mCurrentFocusIndex, toIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateCurrentFocusIndex(int index) {
|
||||
mCurrentFocusIndex = index;
|
||||
}
|
||||
|
||||
void launchTappedTask(int index) {
|
||||
KeyboardQuickSwitchViewController.this.launchTaskAt(index);
|
||||
closeQuickSwitchView(true);
|
||||
}
|
||||
|
||||
void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) {
|
||||
mControllerCallbacks.updateThumbnailInBackground(task, callback);
|
||||
}
|
||||
|
||||
void updateTitleInBackground(Task task, Consumer<Task> callback) {
|
||||
mControllerCallbacks.updateTitleInBackground(task, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.content.pm.PackageManager.FEATURE_PC;
|
||||
import static android.os.Trace.TRACE_TAG_APP;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
@@ -236,7 +235,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
isDesktopMode
|
||||
? new DesktopTaskbarRecentAppsController(this)
|
||||
: TaskbarRecentAppsController.DEFAULT,
|
||||
new TaskbarEduTooltipController(this));
|
||||
new TaskbarEduTooltipController(this),
|
||||
new KeyboardQuickSwitchController());
|
||||
}
|
||||
|
||||
public void init(@NonNull TaskbarSharedState sharedState) {
|
||||
|
||||
@@ -59,6 +59,7 @@ public class TaskbarControllers {
|
||||
public final TaskbarTranslationController taskbarTranslationController;
|
||||
public final TaskbarOverlayController taskbarOverlayController;
|
||||
public final TaskbarEduTooltipController taskbarEduTooltipController;
|
||||
public final KeyboardQuickSwitchController keyboardQuickSwitchController;
|
||||
|
||||
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
|
||||
@Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null;
|
||||
@@ -103,7 +104,8 @@ public class TaskbarControllers {
|
||||
VoiceInteractionWindowController voiceInteractionWindowController,
|
||||
TaskbarTranslationController taskbarTranslationController,
|
||||
TaskbarRecentAppsController taskbarRecentAppsController,
|
||||
TaskbarEduTooltipController taskbarEduTooltipController) {
|
||||
TaskbarEduTooltipController taskbarEduTooltipController,
|
||||
KeyboardQuickSwitchController keyboardQuickSwitchController) {
|
||||
this.taskbarActivityContext = taskbarActivityContext;
|
||||
this.taskbarDragController = taskbarDragController;
|
||||
this.navButtonController = navButtonController;
|
||||
@@ -127,6 +129,7 @@ public class TaskbarControllers {
|
||||
this.taskbarTranslationController = taskbarTranslationController;
|
||||
this.taskbarRecentAppsController = taskbarRecentAppsController;
|
||||
this.taskbarEduTooltipController = taskbarEduTooltipController;
|
||||
this.keyboardQuickSwitchController = keyboardQuickSwitchController;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,6 +162,7 @@ public class TaskbarControllers {
|
||||
taskbarRecentAppsController.init(this);
|
||||
taskbarTranslationController.init(this);
|
||||
taskbarEduTooltipController.init(this);
|
||||
keyboardQuickSwitchController.init(this);
|
||||
|
||||
mControllersToLog = new LoggableTaskbarController[] {
|
||||
taskbarDragController, navButtonController, navbarButtonsViewController,
|
||||
@@ -167,7 +171,7 @@ public class TaskbarControllers {
|
||||
stashedHandleViewController, taskbarStashController, taskbarEduController,
|
||||
taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
|
||||
voiceInteractionWindowController, taskbarTranslationController,
|
||||
taskbarEduTooltipController
|
||||
taskbarEduTooltipController, keyboardQuickSwitchController
|
||||
};
|
||||
mBackgroundRendererControllers = new BackgroundRendererController[] {
|
||||
taskbarDragLayerController, taskbarScrimViewController,
|
||||
@@ -191,6 +195,7 @@ public class TaskbarControllers {
|
||||
public void onConfigurationChanged(@Config int configChanges) {
|
||||
navbarButtonsViewController.onConfigurationChanged(configChanges);
|
||||
taskbarDragLayerController.onConfigurationChanged();
|
||||
keyboardQuickSwitchController.onConfigurationChanged(configChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,6 +221,7 @@ public class TaskbarControllers {
|
||||
taskbarInsetsController.onDestroy();
|
||||
voiceInteractionWindowController.onDestroy();
|
||||
taskbarRecentAppsController.onDestroy();
|
||||
keyboardQuickSwitchController.onDestroy();
|
||||
|
||||
mControllersToLog = null;
|
||||
mBackgroundRendererControllers = null;
|
||||
|
||||
@@ -230,6 +230,38 @@ public class TaskbarUIController {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Keyboard Quick Switch View.
|
||||
*
|
||||
* This will set the focus to the first task from the right (from the left in RTL)
|
||||
*/
|
||||
public void openQuickSwitchView() {
|
||||
mControllers.keyboardQuickSwitchController.openQuickSwitchView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the Keyboard Quick Switch View.
|
||||
*
|
||||
* No-op if the view is already closed
|
||||
*/
|
||||
public void closeQuickSwitchView() {
|
||||
mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the focused task and closes the Keyboard Quick Switch View.
|
||||
*
|
||||
* If the overlay or view are closed, or the overview task is focused, then Overview is
|
||||
* launched. If the overview task is launched, then the first hidden task is focused.
|
||||
*
|
||||
* @return the index of what task should be focused in ; -1 iff Overview shouldn't be launched
|
||||
*/
|
||||
public int launchFocusedTask() {
|
||||
int focusedTaskIndex = mControllers.keyboardQuickSwitchController.launchFocusedTask();
|
||||
mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
|
||||
return focusedTaskIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the focused task in splitscreen.
|
||||
*
|
||||
|
||||
@@ -31,7 +31,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StatefulActivity;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
@@ -174,8 +177,25 @@ public class OverviewCommandHelper {
|
||||
mOverviewComponentObserver.getActivityInterface();
|
||||
RecentsView recents = activityInterface.getVisibleRecentsView();
|
||||
if (recents == null) {
|
||||
T activity = activityInterface.getCreatedActivity();
|
||||
DeviceProfile dp = activity == null ? null : activity.getDeviceProfile();
|
||||
TaskbarUIController uiController = activityInterface.getTaskbarController();
|
||||
boolean allowQuickSwitch = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
|
||||
&& uiController != null
|
||||
&& dp != null
|
||||
&& (dp.isTablet || dp.isTwoPanels);
|
||||
|
||||
if (cmd.type == TYPE_HIDE) {
|
||||
// already hidden
|
||||
if (!allowQuickSwitch) {
|
||||
return true;
|
||||
}
|
||||
mTaskFocusIndexOverride = uiController.launchFocusedTask();
|
||||
if (mTaskFocusIndexOverride == -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (cmd.type == TYPE_KEYBOARD_INPUT && allowQuickSwitch) {
|
||||
uiController.openQuickSwitchView();
|
||||
return true;
|
||||
}
|
||||
if (cmd.type == TYPE_HOME) {
|
||||
|
||||
@@ -38,7 +38,8 @@ import com.android.launcher3.anim.Interpolators;
|
||||
* 1. Create an instance in the target view.
|
||||
* 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
|
||||
* {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
|
||||
* 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation where appropriate.
|
||||
* 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
|
||||
* {@link BorderAnimator#setBorderVisible(boolean)} where appropriate.
|
||||
*/
|
||||
public final class BorderAnimator {
|
||||
|
||||
@@ -138,6 +139,7 @@ public final class BorderAnimator {
|
||||
/**
|
||||
* Builds the border appearance/disappearance animation.
|
||||
*/
|
||||
@NonNull
|
||||
public Animator buildAnimator(boolean isAppearing) {
|
||||
mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
|
||||
mRunningBorderAnimation = mBorderAnimationProgress.animateToValue(isAppearing ? 1f : 0f);
|
||||
@@ -150,6 +152,18 @@ public final class BorderAnimator {
|
||||
return mRunningBorderAnimation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately shows/hides the border without an animation.
|
||||
*
|
||||
* To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)}
|
||||
*/
|
||||
public void setBorderVisible(boolean visible) {
|
||||
if (mRunningBorderAnimation != null) {
|
||||
mRunningBorderAnimation.end();
|
||||
}
|
||||
mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to update the border bounds when building this animation.
|
||||
*/
|
||||
|
||||
@@ -52,6 +52,7 @@ abstract class TaskbarBaseTestCase {
|
||||
@Mock lateinit var taskbarTranslationController: TaskbarTranslationController
|
||||
@Mock lateinit var taskbarOverlayController: TaskbarOverlayController
|
||||
@Mock lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
|
||||
@Mock lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
|
||||
|
||||
lateinit var mTaskbarControllers: TaskbarControllers
|
||||
|
||||
@@ -90,6 +91,7 @@ abstract class TaskbarBaseTestCase {
|
||||
taskbarTranslationController,
|
||||
taskbarRecentAppsController,
|
||||
taskbarEduTooltipController,
|
||||
keyboardQuickSwitchController
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user