1 /* 2 * Copyright (C) 2025 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.os.Handler 20 import android.os.Looper 21 import android.system.virtualmachine.VirtualMachine 22 import android.util.Log 23 import androidx.lifecycle.DefaultLifecycleObserver 24 import androidx.lifecycle.LifecycleOwner 25 import androidx.lifecycle.ProcessLifecycleOwner 26 import com.android.virtualization.terminal.MainActivity.Companion.TAG 27 import java.util.concurrent.Executors 28 import java.util.concurrent.ScheduledFuture 29 import java.util.concurrent.TimeUnit 30 31 /** 32 * MemBalloonController is responsible for adjusting the memory ballon size of a VM depending on 33 * whether the app is visible or running in the background 34 */ 35 class MemBalloonController(val context: Context, val vm: VirtualMachine) { 36 companion object { 37 private const val INITIAL_PERCENT = 10 38 private const val MAX_PERCENT = 50 39 private const val INFLATION_STEP_PERCENT = 5 40 private const val INFLATION_PERIOD_SEC = 60L 41 42 private val mainHandler = Handler(Looper.getMainLooper()) 43 runOnMainThreadnull44 private fun runOnMainThread(runnable: Runnable) { 45 mainHandler.post(runnable) 46 } 47 } 48 49 private val executor = 50 Executors.newSingleThreadScheduledExecutor( 51 TerminalThreadFactory(context.getApplicationContext()) 52 ) 53 54 private val observer = 55 object : DefaultLifecycleObserver { 56 57 // If the app is started or resumed, give deflate the balloon to 0 to give maximum 58 // available memory to the virtual machine onResumenull59 override fun onResume(owner: LifecycleOwner) { 60 ongoingInflation?.cancel(false) 61 executor.execute({ 62 Log.v(TAG, "app resumed. deflating mem balloon to the minimum") 63 vm.setMemoryBalloonByPercent(0) 64 }) 65 } 66 67 // If the app goes into background, progressively inflate the balloon from 68 // INITIAL_PERCENT until it reaches MAX_PERCENT onStopnull69 override fun onStop(owner: LifecycleOwner) { 70 ongoingInflation?.cancel(false) 71 balloonPercent = INITIAL_PERCENT 72 ongoingInflation = 73 executor.scheduleAtFixedRate( 74 { 75 if (balloonPercent <= MAX_PERCENT) { 76 Log.v(TAG, "inflating mem balloon to ${balloonPercent} %") 77 vm.setMemoryBalloonByPercent(balloonPercent) 78 balloonPercent += INFLATION_STEP_PERCENT 79 } else { 80 Log.v(TAG, "mem balloon is inflated to its max (${MAX_PERCENT} %)") 81 ongoingInflation!!.cancel(false) 82 } 83 }, 84 0 /* initialDelay */, 85 INFLATION_PERIOD_SEC, 86 TimeUnit.SECONDS, 87 ) 88 } 89 } 90 91 private var balloonPercent = 0 92 private var ongoingInflation: ScheduledFuture<*>? = null 93 startnull94 fun start() { 95 // addObserver is @MainThread 96 runOnMainThread({ ProcessLifecycleOwner.get().lifecycle.addObserver(observer) }) 97 } 98 stopnull99 fun stop() { 100 // removeObserver is @MainThread 101 runOnMainThread({ 102 ProcessLifecycleOwner.get().lifecycle.removeObserver(observer) 103 executor.shutdown() 104 }) 105 } 106 } 107