1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.platform.systemui_tapl.controller 18 19 import android.content.BroadcastReceiver 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.platform.uiautomatorhelpers.DeviceHelpers.context 24 import android.platform.uiautomatorhelpers.DeviceHelpers.shell 25 import android.platform.uiautomatorhelpers.WaitUtils.ensureThat 26 import android.util.Log 27 import java.time.Duration 28 import org.junit.rules.ExternalResource 29 30 /** Controller for manipulating dock status of a tablet. */ 31 class DockController : ExternalResource() { 32 private var lastDockState = UNKNOWN_DOCK_STATE 33 34 private val dockChangedReceiver = 35 object : BroadcastReceiver() { onReceivenull36 override fun onReceive(context: Context, intent: Intent) { 37 val newDockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, UNKNOWN_DOCK_STATE) 38 Log.i( 39 TAG, 40 "ACTION_DOCK_EVENT received, " + 41 "lastDockState: $lastDockState (${DOCK_STATE_NAMES[lastDockState]}), " + 42 "newDockState: $newDockState (${DOCK_STATE_NAMES[newDockState]})", 43 ) 44 lastDockState = newDockState 45 } 46 } 47 beforenull48 override fun before() { 49 registerReceiver() 50 } 51 afternull52 override fun after() { 53 unregisterReceiver() 54 } 55 enterDockStatenull56 fun enterDockState() { 57 if (DEBUG) Log.i(TAG, "enterDockState") 58 // Fake Korlan Dock. See go/dock-manager-guide#fake-korlan-dock 59 shell("dumpsys DockObserver set state ${Intent.EXTRA_DOCK_STATE_HE_DESK}") 60 context.sendBroadcast( 61 Intent("ACTION_DEBUG_DOCK") 62 .putExtra("EXTRA_DEBUG_DOCK_PRODUCT", "korlan") 63 .putExtra("EXTRA_DEBUG_DOCK_VERSION", "9.99.999999") 64 .putExtra("EXTRA_DEBUG_DOCK_HGS_ID", "fake-hgs-id") 65 .setClassName(context, DOCK_MANAGER_DEBUG_CONTROLLER_BROADCAST_RECEIVER) 66 ) 67 waitUntilDocked() 68 } 69 exitDockStatenull70 fun exitDockState() { 71 if (DEBUG) Log.i(TAG, "exitDockState") 72 shell("dumpsys DockObserver set state ${Intent.EXTRA_DOCK_STATE_UNDOCKED}") 73 context.sendBroadcast( 74 Intent("ACTION_DEBUG_UNDOCK") 75 .setClassName(context, DOCK_MANAGER_DEBUG_CONTROLLER_BROADCAST_RECEIVER) 76 ) 77 waitUntilUndocked() 78 } 79 resetDockStatenull80 fun resetDockState() { 81 if (DEBUG) Log.i(TAG, "resetDockState") 82 shell("dumpsys DockObserver reset") 83 context.sendBroadcast( 84 Intent("ACTION_DEBUG_UNDOCK") 85 .setClassName(context, DOCK_MANAGER_DEBUG_CONTROLLER_BROADCAST_RECEIVER) 86 ) 87 waitUntilUndocked() 88 } 89 isDockednull90 private fun isDocked(): Boolean { 91 val docked = lastDockState in DOCKED_STATES 92 if (DEBUG) Log.i(TAG, "isDocked: $docked") 93 return docked 94 } 95 waitUntilDockednull96 fun waitUntilDocked() { 97 if (DEBUG) Log.i(TAG, "waitUntilDocked") 98 ensureThat("device is docked", DEFAULT_DEADLINE) { isDocked() } 99 } 100 waitUntilUndockednull101 fun waitUntilUndocked() { 102 if (DEBUG) Log.i(TAG, "waitUntilUndocked") 103 ensureThat("device is undocked", DEFAULT_DEADLINE) { !isDocked() } 104 } 105 registerReceivernull106 private fun registerReceiver() { 107 val intentFilter = IntentFilter(Intent.ACTION_DOCK_EVENT) 108 if (DEBUG) Log.i(TAG, "Registered event receiver") 109 context.registerReceiver(dockChangedReceiver, intentFilter) 110 } 111 unregisterReceivernull112 private fun unregisterReceiver() { 113 if (DEBUG) Log.i(TAG, "Unregistered event receiver") 114 context.unregisterReceiver(dockChangedReceiver) 115 } 116 117 companion object { 118 private val TAG = DockController::class.java.simpleName 119 private const val DOCK_MANAGER_DEBUG_CONTROLLER_BROADCAST_RECEIVER = 120 "com.google.android.apps.nest.dockmanager.app/.service.DebugControllerBroadcastReceiver" 121 private const val UNKNOWN_DOCK_STATE = -1 122 private const val DEBUG = false 123 private val DOCK_STATE_NAMES = 124 mapOf( 125 UNKNOWN_DOCK_STATE to "UNKNOWN_DOCK_STATE", 126 Intent.EXTRA_DOCK_STATE_CAR to "EXTRA_DOCK_STATE_CAR", 127 Intent.EXTRA_DOCK_STATE_DESK to "EXTRA_DOCK_STATE_DESK", 128 Intent.EXTRA_DOCK_STATE_HE_DESK to "EXTRA_DOCK_STATE_HE_DESK", 129 Intent.EXTRA_DOCK_STATE_LE_DESK to "EXTRA_DOCK_STATE_LE_DESK", 130 Intent.EXTRA_DOCK_STATE_UNDOCKED to "EXTRA_DOCK_STATE_UNDOCKED", 131 ) 132 private val DOCKED_STATES = 133 arrayOf( 134 Intent.EXTRA_DOCK_STATE_HE_DESK, 135 Intent.EXTRA_DOCK_STATE_LE_DESK, 136 Intent.EXTRA_DOCK_STATE_DESK, 137 ) 138 private val DEFAULT_DEADLINE = Duration.ofSeconds(20) 139 } 140 } 141