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.documentsui 17 18 import android.app.Activity 19 import android.app.UiAutomation 20 import android.content.ContentResolver 21 import android.content.Context 22 import android.content.Intent 23 import android.os.Bundle 24 import android.os.RemoteException 25 import android.provider.DocumentsContract 26 import android.view.KeyEvent 27 import android.view.MotionEvent 28 import androidx.test.core.app.ActivityScenario 29 import androidx.test.platform.app.InstrumentationRegistry 30 import androidx.test.uiautomator.Configurator 31 import androidx.test.uiautomator.UiDevice 32 import com.android.documentsui.base.Features 33 import com.android.documentsui.base.Features.RuntimeFeatures 34 import com.android.documentsui.base.RootInfo 35 import com.android.documentsui.base.UserId 36 import com.android.documentsui.bots.Bots 37 import com.android.documentsui.files.FilesActivity 38 import java.io.IOException 39 import java.util.Objects 40 41 /** 42 * Provides basic test environment for UI tests: 43 * - Launches activity 44 * - Creates and gives access to test root directories and test files 45 * - Cleans up the test environment 46 */ 47 abstract class ActivityTestJunit4<T : Activity?> { 48 @JvmField 49 var bots: Bots? = null 50 51 @JvmField 52 var device: UiDevice? = null 53 54 @JvmField 55 var context: Context? = null 56 var userId: UserId? = null 57 var automation: UiAutomation? = null 58 59 @JvmField 60 var features: Features? = null 61 62 /** 63 * Returns the root that will be opened within the activity. 64 * By default tests are started with one of the test roots. 65 * Override the method if you want to open different root on start. 66 * @return Root that will be opened. Return null if you want to open activity's default root. 67 */ 68 protected open var initialRoot: RootInfo? = null 69 70 @JvmField 71 var rootDir0: RootInfo? = null 72 73 @JvmField 74 var rootDir1: RootInfo? = null 75 protected var mResolver: ContentResolver? = null 76 77 @JvmField 78 protected var mDocsHelper: DocumentsProviderHelper? = null 79 protected var mActivityScenario: ActivityScenario<T?>? = null 80 private var initialScreenOffTimeoutValue: String? = null 81 private var initialSleepTimeoutValue: String? = null 82 83 protected val testingProviderAuthority: String 84 /** 85 * Returns the authority of the testing provider begin used. 86 * By default it's StubProvider's authority. 87 * @return Authority of the provider. 88 */ 89 get() = StubProvider.DEFAULT_AUTHORITY 90 91 /** 92 * Resolves testing roots. 93 */ 94 @Throws(RemoteException::class) setupTestingRootsnull95 protected fun setupTestingRoots() { 96 rootDir0 = mDocsHelper!!.getRoot(StubProvider.ROOT_0_ID) 97 rootDir1 = mDocsHelper!!.getRoot(StubProvider.ROOT_1_ID) 98 this.initialRoot = rootDir0 99 } 100 101 @Throws(Exception::class) setUpnull102 open fun setUp() { 103 device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 104 // NOTE: Must be the "target" context, else security checks in content provider will fail. 105 context = InstrumentationRegistry.getInstrumentation().getTargetContext() 106 userId = UserId.DEFAULT_USER 107 automation = InstrumentationRegistry.getInstrumentation().getUiAutomation() 108 features = RuntimeFeatures(context!!.getResources(), null) 109 110 bots = Bots(device, automation, context, TIMEOUT) 111 112 Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE) 113 114 mResolver = context!!.getContentResolver() 115 mDocsHelper = DocumentsProviderHelper( 116 userId, this.testingProviderAuthority, context, 117 this.testingProviderAuthority 118 ) 119 120 device!!.setOrientationNatural() 121 device!!.pressKeyCode(KeyEvent.KEYCODE_WAKEUP) 122 123 disableScreenOffAndSleepTimeouts() 124 125 setupTestingRoots() 126 127 launchActivity() 128 resetStorage() 129 130 // Since at the launch of activity, ROOT_0 and ROOT_1 have no files, drawer will 131 // automatically open for phone devices. Espresso register click() as (x, y) MotionEvents, 132 // so if a drawer is on top of a file we want to select, it will actually click the drawer. 133 // Thus to start a clean state, we always try to close first. 134 bots!!.roots!!.closeDrawer() 135 136 // Configure the provider back to default. 137 mDocsHelper!!.configure(null, Bundle.EMPTY) 138 } 139 140 @Throws(Exception::class) tearDownnull141 open fun tearDown() { 142 device!!.unfreezeRotation() 143 mDocsHelper!!.cleanUp() 144 restoreScreenOffAndSleepTimeouts() 145 mActivityScenario!!.close() 146 } 147 launchActivitynull148 protected fun launchActivity() { 149 val intent = Intent(context, FilesActivity::class.java) 150 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) 151 if (this.initialRoot != null) { 152 intent.setAction(Intent.ACTION_VIEW) 153 intent.setDataAndType( 154 this.initialRoot!!.uri, 155 DocumentsContract.Root.MIME_TYPE_ITEM 156 ) 157 } 158 mActivityScenario = ActivityScenario.launch(intent) 159 } 160 161 @Throws(RemoteException::class) resetStoragenull162 protected fun resetStorage() { 163 mDocsHelper!!.clear(null, null) 164 device!!.waitForIdle() 165 } 166 167 @Throws(RemoteException::class) initTestFilesnull168 protected open fun initTestFiles() { 169 mDocsHelper!!.createFolder(this.initialRoot, dirName1) 170 mDocsHelper!!.createDocument(this.initialRoot, "text/plain", fileName1) 171 mDocsHelper!!.createDocument(this.initialRoot, "image/png", fileName2) 172 mDocsHelper!!.createDocumentWithFlags( 173 initialRoot!!.documentId, 174 "text/plain", 175 fileNameNoRename, 176 DocumentsContract.Document.FLAG_SUPPORTS_WRITE 177 ) 178 179 mDocsHelper!!.createDocument(rootDir1, "text/plain", fileName3) 180 mDocsHelper!!.createDocument(rootDir1, "text/plain", fileName4) 181 } 182 183 @Throws(IOException::class) disableScreenOffAndSleepTimeoutsnull184 private fun disableScreenOffAndSleepTimeouts() { 185 initialScreenOffTimeoutValue = device!!.executeShellCommand( 186 "settings get system screen_off_timeout" 187 ) 188 initialSleepTimeoutValue = device!!.executeShellCommand( 189 "settings get secure sleep_timeout" 190 ) 191 device!!.executeShellCommand("settings put system screen_off_timeout -1") 192 device!!.executeShellCommand("settings put secure sleep_timeout -1") 193 } 194 195 @Throws(IOException::class) restoreScreenOffAndSleepTimeoutsnull196 private fun restoreScreenOffAndSleepTimeouts() { 197 Objects.requireNonNull<String?>(initialScreenOffTimeoutValue) 198 Objects.requireNonNull<String?>(initialSleepTimeoutValue) 199 try { 200 device!!.executeShellCommand( 201 "settings put system screen_off_timeout $initialScreenOffTimeoutValue" 202 ) 203 device!!.executeShellCommand( 204 "settings put secure sleep_timeout $initialSleepTimeoutValue" 205 ) 206 } finally { 207 initialScreenOffTimeoutValue = null 208 initialSleepTimeoutValue = null 209 } 210 } 211 212 companion object { 213 // Testing files. For custom ones, override initTestFiles(). 214 const val dirName1 = "Dir1" 215 const val childDir1 = "ChildDir1" 216 const val fileName1 = "file1.log" 217 const val fileName2 = "file12.png" 218 const val fileName3 = "anotherFile0.log" 219 const val fileName4 = "poodles.text" 220 const val fileNameNoRename = "NO_RENAMEfile.txt" 221 const val TIMEOUT = 5000 222 } 223 } 224