Snap for 12078078 from 01b577432a to 24Q4-release

Change-Id: I7dcaf5c208a4e559f35f4d6f2889afed445a7e08
This commit is contained in:
Android Build Coastguard Worker
2024-07-11 23:21:29 +00:00
25 changed files with 445 additions and 310 deletions
+6 -2
View File
@@ -190,7 +190,7 @@ android_app {
],
optimize: {
proguard_flags_files: ["proguard.flags"],
proguard_flags_files: [":launcher-proguard-rules"],
// Proguard is disable for testing. Derivarive prjects to keep proguard enabled
enabled: false,
},
@@ -302,7 +302,9 @@ android_app {
static_libs: ["Launcher3QuickStepLib"],
optimize: {
enabled: false,
proguard_flags_files: [":launcher-proguard-rules"],
enabled: true,
shrink_resources: true,
},
platform_apis: true,
@@ -349,6 +351,7 @@ android_app {
optimize: {
proguard_flags_files: ["proguard.flags"],
enabled: true,
shrink_resources: true,
},
privileged: true,
@@ -385,6 +388,7 @@ android_app {
optimize: {
proguard_flags_files: ["proguard.flags"],
enabled: true,
shrink_resources: true,
},
privileged: true,
+1 -1
View File
@@ -28,7 +28,7 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<include layout="@layout/task_thumbnail" />
<include layout="@layout/task_thumbnail_deprecated" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
separately through RecentsView#resetFromSplitSelectionState() -->
+2 -2
View File
@@ -33,9 +33,9 @@
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary">
<include layout="@layout/task_thumbnail"/>
<include layout="@layout/task_thumbnail_deprecated"/>
<include layout="@layout/task_thumbnail"
<include layout="@layout/task_thumbnail_deprecated"
android:id="@+id/bottomright_snapshot" />
<!-- Filtering affects only alpha instead of the visibility since visibility can be altered
+24 -3
View File
@@ -13,8 +13,29 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.quickstep.views.TaskThumbnailViewDeprecated
<com.android.quickstep.task.thumbnail.TaskThumbnailView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent">
<ImageView
android:id="@+id/task_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:visibility="gone"/>
<com.android.quickstep.task.thumbnail.LiveTileView
android:id="@+id/task_thumbnail_live_tile"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<View
android:id="@+id/task_thumbnail_scrim"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/overview_foreground_scrim_color"
android:alpha="0" />
</com.android.quickstep.task.thumbnail.TaskThumbnailView>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2024 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.quickstep.views.TaskThumbnailViewDeprecated
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent" />
@@ -20,6 +20,7 @@ import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -33,6 +34,9 @@ import android.util.Log;
import android.view.View;
import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -124,6 +128,8 @@ public class WidgetPickerActivity extends BaseActivity {
/** A set of user ids that should be filtered out from the selected widgets. */
@NonNull
Set<Integer> mFilteredUserIds = new HashSet<>();
@Nullable
private WidgetsFullSheet mWidgetSheet;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -148,6 +154,18 @@ public class WidgetPickerActivity extends BaseActivity {
refreshAndBindWidgets();
}
@Override
protected void registerBackDispatcher() {
if (!enablePredictiveBackGesture()) {
super.registerBackDispatcher();
return;
}
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new BackAnimationCallback());
}
private void parseIntentExtras() {
mTitle = getIntent().getStringExtra(EXTRA_PICKER_TITLE);
mDescription = getIntent().getStringExtra(EXTRA_PICKER_DESCRIPTION);
@@ -293,12 +311,12 @@ public class WidgetPickerActivity extends BaseActivity {
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
}
private void openWidgetsSheet() {
private void openWidgetsSheet() {
MAIN_EXECUTOR.execute(() -> {
WidgetsFullSheet widgetSheet = WidgetsFullSheet.show(this, true);
widgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
mWidgetSheet = WidgetsFullSheet.show(this, true);
mWidgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
mWidgetSheet.disableNavBarScrim(true);
mWidgetSheet.addOnCloseListener(this::finish);
});
}
@@ -317,6 +335,51 @@ public class WidgetPickerActivity extends BaseActivity {
}
}
/**
* Animation callback for different predictive back animation states for the widget picker.
*/
private class BackAnimationCallback implements OnBackAnimationCallback {
@Nullable
OnBackAnimationCallback mActiveOnBackAnimationCallback;
@Override
public void onBackStarted(@NonNull BackEvent backEvent) {
if (mActiveOnBackAnimationCallback != null) {
mActiveOnBackAnimationCallback.onBackCancelled();
}
if (mWidgetSheet != null) {
mActiveOnBackAnimationCallback = mWidgetSheet;
mActiveOnBackAnimationCallback.onBackStarted(backEvent);
}
}
@Override
public void onBackInvoked() {
if (mActiveOnBackAnimationCallback == null) {
return;
}
mActiveOnBackAnimationCallback.onBackInvoked();
mActiveOnBackAnimationCallback = null;
}
@Override
public void onBackProgressed(@NonNull BackEvent backEvent) {
if (mActiveOnBackAnimationCallback == null) {
return;
}
mActiveOnBackAnimationCallback.onBackProgressed(backEvent);
}
@Override
public void onBackCancelled() {
if (mActiveOnBackAnimationCallback == null) {
return;
}
mActiveOnBackAnimationCallback.onBackCancelled();
mActiveOnBackAnimationCallback = null;
}
};
private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
final AppWidgetProviderInfo info = widget.widgetInfo;
if (info == null) {
@@ -42,6 +42,7 @@ import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActivityInitListener;
@@ -51,6 +52,7 @@ import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.HashMap;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -269,8 +271,11 @@ public abstract class BaseContainerInterface<STATE_TYPE extends BaseState<STATE_
} else {
Rect portraitInsets = dp.getInsets();
DisplayController displayController = DisplayController.INSTANCE.get(context);
Rect deviceRotationInsets = displayController.getInfo().getCurrentBounds().get(
orientationHandler.getRotation()).insets;
@Nullable List<WindowBounds> windowBounds =
displayController.getInfo().getCurrentBounds();
Rect deviceRotationInsets = windowBounds != null
? windowBounds.get(orientationHandler.getRotation()).insets
: new Rect();
// Obtain the landscape/seascape insets, and rotate it to portrait perspective.
orientationHandler.rotateInsets(deviceRotationInsets, outRect);
// Then combine with portrait's insets to leave space for status bar/nav bar in
@@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
@@ -416,7 +417,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
| SYSUI_STATE_MAGNIFICATION_OVERLAP
| SYSUI_STATE_DEVICE_DREAMING
| SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
| SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
| SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
}
@@ -33,6 +33,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.R;
@@ -48,6 +49,7 @@ import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.task.thumbnail.data.TaskIconDataSource;
import com.android.quickstep.util.TaskKeyLruCache;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.systemui.shared.recents.model.Task;
@@ -59,7 +61,7 @@ import java.util.concurrent.Executor;
/**
* Manages the caching of task icons and related data.
*/
public class TaskIconCache implements DisplayInfoChangeListener {
public class TaskIconCache implements TaskIconDataSource, DisplayInfoChangeListener {
private final Executor mBgExecutor;
@@ -102,7 +104,8 @@ public class TaskIconCache implements DisplayInfoChangeListener {
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
public CancellableTask getIconInBackground(Task task, GetTaskIconCallback callback) {
@Override
public CancellableTask getIconInBackground(Task task, @NonNull GetTaskIconCallback callback) {
Preconditions.assertUIThread();
if (task.icon != null) {
// Nothing to load, the icon is already loaded
@@ -16,7 +16,8 @@
package com.android.quickstep.recents.data
import com.android.quickstep.TaskIconCache
import android.graphics.drawable.Drawable
import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
@@ -38,7 +39,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
class TasksRepository(
private val recentsModel: RecentTasksDataSource,
private val taskThumbnailDataSource: TaskThumbnailDataSource,
private val taskIconCache: TaskIconCache,
private val taskIconDataSource: TaskIconDataSource,
) : RecentTasksRepository {
private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>())
private val _taskData =
@@ -46,10 +47,19 @@ class TasksRepository(
private val visibleTaskIds = MutableStateFlow(emptySet<Int>())
private val taskData: Flow<List<Task>> =
combine(_taskData, getThumbnailQueryResults()) { tasks, results ->
combine(_taskData, getThumbnailQueryResults(), getIconQueryResults()) {
tasks,
thumbnailQueryResults,
iconQueryResults ->
tasks.forEach { task ->
// Add retrieved thumbnails + remove unnecessary thumbnails
task.thumbnail = results[task.key.id]
task.thumbnail = thumbnailQueryResults[task.key.id]
// TODO(b/352331675) don't load icons for DesktopTaskView
// Add retrieved icons + remove unnecessary icons
task.icon = iconQueryResults[task.key.id]?.icon
task.titleDescription = iconQueryResults[task.key.id]?.contentDescription
task.title = iconQueryResults[task.key.id]?.title
}
tasks
}
@@ -79,7 +89,6 @@ class TasksRepository(
suspendCancellableCoroutine { continuation ->
val cancellableTask =
taskThumbnailDataSource.getThumbnailInBackground(task) {
task.thumbnail = it
continuation.resume(it)
}
continuation.invokeOnCancellation { cancellableTask?.cancel() }
@@ -109,6 +118,59 @@ class TasksRepository(
}
}
}
/** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */
private fun getIconDataRequest(task: Task): IconDataRequest =
flow {
emit(task.key.id to task.getTaskIconQueryResponse())
val iconDataResponse: TaskIconQueryResponse? =
suspendCancellableCoroutine { continuation ->
val cancellableTask =
taskIconDataSource.getIconInBackground(task) {
icon,
contentDescription,
title ->
continuation.resume(
TaskIconQueryResponse(icon, contentDescription, title)
)
}
continuation.invokeOnCancellation { cancellableTask?.cancel() }
}
emit(task.key.id to iconDataResponse)
}
.distinctUntilChanged()
private fun getIconQueryResults(): Flow<Map<Int, TaskIconQueryResponse?>> {
val visibleTasks =
combine(_taskData, visibleTaskIds) { tasks, visibleIds ->
tasks.filter { it.key.id in visibleIds }
}
val visibleIconDataRequests: Flow<List<IconDataRequest>> =
visibleTasks.map { visibleTasksList -> visibleTasksList.map(::getIconDataRequest) }
return visibleIconDataRequests.flatMapLatest { iconRequestFlows: List<IconDataRequest> ->
if (iconRequestFlows.isEmpty()) {
flowOf(emptyMap())
} else {
combine(iconRequestFlows) { it.toMap() }
}
}
}
}
typealias ThumbnailDataRequest = Flow<Pair<Int, ThumbnailData?>>
private data class TaskIconQueryResponse(
val icon: Drawable,
val contentDescription: String,
val title: String
)
private fun Task.getTaskIconQueryResponse(): TaskIconQueryResponse? {
val iconVal = icon ?: return null
val titleDescriptionVal = titleDescription ?: return null
val titleVal = title ?: return null
return TaskIconQueryResponse(iconVal, titleDescriptionVal, titleVal)
}
private typealias ThumbnailDataRequest = Flow<Pair<Int, ThumbnailData?>>
private typealias IconDataRequest = Flow<Pair<Int, TaskIconQueryResponse?>>
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.task.thumbnail
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import android.view.View
class LiveTileView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
override fun onDraw(canvas: Canvas) {
canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT)
}
companion object {
private val CLEAR_PAINT =
Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
}
}
@@ -18,17 +18,17 @@ package com.android.quickstep.task.thumbnail
import android.content.Context
import android.content.res.Configuration
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.annotation.ColorInt
import androidx.core.view.isVisible
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.util.ViewPool
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
@@ -43,7 +43,7 @@ import com.android.systemui.shared.system.QuickStepContract
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
class TaskThumbnailView : View, ViewPool.Reusable {
class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
// TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
// to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
// This is using a lazy for now because the dependencies cannot be obtained without DI.
@@ -59,12 +59,12 @@ class TaskThumbnailView : View, ViewPool.Reusable {
)
}
private var uiState: TaskThumbnailUiState = Uninitialized
private var inheritedScale: Float = 1f
private var dimProgress: Float = 0f
private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
private val thumbnail: ImageView by lazy { findViewById(R.id.task_thumbnail) }
private var inheritedScale: Float = 1f
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val scrimPaint = Paint().apply { color = Color.BLACK }
private val _measuredBounds = Rect()
private val measuredBounds: Rect
get() {
@@ -75,12 +75,12 @@ class TaskThumbnailView : View, ViewPool.Reusable {
private var overviewCornerRadius: Float = TaskCornerRadius.get(context)
private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
constructor(context: Context?) : super(context)
constructor(context: Context) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(
context: Context?,
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
@@ -90,15 +90,19 @@ class TaskThumbnailView : View, ViewPool.Reusable {
// TODO(b/335396935) replace MainScope with shorter lifecycle.
MainScope().launch {
viewModel.uiState.collect { viewModelUiState ->
uiState = viewModelUiState
invalidate()
resetViews()
when (viewModelUiState) {
is Uninitialized -> {}
is LiveTile -> drawLiveWindow()
is Snapshot -> drawSnapshot(viewModelUiState)
is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
}
}
}
MainScope().launch {
viewModel.dimProgress.collect { dimProgress ->
// TODO(b/348195366) Add fade in/out for scrim
this@TaskThumbnailView.dimProgress = dimProgress
invalidate()
scrimView.alpha = dimProgress * MAX_SCRIM_ALPHA
}
}
MainScope().launch { viewModel.cornerRadiusProgress.collect { invalidateOutline() } }
@@ -120,25 +124,6 @@ class TaskThumbnailView : View, ViewPool.Reusable {
override fun onRecycle() {
// Do nothing
uiState = Uninitialized
}
override fun onDraw(canvas: Canvas) {
when (val uiStateVal = uiState) {
is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK)
is LiveTile -> drawTransparentUiState(canvas)
is Snapshot -> drawSnapshotState(canvas, uiStateVal)
is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor)
}
if (dimProgress > 0) {
drawScrim(canvas)
}
}
private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) {
backgroundPaint.color = backgroundColor
canvas.drawRect(measuredBounds, backgroundPaint)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -149,18 +134,25 @@ class TaskThumbnailView : View, ViewPool.Reusable {
invalidateOutline()
}
private fun drawTransparentUiState(canvas: Canvas) {
canvas.drawRect(measuredBounds, CLEAR_PAINT)
private fun resetViews() {
liveTileView.isVisible = false
thumbnail.isVisible = false
scrimView.alpha = 0f
setBackgroundColor(Color.BLACK)
}
private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) {
drawBackgroundOnly(canvas, snapshot.backgroundColor)
canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null)
private fun drawBackground(@ColorInt background: Int) {
setBackgroundColor(background)
}
private fun drawScrim(canvas: Canvas) {
scrimPaint.alpha = (dimProgress * MAX_SCRIM_ALPHA).toInt()
canvas.drawRect(measuredBounds, scrimPaint)
private fun drawLiveWindow() {
liveTileView.isVisible = true
}
private fun drawSnapshot(snapshot: Snapshot) {
drawBackground(snapshot.backgroundColor)
thumbnail.setImageBitmap(snapshot.bitmap)
thumbnail.isVisible = true
}
private fun getCurrentCornerRadius() =
@@ -170,9 +162,7 @@ class TaskThumbnailView : View, ViewPool.Reusable {
fullscreenCornerRadius
) / inheritedScale
companion object {
private val CLEAR_PAINT =
Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
private const val MAX_SCRIM_ALPHA = (0.4f * 255).toInt()
private companion object {
const val MAX_SCRIM_ALPHA = 0.4f
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.task.thumbnail.data
import com.android.launcher3.util.CancellableTask
import com.android.quickstep.TaskIconCache.GetTaskIconCallback
import com.android.systemui.shared.recents.model.Task
interface TaskIconDataSource {
fun getIconInBackground(task: Task, callback: GetTaskIconCallback): CancellableTask<*>?
}
@@ -24,6 +24,7 @@ import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
@@ -36,7 +37,6 @@ import com.android.launcher3.util.ViewPool
import com.android.launcher3.util.rects.set
import com.android.quickstep.BaseContainerInterface
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.RecentsOrientedState
import com.android.systemui.shared.recents.model.Task
@@ -54,7 +54,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
ViewPool<TaskThumbnailViewDeprecated>(
context,
this,
R.layout.task_thumbnail,
R.layout.task_thumbnail_deprecated,
VIEW_POOL_MAX_SIZE,
VIEW_POOL_INITIAL_SIZE
)
@@ -108,22 +108,21 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
tasks.map { task ->
val snapshotView =
if (enableRefactorTaskThumbnail()) {
TaskThumbnailView(context)
} else {
taskThumbnailViewDeprecatedPool.view
}
.also { snapshotView ->
addView(
snapshotView,
// Add snapshotView to the front after initial views e.g. icon and
// background.
childCountAtInflation,
LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
}
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false)
} else {
taskThumbnailViewDeprecatedPool.view
}
addView(
snapshotView,
// Add snapshotView to the front after initial views e.g. icon and
// background.
childCountAtInflation,
LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
)
TaskContainer(
this,
task,
@@ -31,6 +31,7 @@ import android.util.AttributeSet
import android.util.FloatProperty
import android.util.Log
import android.view.Display
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.View.OnClickListener
@@ -666,9 +667,8 @@ constructor(
if (enableRefactorTaskThumbnail()) {
thumbnailViewDeprecated.visibility = GONE
val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
TaskThumbnailView(context).apply {
layoutParams = thumbnailViewDeprecated.layoutParams
addView(this, indexOfSnapshotView)
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false).also {
addView(it, indexOfSnapshotView, thumbnailViewDeprecated.layoutParams)
}
} else {
thumbnailViewDeprecated
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.recents.data
import android.graphics.drawable.Drawable
import com.android.launcher3.util.CancellableTask
import com.android.quickstep.TaskIconCache
import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
import com.android.systemui.shared.recents.model.Task
import com.google.common.truth.Truth.assertThat
import org.mockito.kotlin.mock
class FakeTaskIconDataSource : TaskIconDataSource {
val taskIdToDrawable: Map<Int, Drawable> = (0..10).associateWith { mock() }
val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
var shouldLoadSynchronously: Boolean = true
/** Retrieves and sets an icon on [task] from [taskIdToDrawable]. */
override fun getIconInBackground(
task: Task,
callback: TaskIconCache.GetTaskIconCallback
): CancellableTask<*>? {
val wrappedCallback = {
callback.onTaskIconReceived(
taskIdToDrawable.getValue(task.key.id),
"content desc ${task.key.id}",
"title ${task.key.id}"
)
}
if (shouldLoadSynchronously) {
wrappedCallback()
} else {
taskIdToUpdatingTask[task.key.id] = wrappedCallback
}
return null
}
}
fun Task.assertHasIconDataFromSource(fakeTaskIconDataSource: FakeTaskIconDataSource) {
assertThat(icon).isEqualTo(fakeTaskIconDataSource.taskIdToDrawable[key.id])
assertThat(titleDescription).isEqualTo("content desc ${key.id}")
assertThat(title).isEqualTo("title ${key.id}")
}
@@ -18,7 +18,6 @@ package com.android.quickstep.recents.data
import android.content.ComponentName
import android.content.Intent
import com.android.quickstep.TaskIconCache
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
@@ -31,7 +30,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
class TasksRepositoryTest {
@@ -44,10 +42,10 @@ class TasksRepositoryTest {
)
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
private val taskIconCache = mock<TaskIconCache>()
private val taskIconDataSource = FakeTaskIconDataSource()
private val systemUnderTest =
TasksRepository(recentsModel, taskThumbnailDataSource, taskIconCache)
TasksRepository(recentsModel, taskThumbnailDataSource, taskIconDataSource)
@Test
fun getAllTaskDataReturnsFlattenedListOfTasks() = runTest {
@@ -80,6 +78,22 @@ class TasksRepositoryTest {
.isEqualTo(bitmap2)
}
@Test
fun setVisibleTasksPopulatesIcons() = runTest {
recentsModel.seedTasks(defaultTaskList)
systemUnderTest.getAllTaskData(forceRefresh = true)
systemUnderTest.setVisibleTasks(listOf(1, 2))
// .drop(1) to ignore initial null content before from thumbnail was loaded.
systemUnderTest
.getTaskDataById(1)
.drop(1)
.first()!!
.assertHasIconDataFromSource(taskIconDataSource)
systemUnderTest.getTaskDataById(2).first()!!.assertHasIconDataFromSource(taskIconDataSource)
}
@Test
fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = runTest {
recentsModel.seedTasks(defaultTaskList)
@@ -101,7 +115,28 @@ class TasksRepositoryTest {
}
@Test
fun retrievedThumbnailsAreDiscardedWhenTaskBecomesInvisible() = runTest {
fun changingVisibleTasksContainsAlreadyPopulatedIcons() = runTest {
recentsModel.seedTasks(defaultTaskList)
systemUnderTest.getAllTaskData(forceRefresh = true)
systemUnderTest.setVisibleTasks(listOf(1, 2))
// .drop(1) to ignore initial null content before from icon was loaded.
systemUnderTest
.getTaskDataById(2)
.drop(1)
.first()!!
.assertHasIconDataFromSource(taskIconDataSource)
// Prevent new loading of Drawables
taskThumbnailDataSource.shouldLoadSynchronously = false
systemUnderTest.setVisibleTasks(listOf(2, 3))
systemUnderTest.getTaskDataById(2).first()!!.assertHasIconDataFromSource(taskIconDataSource)
}
@Test
fun retrievedImagesAreDiscardedWhenTaskBecomesInvisible() = runTest {
recentsModel.seedTasks(defaultTaskList)
val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
systemUnderTest.getAllTaskData(forceRefresh = true)
@@ -109,14 +144,20 @@ class TasksRepositoryTest {
systemUnderTest.setVisibleTasks(listOf(1, 2))
// .drop(1) to ignore initial null content before from thumbnail was loaded.
assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail)
.isEqualTo(bitmap2)
val task2 = systemUnderTest.getTaskDataById(2).drop(1).first()!!
assertThat(task2.thumbnail!!.thumbnail).isEqualTo(bitmap2)
task2.assertHasIconDataFromSource(taskIconDataSource)
// Prevent new loading of Bitmaps
taskThumbnailDataSource.shouldLoadSynchronously = false
taskIconDataSource.shouldLoadSynchronously = false
systemUnderTest.setVisibleTasks(listOf(0, 1))
assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull()
val task2AfterVisibleTasksChanged = systemUnderTest.getTaskDataById(2).first()!!
assertThat(task2AfterVisibleTasksChanged.thumbnail).isNull()
assertThat(task2AfterVisibleTasksChanged.icon).isNull()
assertThat(task2AfterVisibleTasksChanged.titleDescription).isNull()
assertThat(task2AfterVisibleTasksChanged.title).isNull()
}
@Test
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:showBackdrop="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/standard_decelerate_interpolator"
android:startOffset="100"
android:duration="350" />
<translate
android:fromXDelta="-25%"
android:toXDelta="0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
</set>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/standard_accelerate_interpolator"
android:startOffset="0"
android:duration="100" />
<translate
android:fromXDelta="0"
android:toXDelta="25%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
</set>
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:showBackdrop="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/standard_decelerate_interpolator"
android:startOffset="100"
android:duration="350" />
<translate
android:fromXDelta="25%"
android:toXDelta="0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
</set>
@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/standard_accelerate_interpolator"
android:startOffset="0"
android:duration="100" />
<translate
android:fromXDelta="0"
android:toXDelta="-25%"
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/emphasized_interpolator"
android:startOffset="0"
android:duration="450" />
</set>
-40
View File
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<resources>
<style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:listPreferredItemPaddingEnd">16dp</item>
<item name="android:listPreferredItemPaddingStart">24dp</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:switchStyle">@style/SwitchStyle</item>
<item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
<item name="android:windowAnimationStyle">@style/Animation.SharedBackground</item>
</style>
<style name="Animation.SharedBackground" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/shared_x_axis_activity_open_enter</item>
<item name="android:activityOpenExitAnimation">@anim/shared_x_axis_activity_open_exit</item>
<item name="android:activityCloseEnterAnimation">@anim/shared_x_axis_activity_close_enter</item>
<item name="android:activityCloseExitAnimation">@anim/shared_x_axis_activity_close_exit</item>
</style>
</resources>
@@ -508,6 +508,7 @@ public class LauncherPreviewRenderer extends ContextWrapper
&& !SHOULD_SHOW_FIRST_PAGE_WIDGET) {
CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false);
// TODO: set bgHandler on qsb when it is BaseTemplateCard, which requires API changes.
CellLayoutLayoutParams lp = new CellLayoutLayoutParams(
0, 0, firstScreen.getCountX(), 1);
lp.canReorder = false;
@@ -49,6 +49,7 @@ import android.util.Log;
import android.view.Display;
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
@@ -513,9 +514,8 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable {
return Collections.unmodifiableSet(mPerDisplayBounds.keySet());
}
/**
* Returns all {@link WindowBounds}s for the current display.
*/
/** Returns all {@link WindowBounds}s for the current display. */
@Nullable
public List<WindowBounds> getCurrentBounds() {
return mPerDisplayBounds.get(normalizedDisplayInfo);
}
@@ -167,6 +167,7 @@ public class TaplTwoPanelWorkspaceTest extends AbstractLauncherUiTest<Launcher>
@Test
@PortraitLandscape
@ScreenRecordRule.ScreenRecord // b/352130094
public void testDragIconToPage2() {
Workspace workspace = mLauncher.getWorkspace();