• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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