1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.taskbar; 17 18 import android.content.ComponentName; 19 import android.content.pm.ActivityInfo; 20 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 24 import com.android.launcher3.R; 25 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext; 26 import com.android.quickstep.RecentsModel; 27 import com.android.quickstep.util.GroupTask; 28 import com.android.systemui.shared.recents.model.Task; 29 import com.android.systemui.shared.recents.model.ThumbnailData; 30 31 import java.io.PrintWriter; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.function.Consumer; 36 import java.util.stream.Collectors; 37 38 /** 39 * Handles initialization of the {@link KeyboardQuickSwitchViewController}. 40 */ 41 public final class KeyboardQuickSwitchController implements 42 TaskbarControllers.LoggableTaskbarController { 43 44 static final int MAX_TASKS = 6; 45 46 @NonNull private final ControllerCallbacks mControllerCallbacks = new ControllerCallbacks(); 47 48 // Initialized on init 49 @Nullable private RecentsModel mModel; 50 51 // Used to keep track of the last requested task list id, so that we do not request to load the 52 // tasks again if we have already requested it and the task list has not changed 53 private int mTaskListChangeId = -1; 54 // Only empty before the recent tasks list has been loaded the first time 55 @NonNull private List<GroupTask> mTasks = new ArrayList<>(); 56 private int mNumHiddenTasks = 0; 57 58 // Initialized in init 59 private TaskbarControllers mControllers; 60 61 @Nullable private KeyboardQuickSwitchViewController mQuickSwitchViewController; 62 63 /** Initialize the controller. */ init(@onNull TaskbarControllers controllers)64 public void init(@NonNull TaskbarControllers controllers) { 65 mControllers = controllers; 66 mModel = RecentsModel.INSTANCE.get(controllers.taskbarActivityContext); 67 } 68 onConfigurationChanged(@ctivityInfo.Config int configChanges)69 void onConfigurationChanged(@ActivityInfo.Config int configChanges) { 70 if (mQuickSwitchViewController == null) { 71 return; 72 } 73 if ((configChanges & (ActivityInfo.CONFIG_KEYBOARD 74 | ActivityInfo.CONFIG_KEYBOARD_HIDDEN)) != 0) { 75 mQuickSwitchViewController.closeQuickSwitchView(true); 76 return; 77 } 78 int currentFocusedIndex = mQuickSwitchViewController.getCurrentFocusedIndex(); 79 onDestroy(); 80 if (currentFocusedIndex != -1) { 81 mControllers.taskbarActivityContext.getMainThreadHandler().post( 82 () -> openQuickSwitchView(currentFocusedIndex)); 83 } 84 } 85 openQuickSwitchView()86 void openQuickSwitchView() { 87 openQuickSwitchView(-1); 88 } 89 openQuickSwitchView(int currentFocusedIndex)90 private void openQuickSwitchView(int currentFocusedIndex) { 91 if (mQuickSwitchViewController != null) { 92 return; 93 } 94 TaskbarOverlayContext overlayContext = 95 mControllers.taskbarOverlayController.requestWindow(); 96 KeyboardQuickSwitchView keyboardQuickSwitchView = 97 (KeyboardQuickSwitchView) overlayContext.getLayoutInflater() 98 .inflate( 99 R.layout.keyboard_quick_switch_view, 100 overlayContext.getDragLayer(), 101 /* attachToRoot= */ false); 102 mQuickSwitchViewController = new KeyboardQuickSwitchViewController( 103 mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks); 104 105 if (mModel.isTaskListValid(mTaskListChangeId)) { 106 mQuickSwitchViewController.openQuickSwitchView( 107 mTasks, mNumHiddenTasks, /* updateTasks= */ false, currentFocusedIndex); 108 return; 109 } 110 mTaskListChangeId = mModel.getTasks((tasks) -> { 111 // Only store MAX_TASK tasks, from most to least recent 112 Collections.reverse(tasks); 113 mTasks = tasks.stream().limit(MAX_TASKS).collect(Collectors.toList()); 114 mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS); 115 mQuickSwitchViewController.openQuickSwitchView( 116 mTasks, mNumHiddenTasks, /* updateTasks= */ true, currentFocusedIndex); 117 }); 118 } 119 closeQuickSwitchView()120 void closeQuickSwitchView() { 121 if (mQuickSwitchViewController == null) { 122 return; 123 } 124 mQuickSwitchViewController.closeQuickSwitchView(true); 125 } 126 127 /** 128 * See {@link TaskbarUIController#launchFocusedTask()} 129 */ launchFocusedTask()130 int launchFocusedTask() { 131 // Return -1 so that the RecentsView is not incorrectly opened when the user closes the 132 // quick switch view by tapping the screen. 133 return mQuickSwitchViewController == null 134 ? -1 : mQuickSwitchViewController.launchFocusedTask(); 135 } 136 onDestroy()137 void onDestroy() { 138 if (mQuickSwitchViewController != null) { 139 mQuickSwitchViewController.onDestroy(); 140 } 141 } 142 143 @Override dumpLogs(String prefix, PrintWriter pw)144 public void dumpLogs(String prefix, PrintWriter pw) { 145 pw.println(prefix + "KeyboardQuickSwitchController:"); 146 147 pw.println(prefix + "\tisOpen=" + (mQuickSwitchViewController != null)); 148 pw.println(prefix + "\tmNumHiddenTasks=" + mNumHiddenTasks); 149 pw.println(prefix + "\tmTaskListChangeId=" + mTaskListChangeId); 150 pw.println(prefix + "\tmTasks=["); 151 for (GroupTask task : mTasks) { 152 Task task1 = task.task1; 153 Task task2 = task.task2; 154 ComponentName cn1 = task1.getTopComponent(); 155 ComponentName cn2 = task2 != null ? task2.getTopComponent() : null; 156 pw.println(prefix + "\t\tt1: (id=" + task1.key.id 157 + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)") 158 + " t2: (id=" + (task2 != null ? task2.key.id : "-1") 159 + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" 160 : "no package)")); 161 } 162 pw.println(prefix + "\t]"); 163 164 if (mQuickSwitchViewController != null) { 165 mQuickSwitchViewController.dumpLogs(prefix + '\t', pw); 166 } 167 } 168 169 class ControllerCallbacks { 170 getTaskCount()171 int getTaskCount() { 172 return mNumHiddenTasks == 0 ? mTasks.size() : MAX_TASKS + 1; 173 } 174 175 @Nullable getTaskAt(int index)176 GroupTask getTaskAt(int index) { 177 return index < 0 || index >= mTasks.size() ? null : mTasks.get(index); 178 } 179 updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback)180 void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) { 181 mModel.getThumbnailCache().updateThumbnailInBackground(task, callback); 182 } 183 updateTitleInBackground(Task task, Consumer<Task> callback)184 void updateTitleInBackground(Task task, Consumer<Task> callback) { 185 mModel.getIconCache().updateIconInBackground(task, callback); 186 } 187 onCloseComplete()188 void onCloseComplete() { 189 mQuickSwitchViewController = null; 190 } 191 } 192 } 193