blob: 7311878cf6253b007a66c907fe60f9107fca3734 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os.cts
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
import android.app.Instrumentation
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.platform.test.annotations.AppModeFull
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
import android.provider.Settings
import android.support.test.uiautomator.By
import android.support.test.uiautomator.BySelector
import android.support.test.uiautomator.UiDevice
import android.support.test.uiautomator.UiObject2
import android.support.test.uiautomator.UiScrollable
import android.support.test.uiautomator.UiSelector
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SdkSuppress
import androidx.test.runner.AndroidJUnit4
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
import com.android.compatibility.common.util.SystemUtil
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.compatibility.common.util.UiAutomatorUtils
import org.hamcrest.CoreMatchers
import org.hamcrest.Matchers
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertThat
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
* Integration test for app hibernation.
*/
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
class AppHibernationIntegrationTest {
companion object {
const val LOG_TAG = "AppHibernationIntegrationTest"
const val WAIT_TIME_MS = 1000L
const val MAX_SCROLL_ATTEMPTS = 3
const val TEST_UNUSED_THRESHOLD = 1L
const val CMD_KILL = "am kill %s"
}
private val context: Context = InstrumentationRegistry.getTargetContext()
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private lateinit var packageManager: PackageManager
@get:Rule
val disableAnimationRule = DisableAnimationRule()
@get:Rule
val freezeRotationRule = FreezeRotationRule()
@Before
fun setup() {
packageManager = context.packageManager
// Collapse notifications
assertThat(
runShellCommandOrThrow("cmd statusbar collapse"),
CoreMatchers.equalTo(""))
// Wake up the device
runShellCommandOrThrow("input keyevent KEYCODE_WAKEUP")
runShellCommandOrThrow("input keyevent 82")
}
@After
fun cleanUp() {
goHome()
}
@Test
fun testUnusedApp_getsForceStopped() {
withDeviceConfig(NAMESPACE_APP_HIBERNATION, "app_hibernation_enabled", "true") {
withUnusedThresholdMs(TEST_UNUSED_THRESHOLD) {
withApp(APK_PATH_S_APP, APK_PACKAGE_NAME_S_APP) {
// Use app
startApp(APK_PACKAGE_NAME_S_APP)
leaveApp(APK_PACKAGE_NAME_S_APP)
killApp(APK_PACKAGE_NAME_S_APP)
// Wait for the unused threshold time to pass
Thread.sleep(TEST_UNUSED_THRESHOLD)
// Run job
runAppHibernationJob(context, LOG_TAG)
// Verify
val ai =
packageManager.getApplicationInfo(APK_PACKAGE_NAME_S_APP, 0 /* flags */)
val stopped = ((ai.flags and ApplicationInfo.FLAG_STOPPED) != 0)
assertTrue(stopped)
if (hasFeatureTV()) {
// Skip checking unused apps screen because it may be unavailable on TV
return
}
openUnusedAppsNotification()
waitFindObject(By.text(APK_PACKAGE_NAME_S_APP))
}
}
}
}
@Test
fun testPreSVersionUnusedApp_doesntGetForceStopped() {
assumeFalse(
"TV may have different behaviour for Pre-S version apps",
hasFeatureTV())
withUnusedThresholdMs(TEST_UNUSED_THRESHOLD) {
withApp(APK_PATH_R_APP, APK_PACKAGE_NAME_R_APP) {
// Use app
startApp(APK_PACKAGE_NAME_R_APP)
leaveApp(APK_PACKAGE_NAME_R_APP)
killApp(APK_PACKAGE_NAME_R_APP)
// Wait for the unused threshold time to pass
Thread.sleep(TEST_UNUSED_THRESHOLD)
// Run job
runAppHibernationJob(context, LOG_TAG)
// Verify
val ai =
packageManager.getApplicationInfo(APK_PACKAGE_NAME_R_APP, 0 /* flags */)
val stopped = ((ai.flags and ApplicationInfo.FLAG_STOPPED) != 0)
assertFalse(stopped)
}
}
}
@AppModeFull(reason = "Uses application details settings")
@Test
fun testAppInfo_RemovePermissionsAndFreeUpSpaceToggleExists() {
assumeFalse(
"Remove permissions and free up space toggle may be unavailable on TV",
hasFeatureTV())
withDeviceConfig(NAMESPACE_APP_HIBERNATION, "app_hibernation_enabled", "true") {
withApp(APK_PATH_S_APP, APK_PACKAGE_NAME_S_APP) {
// Open app info
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", APK_PACKAGE_NAME_S_APP, null /* fragment */)
intent.data = uri
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
waitForIdle()
UiAutomatorUtils.getUiDevice()
val packageManager = context.packageManager
val settingsPackage = intent.resolveActivity(packageManager).packageName
val res = packageManager.getResourcesForApplication(settingsPackage)
val title = res.getString(
res.getIdentifier("unused_apps_switch", "string", settingsPackage))
// Settings can have multiple scrollable containers so all of them should be
// searched.
var toggleFound = UiDevice.getInstance(instrumentation)
.findObject(UiSelector().text(title))
.waitForExists(WAIT_TIME_MS)
var i = 0
var scrollableObject = UiScrollable(UiSelector().scrollable(true).instance(i))
while (!toggleFound && scrollableObject.waitForExists(WAIT_TIME_MS)) {
toggleFound = scrollableObject.scrollTextIntoView(title)
scrollableObject = UiScrollable(UiSelector().scrollable(true).instance(++i))
}
assertTrue("Remove permissions and free up space toggle not found", toggleFound)
}
}
}
private fun leaveApp(packageName: String) {
eventually {
goHome()
SystemUtil.runWithShellPermissionIdentity {
val packageImportance = context
.getSystemService(ActivityManager::class.java)!!
.getPackageImportance(packageName)
assertThat(packageImportance, Matchers.greaterThan(IMPORTANCE_TOP_SLEEPING))
}
}
}
private fun killApp(packageName: String) {
eventually {
SystemUtil.runWithShellPermissionIdentity {
runShellCommandOrThrow(String.format(CMD_KILL, packageName))
val packageImportance = context
.getSystemService(ActivityManager::class.java)!!
.getPackageImportance(packageName)
assertThat(packageImportance, Matchers.equalTo(IMPORTANCE_GONE))
}
}
}
private fun waitFindObject(selector: BySelector): UiObject2 {
return waitFindObject(instrumentation.uiAutomation, selector)
}
}