1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * Copyright (C) 2024 Mopria Alliance, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bips.ui 19 20 import android.os.Bundle 21 import android.text.TextUtils 22 import android.util.Log 23 import android.view.LayoutInflater 24 import android.view.View 25 import android.view.ViewGroup 26 import android.widget.ImageView 27 import android.widget.ProgressBar 28 import android.widget.TextView 29 import androidx.constraintlayout.widget.ConstraintLayout 30 import androidx.fragment.app.Fragment 31 import androidx.fragment.app.FragmentActivity 32 import androidx.fragment.app.activityViewModels 33 import androidx.recyclerview.widget.LinearLayoutManager 34 import androidx.recyclerview.widget.RecyclerView 35 import com.android.bips.R 36 import com.android.bips.ipp.JobStatus 37 import com.android.bips.jni.BackendConstants 38 import com.android.bips.jni.LocalPrinterCapabilities 39 import com.android.bips.jni.MediaSizes 40 import java.util.* 41 42 /** 43 * Printer information fragment 44 */ 45 class PrinterInformationFragment : Fragment() { 46 47 /** Printer Information view model */ 48 private val printerInformationViewModel: Lazy<PrinterInformationViewModel> = 49 activityViewModels() 50 private val statusMapping = LinkedHashMap(JobStatus.getBlockReasonsMap()) 51 private lateinit var printerName: TextView 52 private lateinit var printerIcon: ImageView 53 private lateinit var printerStatus: TextView 54 private lateinit var printerStatusLayout: ConstraintLayout 55 private lateinit var progressBarPrinterStatus: ProgressBar 56 private lateinit var mediaReady: TextView 57 private lateinit var mediaReadyLabel: TextView 58 private lateinit var inkLevelsRecyclerView: RecyclerView 59 60 override fun onCreateView( 61 inflater: LayoutInflater, 62 container: ViewGroup?, 63 savedInstanceState: Bundle? 64 ): View { 65 return inflater.inflate(R.layout.printer_information, 66 container, false) 67 } 68 69 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 70 printerName = view.findViewById(R.id.printerName) 71 printerIcon = view.findViewById(R.id.printerIcon) 72 printerStatus = view.findViewById(R.id.printerStatus) 73 printerStatusLayout = view.findViewById(R.id.printerStatusLayout) 74 progressBarPrinterStatus = view.findViewById(R.id.progressBarPrinterStatus) 75 mediaReady = view.findViewById(R.id.mediaReady) 76 mediaReadyLabel = view.findViewById(R.id.mediaReadyLabel) 77 inkLevelsRecyclerView = view.findViewById(R.id.inkLevelsRecyclerView) 78 super.onViewCreated(view, savedInstanceState) 79 statusMapping[BackendConstants.PRINTER_STATE_IDLE] = R.string.printer_ready 80 statusMapping[BackendConstants.PRINTER_STATE_RUNNING] = R.string.printer_state__printing 81 statusMapping[BackendConstants.PRINTER_STATE_UNABLE_TO_CONNECT] = 82 R.string.printer_state__offline 83 statusMapping[BackendConstants.PRINTER_STATE_BLOCKED] = 84 R.string.printer_state__check_printer 85 86 activity?.apply { 87 setPrinterImage(this) 88 setPrinterStatus(this) 89 printerInformationViewModel.value.getPrinterCapsLiveData().observe(this) { 90 it?.also { caps -> 91 getIconBitmap(caps) 92 setMediaReadySize(caps) 93 setMarkerView(caps) 94 view.visibility = View.VISIBLE 95 printerName.text = caps.name 96 } ?: run { 97 view.visibility = View.GONE 98 } 99 } 100 } 101 } 102 103 private fun setMediaReadySize(caps: LocalPrinterCapabilities) { 104 var mediaReadyString = "" 105 caps.mediaReadySizes?.also { mediaReadySizes -> 106 if (mediaReadySizes.isEmpty()) { 107 mediaReady.visibility = View.GONE 108 mediaReadyLabel.visibility = View.GONE 109 } 110 for (i in mediaReadySizes) { 111 mediaReadyString += MediaSizes.getInstance(context) 112 .getMediaName(i, context) + "\n" 113 } 114 mediaReady.text = mediaReadyString.dropLast(1) 115 } ?: run { 116 mediaReady.visibility = View.GONE 117 mediaReadyLabel.visibility = View.GONE 118 } 119 } 120 121 private fun getIconBitmap(caps: LocalPrinterCapabilities) { 122 caps.mPrinterIconUris?.also { iconUri -> 123 if (iconUri.isNotEmpty()) { 124 printerInformationViewModel.value.getBitmap(iconUri.last()) 125 } 126 } 127 } 128 129 private fun setPrinterImage(fragmentActivity: FragmentActivity) { 130 printerInformationViewModel.value.getPrinterBitmapLiveData() 131 .observe(fragmentActivity) { printerImage -> 132 if (printerImage != null) { 133 printerIcon.visibility = View.VISIBLE 134 printerIcon.setImageBitmap(printerImage) 135 } else { 136 printerIcon.visibility = View.GONE 137 } 138 } 139 } 140 141 /** 142 * Set Status Of Printer 143 */ 144 private fun setPrinterStatus(fragmentActivity: FragmentActivity) { 145 printerInformationViewModel.value.getPrinterUnavailableLiveData() 146 .observe(fragmentActivity) { 147 if (it) printerStatusLayout.visibility = View.GONE 148 } 149 printerInformationViewModel.value.getPrinterStatusLiveData() 150 .observe(fragmentActivity) { callbackParams -> 151 callbackParams.apply { 152 val reasonsList = blockedReasons?.toList() ?: emptyList() 153 val statusList = getPrinterStatus(printerState, reasonsList) 154 if (statusList.isEmpty()) { 155 printerStatusLayout.visibility = View.GONE 156 } else { 157 if (DEBUG) { 158 Log.e(TAG, "printer status list ${TextUtils.join("\n", statusList)}") 159 } 160 printerStatus.text = TextUtils.join("\n", statusList) 161 printerStatusLayout.visibility = View.VISIBLE 162 printerStatus.visibility = View.VISIBLE 163 progressBarPrinterStatus.visibility = View.GONE 164 } 165 } 166 } 167 } 168 169 /** 170 * Maps the printer state and reasons into a list of status strings 171 * If the printerReasons is not empty (printer is blocked), returns a list of (one or more) 172 * blocked reasons, otherwise it will be a one item list of printer state. May return an empty 173 * list if no resource id is found for the given status(es) 174 */ 175 private fun getPrinterStatus(printerState: String, printerReasons: List<String>): Set<String> { 176 val resourceIds: MutableSet<String> = LinkedHashSet() 177 for (reason in printerReasons) { 178 if (TextUtils.isEmpty(reason) || 179 reason == BackendConstants.BLOCKED_REASON__SPOOL_AREA_FULL && 180 BackendConstants.PRINTER_STATE_BLOCKED != printerState 181 ) { 182 continue 183 } 184 statusMapping[reason]?.also { resourceIds.add(getString(it)) } 185 } 186 if (resourceIds.isEmpty() || BackendConstants.PRINTER_STATE_RUNNING == printerState) { 187 statusMapping[printerState]?.also { resourceIds.add(getString(it)) } 188 } 189 return resourceIds 190 } 191 192 /** 193 * Set marker view 194 * Fills supplies levels views based on capabilities 195 * @param view view 196 * @param caps the selected printer's capabilities 197 */ 198 private fun setMarkerView(caps: LocalPrinterCapabilities) { 199 val mMarkerInfoList = ArrayList<MarkerInfo>() 200 for (i in caps.markerTypes.indices) { 201 if ((validTonerTypes.contains(caps.markerTypes[i]) || 202 validInkTypes.contains(caps.markerTypes[i])) && caps.markerLevel[i] >= 0 203 ) { 204 caps.markerColors[i].split("#").apply { 205 for (j in 1 until size) { 206 mMarkerInfoList.add( 207 MarkerInfo( 208 caps.markerTypes[i], 209 "#" + this[j], 210 caps.markerHighLevel[i], 211 caps.markerLowLevel[i], 212 caps.markerLevel[i] 213 ) 214 ) 215 216 } 217 } 218 } 219 } 220 with(inkLevelsRecyclerView) { 221 this.layoutManager = LinearLayoutManager(activity) 222 this.adapter = MarkerAdapter(mMarkerInfoList) 223 } 224 } 225 226 companion object { 227 private val validTonerTypes = listOf("toner", "toner-cartridge") 228 private val validInkTypes = listOf("ink", "inkCartridge", "ink-cartridge") 229 private const val TAG = "PrinterInformationFragment" 230 private const val DEBUG = false 231 } 232 }