1 /*
2  * Copyright (C) 2024 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 
17 package com.android.systemui.qs.panels.domain.interactor
18 
19 import android.util.Log
20 import com.android.systemui.dagger.SysUISingleton
21 import com.android.systemui.qs.panels.shared.model.SizedTile
22 import com.android.systemui.qs.panels.shared.model.TileRow
23 import com.android.systemui.qs.pipeline.shared.TileSpec
24 import javax.inject.Inject
25 
26 @SysUISingleton
27 class InfiniteGridConsistencyInteractor
28 @Inject
29 constructor(
30     private val iconTilesInteractor: IconTilesInteractor,
31     private val gridSizeInteractor: InfiniteGridSizeInteractor
32 ) : GridTypeConsistencyInteractor {
33 
34     /**
35      * Tries to fill in every columns of all rows (except the last row), potentially reordering
36      * tiles.
37      */
reconcileTilesnull38     override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
39         val newTiles: MutableList<TileSpec> = mutableListOf()
40         val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
41         val tilesQueue =
42             ArrayDeque(
43                 tiles.map {
44                     SizedTile(
45                         it,
46                         width =
47                             if (iconTilesInteractor.isIconTile(it)) {
48                                 1
49                             } else {
50                                 2
51                             }
52                     )
53                 }
54             )
55 
56         while (tilesQueue.isNotEmpty()) {
57             if (row.isFull()) {
58                 newTiles.addAll(row.tiles.map { it.tile })
59                 row.clear()
60             }
61 
62             val tile = tilesQueue.removeFirst()
63 
64             // If the tile fits in the row, add it.
65             if (!row.maybeAddTile(tile)) {
66                 // If the tile does not fit the row, find an icon tile to move.
67                 // We'll try to either add an icon tile from the queue to complete the row, or
68                 // remove an icon tile from the current row to free up space.
69 
70                 val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 }
71                 if (iconTile != null) {
72                     tilesQueue.remove(iconTile)
73                     tilesQueue.addFirst(tile)
74                     row.maybeAddTile(iconTile)
75                 } else {
76                     val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile()
77                     if (tileToRemove != null) {
78                         row.removeTile(tileToRemove)
79                         row.maybeAddTile(tile)
80 
81                         // Moving the icon tile to the end because there's no other
82                         // icon tiles in the queue.
83                         tilesQueue.addLast(tileToRemove)
84                     } else {
85                         // If the row does not have an icon tile, add the incomplete row.
86                         // Note: this shouldn't happen because an icon tile is guaranteed to be in a
87                         // row that doesn't have enough space for a large tile.
88                         val tileSpecs = row.tiles.map { it.tile }
89                         Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
90                         newTiles.addAll(tileSpecs)
91                         row.clear()
92                         tilesQueue.addFirst(tile)
93                     }
94                 }
95             }
96         }
97 
98         // Add last row that might be incomplete
99         newTiles.addAll(row.tiles.map { it.tile })
100 
101         return newTiles.toList()
102     }
103 
104     private companion object {
105         const val TAG = "InfiniteGridConsistencyInteractor"
106     }
107 }
108