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 package com.android.virtualization.terminal 17 18 import android.content.Context 19 import android.content.SharedPreferences 20 import com.android.internal.annotations.GuardedBy 21 import com.android.virtualization.terminal.proto.ActivePort 22 import java.util.HashSet 23 24 /** 25 * PortsStateManager is responsible for communicating with shared preferences and managing state of 26 * ports. 27 */ 28 class PortsStateManager private constructor(private val sharedPref: SharedPreferences) { 29 private val lock = Any() 30 31 @GuardedBy("lock") private val activePorts: MutableMap<Int, ActivePort> = hashMapOf() 32 33 @GuardedBy("lock") 34 private val enabledPorts: MutableSet<Int> = 35 sharedPref 36 .getAll() 37 .entries 38 .filterIsInstance<MutableMap.MutableEntry<String, Int>>() <lambda>null39 .filter { it.value and FLAG_ENABLED == FLAG_ENABLED } <lambda>null40 .map { it.key.toIntOrNull() } 41 .filterNotNull() 42 .toMutableSet() 43 44 @GuardedBy("lock") private val listeners: MutableSet<Listener> = hashSetOf() 45 getActivePortsnull46 fun getActivePorts(): Set<Int> { 47 synchronized(lock) { 48 return HashSet<Int>(activePorts.keys) 49 } 50 } 51 getActivePortInfonull52 fun getActivePortInfo(port: Int): ActivePort? { 53 synchronized(lock) { 54 return activePorts[port] 55 } 56 } 57 getEnabledPortsnull58 fun getEnabledPorts(): Set<Int> { 59 synchronized(lock) { 60 return HashSet<Int>(enabledPorts) 61 } 62 } 63 updateActivePortsnull64 fun updateActivePorts(ports: List<ActivePort>) { 65 val oldPorts = getActivePorts() 66 synchronized(lock) { 67 activePorts.clear() 68 activePorts.putAll(ports.associateBy { it.port }) 69 } 70 notifyPortsStateUpdated(oldPorts, getActivePorts()) 71 } 72 updateEnabledPortnull73 fun updateEnabledPort(port: Int, enabled: Boolean) { 74 synchronized(lock) { 75 val editor = sharedPref.edit() 76 editor.putInt(port.toString(), if (enabled) FLAG_ENABLED else 0) 77 editor.apply() 78 if (enabled) { 79 enabledPorts.add(port) 80 } else { 81 enabledPorts.remove(port) 82 } 83 } 84 notifyPortsStateUpdated(getActivePorts(), getActivePorts()) 85 } 86 clearEnabledPortsnull87 fun clearEnabledPorts() { 88 synchronized(lock) { 89 val editor = sharedPref.edit() 90 editor.clear() 91 editor.apply() 92 enabledPorts.clear() 93 } 94 notifyPortsStateUpdated(getActivePorts(), getActivePorts()) 95 } 96 registerListenernull97 fun registerListener(listener: Listener) { 98 synchronized(lock) { listeners.add(listener) } 99 } 100 unregisterListenernull101 fun unregisterListener(listener: Listener) { 102 synchronized(lock) { listeners.remove(listener) } 103 } 104 105 // TODO: it notifies when both enabledPort and activePort are changed, but doesn't provide 106 // enabledPort's value change. Make this callback provide that information as well. notifyPortsStateUpdatednull107 private fun notifyPortsStateUpdated(oldActivePorts: Set<Int>, newActivePorts: Set<Int>) { 108 synchronized(lock) { HashSet<Listener>(this@PortsStateManager.listeners) } 109 .forEach { 110 it.onPortsStateUpdated(HashSet<Int>(oldActivePorts), HashSet<Int>(newActivePorts)) 111 } 112 } 113 114 interface Listener { onPortsStateUpdatednull115 fun onPortsStateUpdated(oldActivePorts: Set<Int>, newActivePorts: Set<Int>) {} 116 } 117 118 companion object { 119 private const val PREFS_NAME = ".PORTS" 120 private const val FLAG_ENABLED = 1 121 122 private var instance: PortsStateManager? = null 123 124 @Synchronized getInstancenull125 fun getInstance(context: Context): PortsStateManager { 126 if (instance == null) { 127 val sharedPref = 128 context.getSharedPreferences( 129 context.getPackageName() + PREFS_NAME, 130 Context.MODE_PRIVATE, 131 ) 132 instance = PortsStateManager(sharedPref) 133 } 134 return instance!! 135 } 136 } 137 } 138