1 /*
2  * Copyright (C) 2019 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.controls.management
18 
19 import android.content.ComponentName
20 import android.content.res.Resources
21 import android.view.LayoutInflater
22 import android.view.View
23 import android.view.ViewGroup
24 import android.widget.ImageView
25 import android.widget.TextView
26 import androidx.lifecycle.Lifecycle
27 import androidx.lifecycle.LifecycleOwner
28 import androidx.recyclerview.widget.RecyclerView
29 import com.android.systemui.R
30 import com.android.systemui.controls.ControlsServiceInfo
31 import java.text.Collator
32 import java.util.concurrent.Executor
33 
34 /**
35  * Adapter for binding [ControlsServiceInfo] related to [ControlsProviderService].
36  *
37  * This class handles subscribing and keeping track of the list of valid applications for
38  * displaying.
39  *
40  * @param uiExecutor an executor on the view thread of the containing [RecyclerView]
41  * @param lifecycle the lifecycle of the containing [LifecycleOwner] to control listening status
42  * @param controlsListingController the controller to keep track of valid applications
43  * @param layoutInflater an inflater for the views in the containing [RecyclerView]
44  * @param onAppSelected a callback to indicate that an app has been selected in the list.
45  */
46 class AppAdapter(
47     backgroundExecutor: Executor,
48     uiExecutor: Executor,
49     lifecycle: Lifecycle,
50     controlsListingController: ControlsListingController,
51     private val layoutInflater: LayoutInflater,
52     private val onAppSelected: (ComponentName?) -> Unit = {},
53     private val favoritesRenderer: FavoritesRenderer,
54     private val resources: Resources
55 ) : RecyclerView.Adapter<AppAdapter.Holder>() {
56 
57     private var listOfServices = emptyList<ControlsServiceInfo>()
58 
59     private val callback = object : ControlsListingController.ControlsListingCallback {
60         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
61             backgroundExecutor.execute {
62                 val collator = Collator.getInstance(resources.configuration.locales[0])
63                 val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
64                     it.loadLabel()
65                 }
66                 listOfServices = serviceInfos.sortedWith(localeComparator)
67                 uiExecutor.execute(::notifyDataSetChanged)
68             }
69         }
70     }
71 
72     init {
73         controlsListingController.observe(lifecycle, callback)
74     }
75 
76     override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
77         return Holder(layoutInflater.inflate(R.layout.controls_app_item, parent, false),
78                 favoritesRenderer)
79     }
80 
81     override fun getItemCount() = listOfServices.size
82 
83     override fun onBindViewHolder(holder: Holder, index: Int) {
84         holder.bindData(listOfServices[index])
85         holder.itemView.setOnClickListener {
86             onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
87         }
88     }
89 
90     /**
91      * Holder for binding views in the [RecyclerView]-
92      */
93     class Holder(view: View, val favRenderer: FavoritesRenderer) : RecyclerView.ViewHolder(view) {
94         private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
95         private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
96         private val favorites: TextView = itemView.requireViewById(R.id.favorites)
97 
98         /**
99          * Bind data to the view
100          * @param data Information about the [ControlsProviderService] to bind to the data
101          */
102         fun bindData(data: ControlsServiceInfo) {
103             icon.setImageDrawable(data.loadIcon())
104             title.text = data.loadLabel()
105             val text = favRenderer.renderFavoritesForComponent(data.componentName)
106             favorites.text = text
107             favorites.visibility = if (text == null) View.GONE else View.VISIBLE
108         }
109     }
110 }
111 
112 class FavoritesRenderer(
113     private val resources: Resources,
114     private val favoriteFunction: (ComponentName) -> Int
115 ) {
116 
117     fun renderFavoritesForComponent(component: ComponentName): String? {
118         val qty = favoriteFunction(component)
119         if (qty != 0) {
120             return resources.getQuantityString(R.plurals.controls_number_of_favorites, qty, qty)
121         } else {
122             return null
123         }
124     }
125 }
126