Merge changes from topic "258442632" into tm-qpr-dev
* changes: Add panel using TaskView Distinguish between selected panel or structure
This commit is contained in:
committed by
Android (Google) Code Review
commit
e1833a3a2d
@@ -1,30 +0,0 @@
|
|||||||
<!--
|
|
||||||
~ Copyright (C) 2020 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.systemui.globalactions.MinHeightScrollView
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:scrollbars="none">
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/global_actions_controls_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_marginLeft="@dimen/global_actions_side_margin"
|
|
||||||
android:layout_marginRight="@dimen/global_actions_side_margin" />
|
|
||||||
</com.android.systemui.globalactions.MinHeightScrollView>
|
|
||||||
@@ -15,28 +15,19 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/control_detail_root"
|
android:id="@+id/control_detail_root"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.android.systemui.globalactions.MinHeightScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:scrollbars="none">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/global_actions_controls"
|
android:id="@+id/global_actions_controls"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:clipChildren="false"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingHorizontal="@dimen/controls_padding_horizontal" />
|
android:paddingHorizontal="@dimen/controls_padding_horizontal" />
|
||||||
|
|
||||||
</com.android.systemui.globalactions.MinHeightScrollView>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_marginTop="@dimen/controls_top_margin"
|
android:layout_marginTop="@dimen/controls_top_margin"
|
||||||
android:layout_marginBottom="@dimen/controls_header_bottom_margin">
|
android:layout_marginBottom="@dimen/controls_header_bottom_margin">
|
||||||
@@ -71,5 +71,27 @@
|
|||||||
android:background="?android:attr/selectableItemBackgroundBorderless" />
|
android:background="?android:attr/selectableItemBackgroundBorderless" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<include layout="@layout/global_actions_controls_list_view" />
|
<ScrollView
|
||||||
|
android:id="@+id/controls_scroll_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:clipChildren="true"
|
||||||
|
android:scrollbars="none">
|
||||||
|
<include layout="@layout/global_actions_controls_list_view" />
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/controls_panel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginLeft="@dimen/global_actions_side_margin"
|
||||||
|
android:layout_marginRight="@dimen/global_actions_side_margin"
|
||||||
|
android:background="#ff0000"
|
||||||
|
android:padding="@dimen/global_actions_side_margin"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
</merge>
|
</merge>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.android.systemui.controls.ControlStatus
|
|||||||
import com.android.systemui.util.UserAwareController
|
import com.android.systemui.util.UserAwareController
|
||||||
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
|
import com.android.systemui.controls.ui.SelectedItem
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,8 +185,8 @@ interface ControlsController : UserAwareController {
|
|||||||
*/
|
*/
|
||||||
fun countFavoritesForComponent(componentName: ComponentName): Int
|
fun countFavoritesForComponent(componentName: ComponentName): Int
|
||||||
|
|
||||||
/** See [ControlsUiController.getPreferredStructure]. */
|
/** See [ControlsUiController.getPreferredSelectedItem]. */
|
||||||
fun getPreferredStructure(): StructureInfo
|
fun getPreferredSelection(): SelectedItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for structure to pass data to [ControlsFavoritingActivity].
|
* Interface for structure to pass data to [ControlsFavoritingActivity].
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import com.android.systemui.controls.ControlStatus
|
|||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
|
import com.android.systemui.controls.ui.SelectedItem
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
@@ -556,8 +557,8 @@ class ControlsControllerImpl @Inject constructor (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPreferredStructure(): StructureInfo {
|
override fun getPreferredSelection(): SelectedItem {
|
||||||
return uiController.getPreferredStructure(getFavorites())
|
return uiController.getPreferredSelectedItem(getFavorites())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||||
|
|||||||
@@ -31,4 +31,9 @@ data class StructureInfo(
|
|||||||
val componentName: ComponentName,
|
val componentName: ComponentName,
|
||||||
val structure: CharSequence,
|
val structure: CharSequence,
|
||||||
val controls: List<ControlInfo>
|
val controls: List<ControlInfo>
|
||||||
)
|
) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY_COMPONENT = ComponentName("", "")
|
||||||
|
val EMPTY_STRUCTURE = StructureInfo(EMPTY_COMPONENT, "", mutableListOf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ class AppAdapter(
|
|||||||
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
|
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
|
||||||
it.loadLabel() ?: ""
|
it.loadLabel() ?: ""
|
||||||
}
|
}
|
||||||
listOfServices = serviceInfos.sortedWith(localeComparator)
|
listOfServices = serviceInfos.filter { it.panelActivity == null }
|
||||||
|
.sortedWith(localeComparator)
|
||||||
uiExecutor.execute(::notifyDataSetChanged)
|
uiExecutor.execute(::notifyDataSetChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,13 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.view.WindowInsets.Type
|
import android.view.WindowInsets.Type
|
||||||
|
import android.view.WindowManager
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.controls.management.ControlsAnimations
|
import com.android.systemui.controls.management.ControlsAnimations
|
||||||
|
import com.android.systemui.flags.FeatureFlags
|
||||||
|
import com.android.systemui.flags.Flags
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,6 +47,7 @@ class ControlsActivity @Inject constructor(
|
|||||||
private val uiController: ControlsUiController,
|
private val uiController: ControlsUiController,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
private val dreamManager: IDreamManager,
|
private val dreamManager: IDreamManager,
|
||||||
|
private val featureFlags: FeatureFlags
|
||||||
) : ComponentActivity() {
|
) : ComponentActivity() {
|
||||||
|
|
||||||
private lateinit var parent: ViewGroup
|
private lateinit var parent: ViewGroup
|
||||||
@@ -52,6 +56,9 @@ class ControlsActivity @Inject constructor(
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
|
||||||
|
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
|
||||||
|
}
|
||||||
|
|
||||||
setContentView(R.layout.controls_fullscreen)
|
setContentView(R.layout.controls_fullscreen)
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ interface ControlsUiController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the preferred activity to start, depending on if the user has favorited any
|
* Returns the preferred activity to start, depending on if the user has favorited any
|
||||||
* controls.
|
* controls or whether there are any app providing panels.
|
||||||
*/
|
*/
|
||||||
fun resolveActivity(): Class<*>
|
fun resolveActivity(): Class<*>
|
||||||
|
|
||||||
@@ -53,9 +53,43 @@ interface ControlsUiController {
|
|||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the structure that is currently preferred by the user.
|
* Returns the element that is currently preferred by the user.
|
||||||
*
|
*
|
||||||
* This structure will be the one that appears when the user first opens the controls activity.
|
* This element will be the one that appears when the user first opens the controls activity.
|
||||||
*/
|
*/
|
||||||
fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo
|
fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class SelectedItem {
|
||||||
|
|
||||||
|
abstract val name: CharSequence
|
||||||
|
abstract val hasControls: Boolean
|
||||||
|
abstract val componentName: ComponentName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the currently selected item for a structure.
|
||||||
|
*/
|
||||||
|
data class StructureItem(val structure: StructureInfo) : SelectedItem() {
|
||||||
|
override val name: CharSequence = structure.structure
|
||||||
|
override val hasControls: Boolean = structure.controls.isNotEmpty()
|
||||||
|
override val componentName: ComponentName = structure.componentName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the currently selected item for a service that provides a panel activity.
|
||||||
|
*
|
||||||
|
* The [componentName] is that of the service, as that is the expected identifier that should
|
||||||
|
* not change (to always provide proper migration).
|
||||||
|
*/
|
||||||
|
data class PanelItem(
|
||||||
|
val appName: CharSequence,
|
||||||
|
override val componentName:
|
||||||
|
ComponentName
|
||||||
|
) : SelectedItem() {
|
||||||
|
override val name: CharSequence = appName
|
||||||
|
override val hasControls: Boolean = true
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
val EMPTY_SELECTION: SelectedItem = StructureItem(StructureInfo.EMPTY_STRUCTURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter
|
|||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.ActivityOptions
|
import android.app.ActivityOptions
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -36,18 +37,22 @@ import android.view.animation.AccelerateInterpolator
|
|||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.ListPopupWindow
|
import android.widget.ListPopupWindow
|
||||||
import android.widget.Space
|
import android.widget.Space
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.systemui.Dumpable
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.CustomIconCache
|
import com.android.systemui.controls.CustomIconCache
|
||||||
import com.android.systemui.controls.controller.ControlInfo
|
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
import com.android.systemui.controls.controller.StructureInfo
|
import com.android.systemui.controls.controller.StructureInfo
|
||||||
|
import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_COMPONENT
|
||||||
|
import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_STRUCTURE
|
||||||
import com.android.systemui.controls.management.ControlAdapter
|
import com.android.systemui.controls.management.ControlAdapter
|
||||||
import com.android.systemui.controls.management.ControlsEditingActivity
|
import com.android.systemui.controls.management.ControlsEditingActivity
|
||||||
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
import com.android.systemui.controls.management.ControlsFavoritingActivity
|
||||||
@@ -56,16 +61,21 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
|||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.globalactions.GlobalActionsPopupMenu
|
import com.android.systemui.globalactions.GlobalActionsPopupMenu
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.settings.UserFileManager
|
import com.android.systemui.settings.UserFileManager
|
||||||
import com.android.systemui.settings.UserTracker
|
import com.android.systemui.settings.UserTracker
|
||||||
import com.android.systemui.shade.ShadeController
|
|
||||||
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
|
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
|
import com.android.systemui.util.asIndenting
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.indentIfPossible
|
||||||
|
import com.android.wm.shell.TaskViewFactory
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
import java.io.PrintWriter
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
|
import java.util.Optional
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -80,39 +90,34 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
val controlsListingController: Lazy<ControlsListingController>,
|
val controlsListingController: Lazy<ControlsListingController>,
|
||||||
val controlActionCoordinator: ControlActionCoordinator,
|
val controlActionCoordinator: ControlActionCoordinator,
|
||||||
private val activityStarter: ActivityStarter,
|
private val activityStarter: ActivityStarter,
|
||||||
private val shadeController: ShadeController,
|
|
||||||
private val iconCache: CustomIconCache,
|
private val iconCache: CustomIconCache,
|
||||||
private val controlsMetricsLogger: ControlsMetricsLogger,
|
private val controlsMetricsLogger: ControlsMetricsLogger,
|
||||||
private val keyguardStateController: KeyguardStateController,
|
private val keyguardStateController: KeyguardStateController,
|
||||||
private val userFileManager: UserFileManager,
|
private val userFileManager: UserFileManager,
|
||||||
private val userTracker: UserTracker,
|
private val userTracker: UserTracker,
|
||||||
) : ControlsUiController {
|
private val taskViewFactory: Optional<TaskViewFactory>,
|
||||||
|
dumpManager: DumpManager
|
||||||
|
) : ControlsUiController, Dumpable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PREF_COMPONENT = "controls_component"
|
private const val PREF_COMPONENT = "controls_component"
|
||||||
private const val PREF_STRUCTURE = "controls_structure"
|
private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
|
||||||
|
private const val PREF_IS_PANEL = "controls_is_panel"
|
||||||
|
|
||||||
private const val FADE_IN_MILLIS = 200L
|
private const val FADE_IN_MILLIS = 200L
|
||||||
|
|
||||||
private val EMPTY_COMPONENT = ComponentName("", "")
|
|
||||||
private val EMPTY_STRUCTURE = StructureInfo(
|
|
||||||
EMPTY_COMPONENT,
|
|
||||||
"",
|
|
||||||
mutableListOf<ControlInfo>()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var selectedStructure: StructureInfo = EMPTY_STRUCTURE
|
private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
|
||||||
private lateinit var allStructures: List<StructureInfo>
|
private lateinit var allStructures: List<StructureInfo>
|
||||||
private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
|
private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
|
||||||
private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
|
private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
|
||||||
private lateinit var parent: ViewGroup
|
private lateinit var parent: ViewGroup
|
||||||
private lateinit var lastItems: List<SelectionItem>
|
|
||||||
private var popup: ListPopupWindow? = null
|
private var popup: ListPopupWindow? = null
|
||||||
private var hidden = true
|
private var hidden = true
|
||||||
private lateinit var onDismiss: Runnable
|
private lateinit var onDismiss: Runnable
|
||||||
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
|
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
|
||||||
private var retainCache = false
|
private var retainCache = false
|
||||||
|
private var lastSelections = emptyList<SelectionItem>()
|
||||||
private val sharedPreferences
|
private val sharedPreferences
|
||||||
get() = userFileManager.getSharedPreferences(
|
get() = userFileManager.getSharedPreferences(
|
||||||
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
|
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
|
||||||
@@ -120,6 +125,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
userId = userTracker.userId
|
userId = userTracker.userId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private var taskViewController: PanelTaskViewController? = null
|
||||||
|
|
||||||
private val collator = Collator.getInstance(context.resources.configuration.locales[0])
|
private val collator = Collator.getInstance(context.resources.configuration.locales[0])
|
||||||
private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
|
private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
|
||||||
it.getTitle()
|
it.getTitle()
|
||||||
@@ -128,10 +135,12 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private val onSeedingComplete = Consumer<Boolean> {
|
private val onSeedingComplete = Consumer<Boolean> {
|
||||||
accepted ->
|
accepted ->
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
selectedStructure = controlsController.get().getFavorites().maxByOrNull {
|
selectedItem = controlsController.get().getFavorites().maxByOrNull {
|
||||||
it.controls.size
|
it.controls.size
|
||||||
} ?: EMPTY_STRUCTURE
|
}?.let {
|
||||||
updatePreferences(selectedStructure)
|
SelectedItem.StructureItem(it)
|
||||||
|
} ?: SelectedItem.EMPTY_SELECTION
|
||||||
|
updatePreferences(selectedItem)
|
||||||
}
|
}
|
||||||
reload(parent)
|
reload(parent)
|
||||||
}
|
}
|
||||||
@@ -139,6 +148,10 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private lateinit var activityContext: Context
|
private lateinit var activityContext: Context
|
||||||
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
|
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
|
||||||
|
|
||||||
|
init {
|
||||||
|
dumpManager.registerDumpable(javaClass.name, this)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createCallback(
|
private fun createCallback(
|
||||||
onResult: (List<SelectionItem>) -> Unit
|
onResult: (List<SelectionItem>) -> Unit
|
||||||
): ControlsListingController.ControlsListingCallback {
|
): ControlsListingController.ControlsListingCallback {
|
||||||
@@ -146,7 +159,15 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
|
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
|
||||||
val lastItems = serviceInfos.map {
|
val lastItems = serviceInfos.map {
|
||||||
val uid = it.serviceInfo.applicationInfo.uid
|
val uid = it.serviceInfo.applicationInfo.uid
|
||||||
SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
|
|
||||||
|
SelectionItem(
|
||||||
|
it.loadLabel(),
|
||||||
|
"",
|
||||||
|
it.loadIcon(),
|
||||||
|
it.componentName,
|
||||||
|
uid,
|
||||||
|
it.panelActivity
|
||||||
|
)
|
||||||
}
|
}
|
||||||
uiExecutor.execute {
|
uiExecutor.execute {
|
||||||
parent.removeAllViews()
|
parent.removeAllViews()
|
||||||
@@ -160,11 +181,13 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
|
|
||||||
override fun resolveActivity(): Class<*> {
|
override fun resolveActivity(): Class<*> {
|
||||||
val allStructures = controlsController.get().getFavorites()
|
val allStructures = controlsController.get().getFavorites()
|
||||||
val selectedStructure = getPreferredStructure(allStructures)
|
val selected = getPreferredSelectedItem(allStructures)
|
||||||
|
val anyPanels = controlsListingController.get().getCurrentServices()
|
||||||
|
.none { it.panelActivity != null }
|
||||||
|
|
||||||
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
|
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
|
||||||
ControlsActivity::class.java
|
ControlsActivity::class.java
|
||||||
} else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
|
} else if (!selected.hasControls && allStructures.size <= 1 && !anyPanels) {
|
||||||
ControlsProviderSelectorActivity::class.java
|
ControlsProviderSelectorActivity::class.java
|
||||||
} else {
|
} else {
|
||||||
ControlsActivity::class.java
|
ControlsActivity::class.java
|
||||||
@@ -186,31 +209,49 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
controlActionCoordinator.activityContext = activityContext
|
controlActionCoordinator.activityContext = activityContext
|
||||||
|
|
||||||
allStructures = controlsController.get().getFavorites()
|
allStructures = controlsController.get().getFavorites()
|
||||||
selectedStructure = getPreferredStructure(allStructures)
|
selectedItem = getPreferredSelectedItem(allStructures)
|
||||||
|
|
||||||
if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
|
if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
|
||||||
listingCallback = createCallback(::showSeedingView)
|
listingCallback = createCallback(::showSeedingView)
|
||||||
} else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
|
} else if (
|
||||||
|
selectedItem !is SelectedItem.PanelItem &&
|
||||||
|
!selectedItem.hasControls &&
|
||||||
|
allStructures.size <= 1
|
||||||
|
) {
|
||||||
// only show initial view if there are really no favorites across any structure
|
// only show initial view if there are really no favorites across any structure
|
||||||
listingCallback = createCallback(::showInitialSetupView)
|
listingCallback = createCallback(::initialView)
|
||||||
} else {
|
} else {
|
||||||
selectedStructure.controls.map {
|
val selected = selectedItem
|
||||||
ControlWithState(selectedStructure.componentName, it, null)
|
if (selected is SelectedItem.StructureItem) {
|
||||||
}.associateByTo(controlsById) {
|
selected.structure.controls.map {
|
||||||
ControlKey(selectedStructure.componentName, it.ci.controlId)
|
ControlWithState(selected.structure.componentName, it, null)
|
||||||
|
}.associateByTo(controlsById) {
|
||||||
|
ControlKey(selected.structure.componentName, it.ci.controlId)
|
||||||
|
}
|
||||||
|
controlsController.get().subscribeToFavorites(selected.structure)
|
||||||
}
|
}
|
||||||
listingCallback = createCallback(::showControlsView)
|
listingCallback = createCallback(::showControlsView)
|
||||||
controlsController.get().subscribeToFavorites(selectedStructure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controlsListingController.get().addCallback(listingCallback)
|
controlsListingController.get().addCallback(listingCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initialView(items: List<SelectionItem>) {
|
||||||
|
if (items.any { it.isPanel }) {
|
||||||
|
// We have at least a panel, so we'll end up showing that.
|
||||||
|
showControlsView(items)
|
||||||
|
} else {
|
||||||
|
showInitialSetupView(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun reload(parent: ViewGroup) {
|
private fun reload(parent: ViewGroup) {
|
||||||
if (hidden) return
|
if (hidden) return
|
||||||
|
|
||||||
controlsListingController.get().removeCallback(listingCallback)
|
controlsListingController.get().removeCallback(listingCallback)
|
||||||
controlsController.get().unsubscribe()
|
controlsController.get().unsubscribe()
|
||||||
|
taskViewController?.dismiss()
|
||||||
|
taskViewController = null
|
||||||
|
|
||||||
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
|
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
|
||||||
fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
|
fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
|
||||||
@@ -290,27 +331,90 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
private fun showControlsView(items: List<SelectionItem>) {
|
private fun showControlsView(items: List<SelectionItem>) {
|
||||||
controlViewsById.clear()
|
controlViewsById.clear()
|
||||||
|
|
||||||
val itemsByComponent = items.associateBy { it.componentName }
|
val (panels, structures) = items.partition { it.isPanel }
|
||||||
val itemsWithStructure = mutableListOf<SelectionItem>()
|
val panelComponents = panels.map { it.componentName }.toSet()
|
||||||
allStructures.mapNotNullTo(itemsWithStructure) {
|
|
||||||
|
val itemsByComponent = structures.associateBy { it.componentName }
|
||||||
|
.filterNot { it.key in panelComponents }
|
||||||
|
val panelsAndStructures = mutableListOf<SelectionItem>()
|
||||||
|
allStructures.mapNotNullTo(panelsAndStructures) {
|
||||||
itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
|
itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
|
||||||
}
|
}
|
||||||
itemsWithStructure.sortWith(localeComparator)
|
panelsAndStructures.addAll(panels)
|
||||||
|
|
||||||
val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
|
panelsAndStructures.sortWith(localeComparator)
|
||||||
|
|
||||||
controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
|
lastSelections = panelsAndStructures
|
||||||
|
|
||||||
createListView(selectionItem)
|
val selectionItem = findSelectionItem(selectedItem, panelsAndStructures)
|
||||||
createDropDown(itemsWithStructure, selectionItem)
|
?: if (panels.isNotEmpty()) {
|
||||||
|
// If we couldn't find a good selected item, but there's at least one panel,
|
||||||
|
// show a panel.
|
||||||
|
panels[0]
|
||||||
|
} else {
|
||||||
|
items[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeUpdateSelectedItem(selectionItem)
|
||||||
|
|
||||||
|
createControlsSpaceFrame()
|
||||||
|
|
||||||
|
if (taskViewFactory.isPresent && selectionItem.isPanel) {
|
||||||
|
createPanelView(selectionItem.panelComponentName!!)
|
||||||
|
} else if (!selectionItem.isPanel) {
|
||||||
|
controlsMetricsLogger
|
||||||
|
.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
|
||||||
|
createListView(selectionItem)
|
||||||
|
} else {
|
||||||
|
Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
|
||||||
|
}
|
||||||
|
|
||||||
|
createDropDown(panelsAndStructures, selectionItem)
|
||||||
createMenu()
|
createMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMenu() {
|
private fun createPanelView(componentName: ComponentName) {
|
||||||
val items = arrayOf(
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
context.resources.getString(R.string.controls_menu_add),
|
context,
|
||||||
context.resources.getString(R.string.controls_menu_edit)
|
0,
|
||||||
|
Intent().setComponent(componentName),
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE
|
||||||
|
val container = parent.requireViewById<FrameLayout>(R.id.controls_panel)
|
||||||
|
container.visibility = View.VISIBLE
|
||||||
|
container.post {
|
||||||
|
taskViewFactory.get().create(activityContext, uiExecutor) { taskView ->
|
||||||
|
taskViewController = PanelTaskViewController(
|
||||||
|
activityContext,
|
||||||
|
uiExecutor,
|
||||||
|
pendingIntent,
|
||||||
|
taskView,
|
||||||
|
onDismiss::run
|
||||||
|
).also {
|
||||||
|
container.addView(taskView)
|
||||||
|
it.launchTaskView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMenu() {
|
||||||
|
val isPanel = selectedItem is SelectedItem.PanelItem
|
||||||
|
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
|
||||||
|
?: EMPTY_STRUCTURE
|
||||||
|
|
||||||
|
val items = if (isPanel) {
|
||||||
|
arrayOf(
|
||||||
|
context.resources.getString(R.string.controls_menu_add),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
arrayOf(
|
||||||
|
context.resources.getString(R.string.controls_menu_add),
|
||||||
|
context.resources.getString(R.string.controls_menu_edit)
|
||||||
|
)
|
||||||
|
}
|
||||||
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
|
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
|
||||||
|
|
||||||
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
|
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
|
||||||
@@ -331,7 +435,13 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
) {
|
) {
|
||||||
when (pos) {
|
when (pos) {
|
||||||
// 0: Add Control
|
// 0: Add Control
|
||||||
0 -> startFavoritingActivity(selectedStructure)
|
0 -> {
|
||||||
|
if (isPanel) {
|
||||||
|
startProviderSelectorActivity()
|
||||||
|
} else {
|
||||||
|
startFavoritingActivity(selectedStructure)
|
||||||
|
}
|
||||||
|
}
|
||||||
// 1: Edit controls
|
// 1: Edit controls
|
||||||
1 -> startEditingActivity(selectedStructure)
|
1 -> startEditingActivity(selectedStructure)
|
||||||
}
|
}
|
||||||
@@ -353,6 +463,9 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
addAll(items)
|
addAll(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val iconSize = context.resources
|
||||||
|
.getDimensionPixelSize(R.dimen.controls_header_app_icon_size)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default spinner widget does not work with the window type required
|
* Default spinner widget does not work with the window type required
|
||||||
* for this dialog. Use a textView with the ListPopupWindow to achieve
|
* for this dialog. Use a textView with the ListPopupWindow to achieve
|
||||||
@@ -363,14 +476,21 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
// override the default color on the dropdown drawable
|
// override the default color on the dropdown drawable
|
||||||
(getBackground() as LayerDrawable).getDrawable(0)
|
(getBackground() as LayerDrawable).getDrawable(0)
|
||||||
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
|
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
|
||||||
}
|
selected.icon.setBounds(0, 0, iconSize, iconSize)
|
||||||
|
compoundDrawablePadding = (iconSize / 2.4f).toInt()
|
||||||
if (items.size == 1) {
|
setCompoundDrawablesRelative(selected.icon, null, null, null)
|
||||||
spinner.setBackground(null)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val anchor = parent.requireViewById<ViewGroup>(R.id.controls_header)
|
val anchor = parent.requireViewById<ViewGroup>(R.id.controls_header)
|
||||||
|
if (items.size == 1) {
|
||||||
|
spinner.setBackground(null)
|
||||||
|
anchor.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
spinner.background = parent.context.resources
|
||||||
|
.getDrawable(R.drawable.control_spinner_background)
|
||||||
|
}
|
||||||
|
|
||||||
anchor.setOnClickListener(object : View.OnClickListener {
|
anchor.setOnClickListener(object : View.OnClickListener {
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
popup = GlobalActionsPopupMenu(
|
popup = GlobalActionsPopupMenu(
|
||||||
@@ -398,14 +518,20 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createListView(selected: SelectionItem) {
|
private fun createControlsSpaceFrame() {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(activityContext)
|
||||||
inflater.inflate(R.layout.controls_with_favorites, parent, true)
|
inflater.inflate(R.layout.controls_with_favorites, parent, true)
|
||||||
|
|
||||||
parent.requireViewById<ImageView>(R.id.controls_close).apply {
|
parent.requireViewById<ImageView>(R.id.controls_close).apply {
|
||||||
setOnClickListener { _: View -> onDismiss.run() }
|
setOnClickListener { _: View -> onDismiss.run() }
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createListView(selected: SelectionItem) {
|
||||||
|
if (selectedItem !is SelectedItem.StructureItem) return
|
||||||
|
val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure
|
||||||
|
val inflater = LayoutInflater.from(activityContext)
|
||||||
|
|
||||||
val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
|
val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
|
||||||
|
|
||||||
@@ -453,35 +579,51 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo {
|
override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
|
||||||
if (structures.isEmpty()) return EMPTY_STRUCTURE
|
val sp = sharedPreferences
|
||||||
|
|
||||||
val component = sharedPreferences.getString(PREF_COMPONENT, null)?.let {
|
val component = sp.getString(PREF_COMPONENT, null)?.let {
|
||||||
ComponentName.unflattenFromString(it)
|
ComponentName.unflattenFromString(it)
|
||||||
} ?: EMPTY_COMPONENT
|
} ?: EMPTY_COMPONENT
|
||||||
val structure = sharedPreferences.getString(PREF_STRUCTURE, "")
|
val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
|
||||||
|
val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
|
||||||
return structures.firstOrNull {
|
return if (isPanel) {
|
||||||
component == it.componentName && structure == it.structure
|
SelectedItem.PanelItem(name, component)
|
||||||
} ?: structures.get(0)
|
} else {
|
||||||
|
if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
|
||||||
|
SelectedItem.StructureItem(structures.firstOrNull {
|
||||||
|
component == it.componentName && name == it.structure
|
||||||
|
} ?: structures.get(0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePreferences(si: StructureInfo) {
|
private fun updatePreferences(si: SelectedItem) {
|
||||||
if (si == EMPTY_STRUCTURE) return
|
|
||||||
sharedPreferences.edit()
|
sharedPreferences.edit()
|
||||||
.putString(PREF_COMPONENT, si.componentName.flattenToString())
|
.putString(PREF_COMPONENT, si.componentName.flattenToString())
|
||||||
.putString(PREF_STRUCTURE, si.structure.toString())
|
.putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString())
|
||||||
.commit()
|
.putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
|
||||||
|
val newSelection = if (item.isPanel) {
|
||||||
|
SelectedItem.PanelItem(item.appName, item.componentName)
|
||||||
|
} else {
|
||||||
|
SelectedItem.StructureItem(allStructures.firstOrNull {
|
||||||
|
it.structure == item.structure && it.componentName == item.componentName
|
||||||
|
} ?: EMPTY_STRUCTURE)
|
||||||
|
}
|
||||||
|
return if (newSelection != selectedItem ) {
|
||||||
|
selectedItem = newSelection
|
||||||
|
updatePreferences(selectedItem)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun switchAppOrStructure(item: SelectionItem) {
|
private fun switchAppOrStructure(item: SelectionItem) {
|
||||||
val newSelection = allStructures.first {
|
if (maybeUpdateSelectedItem(item)) {
|
||||||
it.structure == item.structure && it.componentName == item.componentName
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newSelection != selectedStructure) {
|
|
||||||
selectedStructure = newSelection
|
|
||||||
updatePreferences(selectedStructure)
|
|
||||||
reload(parent)
|
reload(parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,6 +647,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
|
|
||||||
closeDialogs(true)
|
closeDialogs(true)
|
||||||
controlsController.get().unsubscribe()
|
controlsController.get().unsubscribe()
|
||||||
|
taskViewController?.dismiss()
|
||||||
|
taskViewController = null
|
||||||
|
|
||||||
parent.removeAllViews()
|
parent.removeAllViews()
|
||||||
controlsById.clear()
|
controlsById.clear()
|
||||||
@@ -545,20 +689,46 @@ class ControlsUiControllerImpl @Inject constructor (
|
|||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findSelectionItem(si: StructureInfo, items: List<SelectionItem>): SelectionItem? =
|
private fun findSelectionItem(si: SelectedItem, items: List<SelectionItem>): SelectionItem? =
|
||||||
items.firstOrNull {
|
items.firstOrNull { it.matches(si) }
|
||||||
it.componentName == si.componentName && it.structure == si.structure
|
|
||||||
|
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||||
|
pw.println("ControlsUiControllerImpl:")
|
||||||
|
pw.asIndenting().indentIfPossible {
|
||||||
|
println("hidden: $hidden")
|
||||||
|
println("selectedItem: $selectedItem")
|
||||||
|
println("lastSelections: $lastSelections")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class SelectionItem(
|
@VisibleForTesting
|
||||||
|
internal data class SelectionItem(
|
||||||
val appName: CharSequence,
|
val appName: CharSequence,
|
||||||
val structure: CharSequence,
|
val structure: CharSequence,
|
||||||
val icon: Drawable,
|
val icon: Drawable,
|
||||||
val componentName: ComponentName,
|
val componentName: ComponentName,
|
||||||
val uid: Int
|
val uid: Int,
|
||||||
|
val panelComponentName: ComponentName?
|
||||||
) {
|
) {
|
||||||
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
|
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
|
||||||
|
|
||||||
|
val isPanel: Boolean = panelComponentName != null
|
||||||
|
|
||||||
|
fun matches(selectedItem: SelectedItem): Boolean {
|
||||||
|
if (componentName != selectedItem.componentName) {
|
||||||
|
// Not the same component so they are not the same.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isPanel || selectedItem is SelectedItem.PanelItem) {
|
||||||
|
// As they have the same component, if [this.isPanel] then we may be migrating from
|
||||||
|
// device controls API into panel. Want this to match, even if the selectedItem is not
|
||||||
|
// a panel. We don't want to match on app name because that can change with locale.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Return true if we find a structure with the correct name
|
||||||
|
return structure == (selectedItem as SelectedItem.StructureItem).structure.structure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ItemAdapter(
|
private class ItemAdapter(
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.controls.ui
|
||||||
|
|
||||||
|
import android.app.ActivityOptions
|
||||||
|
import android.app.ActivityTaskManager
|
||||||
|
import android.app.ActivityTaskManager.INVALID_TASK_ID
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.android.systemui.util.boundsOnScreen
|
||||||
|
import com.android.wm.shell.TaskView
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
class PanelTaskViewController(
|
||||||
|
private val activityContext: Context,
|
||||||
|
private val uiExecutor: Executor,
|
||||||
|
private val pendingIntent: PendingIntent,
|
||||||
|
private val taskView: TaskView,
|
||||||
|
private val hide: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var detailTaskId = INVALID_TASK_ID
|
||||||
|
|
||||||
|
private val fillInIntent =
|
||||||
|
Intent().apply {
|
||||||
|
// Apply flags to make behaviour match documentLaunchMode=always.
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeDetailTask() {
|
||||||
|
if (detailTaskId == INVALID_TASK_ID) return
|
||||||
|
ActivityTaskManager.getInstance().removeTask(detailTaskId)
|
||||||
|
detailTaskId = INVALID_TASK_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
private val stateCallback =
|
||||||
|
object : TaskView.Listener {
|
||||||
|
override fun onInitialized() {
|
||||||
|
|
||||||
|
val options =
|
||||||
|
ActivityOptions.makeCustomAnimation(
|
||||||
|
activityContext,
|
||||||
|
0 /* enterResId */,
|
||||||
|
0 /* exitResId */
|
||||||
|
)
|
||||||
|
options.taskAlwaysOnTop = true
|
||||||
|
|
||||||
|
taskView.post {
|
||||||
|
taskView.startActivity(
|
||||||
|
pendingIntent,
|
||||||
|
fillInIntent,
|
||||||
|
options,
|
||||||
|
taskView.boundsOnScreen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTaskRemovalStarted(taskId: Int) {
|
||||||
|
detailTaskId = INVALID_TASK_ID
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTaskCreated(taskId: Int, name: ComponentName?) {
|
||||||
|
detailTaskId = taskId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReleased() {
|
||||||
|
removeDetailTask()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressedOnTaskRoot(taskId: Int) {
|
||||||
|
dismiss()
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismiss() {
|
||||||
|
taskView.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launchTaskView() {
|
||||||
|
taskView.setListener(uiExecutor, stateCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2020 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.android.systemui.globalactions;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ScrollView;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When measured, this view sets the minimum height of its first child to be equal to its own
|
|
||||||
* target height.
|
|
||||||
*
|
|
||||||
* This ensures fall-through click handlers can be placed on this view's child component.
|
|
||||||
*/
|
|
||||||
public class MinHeightScrollView extends ScrollView {
|
|
||||||
public MinHeightScrollView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
View firstChild = getChildAt(0);
|
|
||||||
if (firstChild != null) {
|
|
||||||
firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec));
|
|
||||||
}
|
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,7 @@ import com.android.systemui.controls.dagger.ControlsComponent
|
|||||||
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
|
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
|
import com.android.systemui.controls.ui.SelectedItem
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
@@ -125,14 +126,15 @@ class DeviceControlsTile @Inject constructor(
|
|||||||
state.icon = icon
|
state.icon = icon
|
||||||
if (controlsComponent.isEnabled() && hasControlsApps.get()) {
|
if (controlsComponent.isEnabled() && hasControlsApps.get()) {
|
||||||
if (controlsComponent.getVisibility() == AVAILABLE) {
|
if (controlsComponent.getVisibility() == AVAILABLE) {
|
||||||
val structureInfo = controlsComponent
|
val selection = controlsComponent
|
||||||
.getControlsController().get().getPreferredStructure()
|
.getControlsController().get().getPreferredSelection()
|
||||||
state.state = if (structureInfo.controls.isEmpty()) {
|
state.state = if (selection is SelectedItem.StructureItem &&
|
||||||
|
selection.structure.controls.isEmpty()) {
|
||||||
Tile.STATE_INACTIVE
|
Tile.STATE_INACTIVE
|
||||||
} else {
|
} else {
|
||||||
Tile.STATE_ACTIVE
|
Tile.STATE_ACTIVE
|
||||||
}
|
}
|
||||||
val label = structureInfo.structure
|
val label = selection.name
|
||||||
state.secondaryLabel = if (label == tileLabel) null else label
|
state.secondaryLabel = if (label == tileLabel) null else label
|
||||||
} else {
|
} else {
|
||||||
state.state = Tile.STATE_INACTIVE
|
state.state = Tile.STATE_INACTIVE
|
||||||
|
|||||||
@@ -140,6 +140,9 @@ public class DeviceControlsControllerImpl @Inject constructor(
|
|||||||
// is out of sync, perhaps through a device restore, and update the
|
// is out of sync, perhaps through a device restore, and update the
|
||||||
// preference
|
// preference
|
||||||
addPackageToSeededSet(prefs, pkg)
|
addPackageToSeededSet(prefs, pkg)
|
||||||
|
} else if (it.panelActivity != null) {
|
||||||
|
// Do not seed for packages with panels
|
||||||
|
addPackageToSeededSet(prefs, pkg)
|
||||||
} else {
|
} else {
|
||||||
componentsToSeed.add(it.componentName)
|
componentsToSeed.add(it.componentName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.mockito.any
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -34,9 +35,8 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.ArgumentCaptor
|
import org.mockito.ArgumentCaptor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.mock
|
|
||||||
import org.mockito.Mockito.verify
|
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -70,7 +70,7 @@ class AppAdapterTest : SysuiTestCase() {
|
|||||||
fun testOnServicesUpdated_nullLoadLabel() {
|
fun testOnServicesUpdated_nullLoadLabel() {
|
||||||
val captor = ArgumentCaptor
|
val captor = ArgumentCaptor
|
||||||
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
||||||
val controlsServiceInfo = mock(ControlsServiceInfo::class.java)
|
val controlsServiceInfo = mock<ControlsServiceInfo>()
|
||||||
val serviceInfo = listOf(controlsServiceInfo)
|
val serviceInfo = listOf(controlsServiceInfo)
|
||||||
`when`(controlsServiceInfo.loadLabel()).thenReturn(null)
|
`when`(controlsServiceInfo.loadLabel()).thenReturn(null)
|
||||||
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
||||||
@@ -81,4 +81,32 @@ class AppAdapterTest : SysuiTestCase() {
|
|||||||
|
|
||||||
assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
|
assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnServicesUpdatedDoesntHavePanels() {
|
||||||
|
val captor = ArgumentCaptor
|
||||||
|
.forClass(ControlsListingController.ControlsListingCallback::class.java)
|
||||||
|
val serviceInfo = listOf(
|
||||||
|
ControlsServiceInfo("no panel", null),
|
||||||
|
ControlsServiceInfo("panel", mock())
|
||||||
|
)
|
||||||
|
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
|
||||||
|
|
||||||
|
captor.value.onServicesUpdated(serviceInfo)
|
||||||
|
backgroundExecutor.runAllReady()
|
||||||
|
uiExecutor.runAllReady()
|
||||||
|
|
||||||
|
assertThat(adapter.itemCount).isEqualTo(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ControlsServiceInfo(
|
||||||
|
label: CharSequence,
|
||||||
|
panelComponentName: ComponentName? = null
|
||||||
|
): ControlsServiceInfo {
|
||||||
|
return mock {
|
||||||
|
`when`(this.loadLabel()).thenReturn(label)
|
||||||
|
`when`(this.panelActivity).thenReturn(panelComponentName)
|
||||||
|
`when`(this.loadIcon()).thenReturn(mock())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
package com.android.systemui.controls.ui
|
package com.android.systemui.controls.ui
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
|
import android.widget.FrameLayout
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
@@ -26,6 +28,7 @@ import com.android.systemui.controls.CustomIconCache
|
|||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
import com.android.systemui.controls.controller.StructureInfo
|
import com.android.systemui.controls.controller.StructureInfo
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.settings.UserFileManager
|
import com.android.systemui.settings.UserFileManager
|
||||||
import com.android.systemui.settings.UserTracker
|
import com.android.systemui.settings.UserTracker
|
||||||
@@ -34,9 +37,12 @@ import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
|
|||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
import com.android.systemui.util.FakeSharedPreferences
|
import com.android.systemui.util.FakeSharedPreferences
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
|
import com.android.wm.shell.TaskViewFactory
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
|
import java.util.Optional
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -44,7 +50,7 @@ import org.mockito.Mock
|
|||||||
import org.mockito.Mockito.anyInt
|
import org.mockito.Mockito.anyInt
|
||||||
import org.mockito.Mockito.anyString
|
import org.mockito.Mockito.anyString
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.times
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
@@ -63,16 +69,22 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Mock lateinit var keyguardStateController: KeyguardStateController
|
@Mock lateinit var keyguardStateController: KeyguardStateController
|
||||||
@Mock lateinit var userFileManager: UserFileManager
|
@Mock lateinit var userFileManager: UserFileManager
|
||||||
@Mock lateinit var userTracker: UserTracker
|
@Mock lateinit var userTracker: UserTracker
|
||||||
|
@Mock lateinit var taskViewFactory: TaskViewFactory
|
||||||
|
@Mock lateinit var activityContext: Context
|
||||||
|
@Mock lateinit var dumpManager: DumpManager
|
||||||
val sharedPreferences = FakeSharedPreferences()
|
val sharedPreferences = FakeSharedPreferences()
|
||||||
|
|
||||||
var uiExecutor = FakeExecutor(FakeSystemClock())
|
var uiExecutor = FakeExecutor(FakeSystemClock())
|
||||||
var bgExecutor = FakeExecutor(FakeSystemClock())
|
var bgExecutor = FakeExecutor(FakeSystemClock())
|
||||||
lateinit var underTest: ControlsUiControllerImpl
|
lateinit var underTest: ControlsUiControllerImpl
|
||||||
|
lateinit var parent: FrameLayout
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
parent = FrameLayout(mContext)
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
ControlsUiControllerImpl(
|
ControlsUiControllerImpl(
|
||||||
Lazy { controlsController },
|
Lazy { controlsController },
|
||||||
@@ -82,12 +94,13 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
Lazy { controlsListingController },
|
Lazy { controlsListingController },
|
||||||
controlActionCoordinator,
|
controlActionCoordinator,
|
||||||
activityStarter,
|
activityStarter,
|
||||||
shadeController,
|
|
||||||
iconCache,
|
iconCache,
|
||||||
controlsMetricsLogger,
|
controlsMetricsLogger,
|
||||||
keyguardStateController,
|
keyguardStateController,
|
||||||
userFileManager,
|
userFileManager,
|
||||||
userTracker
|
userTracker,
|
||||||
|
Optional.of(taskViewFactory),
|
||||||
|
dumpManager
|
||||||
)
|
)
|
||||||
`when`(
|
`when`(
|
||||||
userFileManager.getSharedPreferences(
|
userFileManager.getSharedPreferences(
|
||||||
@@ -105,8 +118,8 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testGetPreferredStructure() {
|
fun testGetPreferredStructure() {
|
||||||
val structureInfo = mock(StructureInfo::class.java)
|
val structureInfo = mock(StructureInfo::class.java)
|
||||||
underTest.getPreferredStructure(listOf(structureInfo))
|
underTest.getPreferredSelectedItem(listOf(structureInfo))
|
||||||
verify(userFileManager, times(2))
|
verify(userFileManager)
|
||||||
.getSharedPreferences(
|
.getSharedPreferences(
|
||||||
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
|
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
|
||||||
mode = 0,
|
mode = 0,
|
||||||
@@ -116,25 +129,30 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetPreferredStructure_differentUserId() {
|
fun testGetPreferredStructure_differentUserId() {
|
||||||
val structureInfo =
|
val selectedItems =
|
||||||
listOf(
|
listOf(
|
||||||
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()),
|
SelectedItem.StructureItem(
|
||||||
StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList()),
|
StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
|
||||||
|
),
|
||||||
|
SelectedItem.StructureItem(
|
||||||
|
StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
val structures = selectedItems.map { it.structure }
|
||||||
sharedPreferences
|
sharedPreferences
|
||||||
.edit()
|
.edit()
|
||||||
.putString("controls_component", structureInfo[0].componentName.flattenToString())
|
.putString("controls_component", selectedItems[0].componentName.flattenToString())
|
||||||
.putString("controls_structure", structureInfo[0].structure.toString())
|
.putString("controls_structure", selectedItems[0].name.toString())
|
||||||
.commit()
|
.commit()
|
||||||
|
|
||||||
val differentSharedPreferences = FakeSharedPreferences()
|
val differentSharedPreferences = FakeSharedPreferences()
|
||||||
differentSharedPreferences
|
differentSharedPreferences
|
||||||
.edit()
|
.edit()
|
||||||
.putString("controls_component", structureInfo[1].componentName.flattenToString())
|
.putString("controls_component", selectedItems[1].componentName.flattenToString())
|
||||||
.putString("controls_structure", structureInfo[1].structure.toString())
|
.putString("controls_structure", selectedItems[1].name.toString())
|
||||||
.commit()
|
.commit()
|
||||||
|
|
||||||
val previousPreferredStructure = underTest.getPreferredStructure(structureInfo)
|
val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
|
||||||
|
|
||||||
`when`(
|
`when`(
|
||||||
userFileManager.getSharedPreferences(
|
userFileManager.getSharedPreferences(
|
||||||
@@ -146,10 +164,39 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
|
|||||||
.thenReturn(differentSharedPreferences)
|
.thenReturn(differentSharedPreferences)
|
||||||
`when`(userTracker.userId).thenReturn(1)
|
`when`(userTracker.userId).thenReturn(1)
|
||||||
|
|
||||||
val currentPreferredStructure = underTest.getPreferredStructure(structureInfo)
|
val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
|
||||||
|
|
||||||
assertThat(previousPreferredStructure).isEqualTo(structureInfo[0])
|
assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
|
||||||
assertThat(currentPreferredStructure).isEqualTo(structureInfo[1])
|
assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
|
||||||
assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
|
assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetPreferredPanel() {
|
||||||
|
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
|
||||||
|
sharedPreferences
|
||||||
|
.edit()
|
||||||
|
.putString("controls_component", panel.componentName.flattenToString())
|
||||||
|
.putString("controls_structure", panel.appName.toString())
|
||||||
|
.putBoolean("controls_is_panel", true)
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
val selected = underTest.getPreferredSelectedItem(emptyList())
|
||||||
|
|
||||||
|
assertThat(selected).isEqualTo(panel)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPanelDoesNotRefreshControls() {
|
||||||
|
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
|
||||||
|
sharedPreferences
|
||||||
|
.edit()
|
||||||
|
.putString("controls_component", panel.componentName.flattenToString())
|
||||||
|
.putString("controls_structure", panel.appName.toString())
|
||||||
|
.putBoolean("controls_is_panel", true)
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
underTest.show(parent, {}, activityContext)
|
||||||
|
verify(controlsController, never()).refreshStatus(any(), any())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.controls.ui
|
||||||
|
|
||||||
|
import android.app.ActivityOptions
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
||||||
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.testing.TestableLooper
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.util.boundsOnScreen
|
||||||
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.capture
|
||||||
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
|
import com.android.wm.shell.TaskView
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.Captor
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@TestableLooper.RunWithLooper
|
||||||
|
class PanelTaskViewControllerTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val FAKE_BOUNDS = Rect(10, 20, 30, 40)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mock private lateinit var activityContext: Context
|
||||||
|
@Mock private lateinit var taskView: TaskView
|
||||||
|
@Mock private lateinit var pendingIntent: PendingIntent
|
||||||
|
@Mock private lateinit var hideRunnable: () -> Unit
|
||||||
|
|
||||||
|
@Captor private lateinit var listenerCaptor: ArgumentCaptor<TaskView.Listener>
|
||||||
|
|
||||||
|
private lateinit var uiExecutor: FakeExecutor
|
||||||
|
private lateinit var underTest: PanelTaskViewController
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
whenever(taskView.boundsOnScreen).thenAnswer { (it.arguments[0] as Rect).set(FAKE_BOUNDS) }
|
||||||
|
whenever(taskView.post(any())).thenAnswer {
|
||||||
|
uiExecutor.execute(it.arguments[0] as Runnable)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
uiExecutor = FakeExecutor(FakeSystemClock())
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
PanelTaskViewController(
|
||||||
|
activityContext,
|
||||||
|
uiExecutor,
|
||||||
|
pendingIntent,
|
||||||
|
taskView,
|
||||||
|
hideRunnable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLaunchTaskViewAttachedListener() {
|
||||||
|
underTest.launchTaskView()
|
||||||
|
verify(taskView).setListener(eq(uiExecutor), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTaskViewOnInitializeStartsActivity() {
|
||||||
|
underTest.launchTaskView()
|
||||||
|
verify(taskView).setListener(any(), capture(listenerCaptor))
|
||||||
|
|
||||||
|
listenerCaptor.value.onInitialized()
|
||||||
|
uiExecutor.runAllReady()
|
||||||
|
|
||||||
|
val intentCaptor = argumentCaptor<Intent>()
|
||||||
|
val optionsCaptor = argumentCaptor<ActivityOptions>()
|
||||||
|
|
||||||
|
verify(taskView)
|
||||||
|
.startActivity(
|
||||||
|
eq(pendingIntent),
|
||||||
|
/* fillInIntent */ capture(intentCaptor),
|
||||||
|
capture(optionsCaptor),
|
||||||
|
eq(FAKE_BOUNDS)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(intentCaptor.value.flags)
|
||||||
|
.isEqualTo(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||||
|
assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testHideRunnableCalledWhenBackOnRoot() {
|
||||||
|
underTest.launchTaskView()
|
||||||
|
verify(taskView).setListener(any(), capture(listenerCaptor))
|
||||||
|
|
||||||
|
listenerCaptor.value.onBackPressedOnTaskRoot(0)
|
||||||
|
|
||||||
|
verify(hideRunnable).invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTaskViewReleasedOnDismiss() {
|
||||||
|
underTest.dismiss()
|
||||||
|
verify(taskView).release()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTaskViewReleasedOnBackOnRoot() {
|
||||||
|
underTest.launchTaskView()
|
||||||
|
verify(taskView).setListener(any(), capture(listenerCaptor))
|
||||||
|
|
||||||
|
listenerCaptor.value.onBackPressedOnTaskRoot(0)
|
||||||
|
verify(taskView).release()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnTaskRemovalStarted() {
|
||||||
|
underTest.launchTaskView()
|
||||||
|
verify(taskView).setListener(any(), capture(listenerCaptor))
|
||||||
|
|
||||||
|
listenerCaptor.value.onTaskRemovalStarted(0)
|
||||||
|
verify(taskView).release()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.android.systemui.controls.ui
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.controls.controller.StructureInfo
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class SelectionItemTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMatchBadComponentName_false() {
|
||||||
|
val selectionItem =
|
||||||
|
SelectionItem(
|
||||||
|
appName = "app",
|
||||||
|
structure = "structure",
|
||||||
|
icon = mock(),
|
||||||
|
componentName = ComponentName("pkg", "cls"),
|
||||||
|
uid = 0,
|
||||||
|
panelComponentName = null
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
selectionItem.matches(
|
||||||
|
SelectedItem.StructureItem(
|
||||||
|
StructureInfo(ComponentName("", ""), "s", emptyList())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.isFalse()
|
||||||
|
assertThat(selectionItem.matches(SelectedItem.PanelItem("name", ComponentName("", ""))))
|
||||||
|
.isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMatchSameComponentName_panelSelected_true() {
|
||||||
|
val componentName = ComponentName("pkg", "cls")
|
||||||
|
|
||||||
|
val selectionItem =
|
||||||
|
SelectionItem(
|
||||||
|
appName = "app",
|
||||||
|
structure = "structure",
|
||||||
|
icon = mock(),
|
||||||
|
componentName = componentName,
|
||||||
|
uid = 0,
|
||||||
|
panelComponentName = null
|
||||||
|
)
|
||||||
|
assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMatchSameComponentName_panelSelection_true() {
|
||||||
|
val componentName = ComponentName("pkg", "cls")
|
||||||
|
|
||||||
|
val selectionItem =
|
||||||
|
SelectionItem(
|
||||||
|
appName = "app",
|
||||||
|
structure = "structure",
|
||||||
|
icon = mock(),
|
||||||
|
componentName = componentName,
|
||||||
|
uid = 0,
|
||||||
|
panelComponentName = ComponentName("pkg", "panel")
|
||||||
|
)
|
||||||
|
assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMatchSameComponentSameStructure_true() {
|
||||||
|
val componentName = ComponentName("pkg", "cls")
|
||||||
|
val structureName = "structure"
|
||||||
|
|
||||||
|
val structureItem =
|
||||||
|
SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
|
||||||
|
|
||||||
|
val selectionItem =
|
||||||
|
SelectionItem(
|
||||||
|
appName = "app",
|
||||||
|
structure = structureName,
|
||||||
|
icon = mock(),
|
||||||
|
componentName = componentName,
|
||||||
|
uid = 0,
|
||||||
|
panelComponentName = null
|
||||||
|
)
|
||||||
|
assertThat(selectionItem.matches(structureItem)).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMatchSameComponentDifferentStructure_false() {
|
||||||
|
val componentName = ComponentName("pkg", "cls")
|
||||||
|
val structureName = "structure"
|
||||||
|
|
||||||
|
val structureItem =
|
||||||
|
SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
|
||||||
|
|
||||||
|
val selectionItem =
|
||||||
|
SelectionItem(
|
||||||
|
appName = "app",
|
||||||
|
structure = "other",
|
||||||
|
icon = mock(),
|
||||||
|
componentName = componentName,
|
||||||
|
uid = 0,
|
||||||
|
panelComponentName = null
|
||||||
|
)
|
||||||
|
assertThat(selectionItem.matches(structureItem)).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ import com.android.systemui.controls.dagger.ControlsComponent
|
|||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
import com.android.systemui.controls.ui.ControlsActivity
|
import com.android.systemui.controls.ui.ControlsActivity
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
|
import com.android.systemui.controls.ui.SelectedItem
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||||
import com.android.systemui.qs.QSHost
|
import com.android.systemui.qs.QSHost
|
||||||
@@ -118,8 +119,9 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
|||||||
`when`(qsHost.context).thenReturn(spiedContext)
|
`when`(qsHost.context).thenReturn(spiedContext)
|
||||||
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
|
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
|
||||||
`when`(controlsComponent.isEnabled()).thenReturn(true)
|
`when`(controlsComponent.isEnabled()).thenReturn(true)
|
||||||
`when`(controlsController.getPreferredStructure())
|
`when`(controlsController.getPreferredSelection())
|
||||||
.thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
|
.thenReturn(SelectedItem.StructureItem(
|
||||||
|
StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
|
||||||
secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
|
secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
|
||||||
|
|
||||||
setupControlsComponent()
|
setupControlsComponent()
|
||||||
@@ -226,12 +228,12 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
|||||||
capture(listingCallbackCaptor)
|
capture(listingCallbackCaptor)
|
||||||
)
|
)
|
||||||
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
|
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
|
||||||
`when`(controlsController.getPreferredStructure()).thenReturn(
|
`when`(controlsController.getPreferredSelection()).thenReturn(
|
||||||
StructureInfo(
|
SelectedItem.StructureItem(StructureInfo(
|
||||||
ComponentName("pkg", "cls"),
|
ComponentName("pkg", "cls"),
|
||||||
"structure",
|
"structure",
|
||||||
listOf(ControlInfo("id", "title", "subtitle", 1))
|
listOf(ControlInfo("id", "title", "subtitle", 1))
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
||||||
@@ -247,8 +249,9 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
|||||||
capture(listingCallbackCaptor)
|
capture(listingCallbackCaptor)
|
||||||
)
|
)
|
||||||
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
|
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
|
||||||
`when`(controlsController.getPreferredStructure())
|
`when`(controlsController.getPreferredSelection())
|
||||||
.thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
|
.thenReturn(SelectedItem.StructureItem(
|
||||||
|
StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
|
||||||
|
|
||||||
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
||||||
testableLooper.processAllMessages()
|
testableLooper.processAllMessages()
|
||||||
@@ -256,6 +259,22 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
|||||||
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
|
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStateActiveIfPreferredIsPanel() {
|
||||||
|
verify(controlsListingController).observe(
|
||||||
|
any(LifecycleOwner::class.java),
|
||||||
|
capture(listingCallbackCaptor)
|
||||||
|
)
|
||||||
|
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
|
||||||
|
`when`(controlsController.getPreferredSelection())
|
||||||
|
.thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
|
||||||
|
|
||||||
|
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
||||||
|
testableLooper.processAllMessages()
|
||||||
|
|
||||||
|
assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStateInactiveIfLocked() {
|
fun testStateInactiveIfLocked() {
|
||||||
verify(controlsListingController).observe(
|
verify(controlsListingController).observe(
|
||||||
@@ -303,12 +322,12 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
|
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
|
||||||
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
|
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
|
||||||
`when`(controlsController.getPreferredStructure()).thenReturn(
|
`when`(controlsController.getPreferredSelection()).thenReturn(
|
||||||
StructureInfo(
|
SelectedItem.StructureItem(StructureInfo(
|
||||||
ComponentName("pkg", "cls"),
|
ComponentName("pkg", "cls"),
|
||||||
"structure",
|
"structure",
|
||||||
listOf(ControlInfo("id", "title", "subtitle", 1))
|
listOf(ControlInfo("id", "title", "subtitle", 1))
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
||||||
@@ -334,12 +353,12 @@ class DeviceControlsTileTest : SysuiTestCase() {
|
|||||||
`when`(controlsComponent.getVisibility())
|
`when`(controlsComponent.getVisibility())
|
||||||
.thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
|
.thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
|
||||||
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
|
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
|
||||||
`when`(controlsController.getPreferredStructure()).thenReturn(
|
`when`(controlsController.getPreferredSelection()).thenReturn(
|
||||||
StructureInfo(
|
SelectedItem.StructureItem(StructureInfo(
|
||||||
ComponentName("pkg", "cls"),
|
ComponentName("pkg", "cls"),
|
||||||
"structure",
|
"structure",
|
||||||
listOf(ControlInfo("id", "title", "subtitle", 1))
|
listOf(ControlInfo("id", "title", "subtitle", 1))
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
|
||||||
|
|||||||
Reference in New Issue
Block a user