1 /*
2  * Copyright (C) 2021 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.media.taptotransfer
18 
19 import android.annotation.SuppressLint
20 import android.app.StatusBarManager
21 import android.content.Context
22 import android.media.MediaRoute2Info
23 import android.util.Log
24 import androidx.annotation.VisibleForTesting
25 import com.android.systemui.CoreStartable
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Main
28 import com.android.systemui.media.taptotransfer.receiver.ChipStateReceiver
29 import com.android.systemui.media.taptotransfer.sender.ChipStateSender
30 import com.android.systemui.statusbar.commandline.Command
31 import com.android.systemui.statusbar.commandline.CommandRegistry
32 import java.io.PrintWriter
33 import java.lang.IllegalArgumentException
34 import java.util.concurrent.Executor
35 import javax.inject.Inject
36 
37 /**
38  * A helper class to test the media tap-to-transfer chip via the command line. See inner classes for
39  * command usages.
40  */
41 @SysUISingleton
42 class MediaTttCommandLineHelper @Inject constructor(
43     private val commandRegistry: CommandRegistry,
44     private val context: Context,
45     @Main private val mainExecutor: Executor
46 ) : CoreStartable {
47 
48     /** All commands for the sender device. */
49     inner class SenderCommand : Command {
executenull50         override fun execute(pw: PrintWriter, args: List<String>) {
51             if (args.size < 2) {
52                 help(pw)
53                 return
54             }
55 
56             val senderArgs = processArgs(args)
57 
58             @StatusBarManager.MediaTransferSenderState
59             val displayState: Int?
60             try {
61                 displayState = ChipStateSender.getSenderStateIdFromName(senderArgs.commandName)
62             } catch (ex: IllegalArgumentException) {
63                 pw.println("Invalid command name ${senderArgs.commandName}")
64                 return
65             }
66 
67             @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
68             val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
69                     as StatusBarManager
70             val routeInfo = MediaRoute2Info.Builder(senderArgs.id, senderArgs.deviceName)
71                     .addFeature("feature")
72             if (senderArgs.useAppIcon) {
73                 routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
74             }
75 
76             var undoExecutor: Executor? = null
77             var undoRunnable: Runnable? = null
78             if (isSucceededState(displayState) && senderArgs.showUndo) {
79                 undoExecutor = mainExecutor
80                 undoRunnable = Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
81             }
82 
83             statusBarManager.updateMediaTapToTransferSenderDisplay(
84                 displayState,
85                 routeInfo.build(),
86                 undoExecutor,
87                 undoRunnable,
88             )
89         }
90 
processArgsnull91         private fun processArgs(args: List<String>): SenderArgs {
92             val senderArgs = SenderArgs(
93                 deviceName = args[0],
94                 commandName = args[1],
95             )
96 
97             if (args.size == 2) {
98                 return senderArgs
99             }
100 
101             // Process any optional arguments
102             args.subList(2, args.size).forEach {
103                 when {
104                     it == "useAppIcon=false" -> senderArgs.useAppIcon = false
105                     it == "showUndo=false" -> senderArgs.showUndo = false
106                     it.substring(0, 3) == "id=" -> senderArgs.id = it.substring(3)
107                 }
108             }
109 
110             return senderArgs
111         }
112 
isSucceededStatenull113         private fun isSucceededState(
114             @StatusBarManager.MediaTransferSenderState displayState: Int
115         ): Boolean {
116             return displayState ==
117                     StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED ||
118                     displayState ==
119                     StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED
120         }
121 
helpnull122         override fun help(pw: PrintWriter) {
123             pw.println(
124                 "Usage: adb shell cmd statusbar $SENDER_COMMAND " +
125                 "<deviceName> <chipState> " +
126                 "useAppIcon=[true|false] id=<id> showUndo=[true|false]"
127             )
128             pw.println("Note: useAppIcon, id, and showUndo are optional additional commands.")
129         }
130     }
131 
132     private data class SenderArgs(
133         val deviceName: String,
134         val commandName: String,
135         var id: String = "id",
136         var useAppIcon: Boolean = true,
137         var showUndo: Boolean = true,
138     )
139 
140     /** All commands for the receiver device. */
141     inner class ReceiverCommand : Command {
executenull142         override fun execute(pw: PrintWriter, args: List<String>) {
143             if (args.isEmpty()) {
144                 help(pw)
145                 return
146             }
147 
148             val commandName = args[0]
149             @StatusBarManager.MediaTransferReceiverState
150             val displayState: Int?
151             try {
152                 displayState = ChipStateReceiver.getReceiverStateIdFromName(commandName)
153             } catch (ex: IllegalArgumentException) {
154                 pw.println("Invalid command name $commandName")
155                 return
156             }
157 
158             @SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE
159             val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
160                     as StatusBarManager
161             val routeInfo = MediaRoute2Info.Builder(
162                 if (args.size >= 3) args[2] else "id",
163                 "Test Name"
164             ).addFeature("feature")
165             val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
166             if (useAppIcon) {
167                 routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
168             }
169 
170             statusBarManager.updateMediaTapToTransferReceiverDisplay(
171                     displayState,
172                     routeInfo.build(),
173                     null,
174                     null
175                 )
176         }
177 
helpnull178         override fun help(pw: PrintWriter) {
179             pw.println("Usage: adb shell cmd statusbar $RECEIVER_COMMAND " +
180                     "<chipState> useAppIcon=[true|false] <id>")
181         }
182     }
183 
startnull184     override fun start() {
185         commandRegistry.registerCommand(SENDER_COMMAND) { SenderCommand() }
186         commandRegistry.registerCommand(RECEIVER_COMMAND) { ReceiverCommand() }
187     }
188 }
189 
190 @VisibleForTesting
191 const val SENDER_COMMAND = "media-ttt-chip-sender"
192 @VisibleForTesting
193 const val RECEIVER_COMMAND = "media-ttt-chip-receiver"
194 private const val CLI_TAG = "MediaTransferCli"
195 private const val TEST_PACKAGE_NAME = "com.android.systemui"
196