1 /*
<lambda>null2  * Copyright (C) 2018 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 
17 package android.packageinstaller.install.cts
18 
19 import android.app.PendingIntent
20 import android.app.PendingIntent.FLAG_MUTABLE
21 import android.app.PendingIntent.FLAG_UPDATE_CURRENT
22 import android.content.BroadcastReceiver
23 import android.content.Context
24 import android.content.Intent
25 import android.content.Intent.EXTRA_INTENT
26 import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
27 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
28 import android.content.IntentFilter
29 import android.content.pm.PackageInstaller
30 import android.content.pm.PackageInstaller.EXTRA_STATUS
31 import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
32 import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
33 import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
34 import android.content.pm.PackageManager
35 import android.support.test.uiautomator.By
36 import android.support.test.uiautomator.UiDevice
37 import android.support.test.uiautomator.Until
38 import androidx.core.content.FileProvider
39 import androidx.test.InstrumentationRegistry
40 import androidx.test.rule.ActivityTestRule
41 import com.android.compatibility.common.util.FutureResultActivity
42 import org.junit.After
43 import org.junit.Assert
44 import org.junit.Assume.assumeFalse
45 import org.junit.Assume.assumeTrue
46 import org.junit.Before
47 import org.junit.Rule
48 import java.io.File
49 import java.util.concurrent.CompletableFuture
50 import java.util.concurrent.LinkedBlockingQueue
51 import java.util.concurrent.TimeUnit
52 
53 const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
54 const val TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts"
55 const val TEST_APK_EXTERNAL_LOCATION = "/data/local/tmp/cts/packageinstaller"
56 const val INSTALL_ACTION_CB = "PackageInstallerTestBase.install_cb"
57 
58 const val CONTENT_AUTHORITY = "android.packageinstaller.install.cts.fileprovider"
59 
60 const val PACKAGE_INSTALLER_PACKAGE_NAME = "com.android.packageinstaller"
61 const val SYSTEM_PACKAGE_NAME = "android"
62 
63 const val TIMEOUT = 60000L
64 const val APP_OP_STR = "REQUEST_INSTALL_PACKAGES"
65 
66 const val INSTALL_INSTANT_APP = 0x00000800
67 
68 open class PackageInstallerTestBase {
69     @get:Rule
70     val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
71 
72     private val context = InstrumentationRegistry.getTargetContext()
73     private val pm = context.packageManager
74     private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
75     private val apkFile = File(context.filesDir, TEST_APK_NAME)
76 
77     /** If a status was received the value of the status, otherwise null */
78     private var installSessionResult = LinkedBlockingQueue<Int>()
79 
80     private val receiver = object : BroadcastReceiver() {
81         override fun onReceive(context: Context, intent: Intent) {
82             val status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID)
83 
84             if (status == STATUS_PENDING_USER_ACTION) {
85                 val activityIntent = intent.getParcelableExtra<Intent>(EXTRA_INTENT)
86                 activityIntent!!.addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
87                 installDialogStarter.activity.startActivityForResult(activityIntent)
88             }
89 
90             installSessionResult.offer(status)
91         }
92     }
93 
94     @Before
95     fun copyTestApk() {
96         File(TEST_APK_EXTERNAL_LOCATION, TEST_APK_NAME).copyTo(target = apkFile, overwrite = true)
97     }
98 
99     @Before
100     fun wakeUpScreen() {
101         if (!uiDevice.isScreenOn) {
102             uiDevice.wakeUp()
103         }
104         uiDevice.executeShellCommand("wm dismiss-keyguard")
105     }
106 
107     @Before
108     fun assertTestPackageNotInstalled() {
109         try {
110             context.packageManager.getPackageInfo(TEST_APK_PACKAGE_NAME, 0)
111             Assert.fail("Package should not be installed")
112         } catch (expected: PackageManager.NameNotFoundException) {
113         }
114     }
115 
116     @Before
117     fun registerInstallResultReceiver() {
118         context.registerReceiver(receiver, IntentFilter(INSTALL_ACTION_CB))
119     }
120 
121     @Before
122     fun waitForUIIdle() {
123         uiDevice.waitForIdle()
124     }
125 
126     /**
127      * Wait for session's install result and return it
128      */
129     protected fun getInstallSessionResult(timeout: Long = TIMEOUT): Int? {
130         return installSessionResult.poll(timeout, TimeUnit.MILLISECONDS)
131     }
132 
133     /**
134      * Start an installation via a session
135      */
136     protected fun startInstallationViaSession(): CompletableFuture<Int> {
137         return startInstallationViaSession(0 /* installFlags */)
138     }
139 
140     protected fun startInstallationViaSession(installFlags: Int): CompletableFuture<Int> {
141         val pi = pm.packageInstaller
142 
143         // Create session
144         val sessionParam = PackageInstaller.SessionParams(MODE_FULL_INSTALL)
145         // Handle additional install flags
146         if (installFlags and INSTALL_INSTANT_APP != 0) {
147             sessionParam.setInstallAsInstantApp(true)
148         }
149 
150         val sessionId = pi.createSession(sessionParam)
151         val session = pi.openSession(sessionId)!!
152 
153         // Write data to session
154         apkFile.inputStream().use { fileOnDisk ->
155             session.openWrite(TEST_APK_NAME, 0, -1).use { sessionFile ->
156                 fileOnDisk.copyTo(sessionFile)
157             }
158         }
159 
160         // Commit session
161         val dialog = FutureResultActivity.doAndAwaitStart {
162             val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(INSTALL_ACTION_CB),
163                     FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
164             session.commit(pendingIntent.intentSender)
165         }
166 
167         // The system should have asked us to launch the installer
168         Assert.assertEquals(STATUS_PENDING_USER_ACTION, getInstallSessionResult())
169 
170         return dialog
171     }
172 
173     /**
174      * Start an installation via a session
175      */
176     protected fun startInstallationViaIntent(): CompletableFuture<Int> {
177         val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
178         intent.data = FileProvider.getUriForFile(context, CONTENT_AUTHORITY, apkFile)
179         intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
180         intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
181 
182         return installDialogStarter.activity.startActivityForResult(intent)
183     }
184 
185     fun assertInstalled() {
186         // Throws exception if package is not installed.
187         pm.getPackageInfo(TEST_APK_PACKAGE_NAME, 0)
188     }
189 
190     fun assertNotInstalled() {
191         try {
192             pm.getPackageInfo(TEST_APK_PACKAGE_NAME, 0)
193             Assert.fail("Package should not be installed")
194         } catch (expected: PackageManager.NameNotFoundException) {
195         }
196     }
197 
198     /**
199      * Click a button in the UI of the installer app
200      *
201      * @param resId The resource ID of the button to click
202      */
203     fun clickInstallerUIButton(resId: String) {
204         uiDevice.wait(Until.findObject(By.res(SYSTEM_PACKAGE_NAME, resId)), TIMEOUT)
205                 .click()
206     }
207 
208     /**
209      * Sets the given secure setting to the provided value.
210      */
211     fun setSecureSetting(secureSetting: String, value: Int) {
212         uiDevice.executeShellCommand("settings put secure $secureSetting $value")
213     }
214 
215     fun setSecureFrp(secureFrp: Boolean) {
216         uiDevice.executeShellCommand("settings --user 0 " +
217                 "put secure secure_frp_mode ${if (secureFrp) 1 else 0}")
218     }
219 
220     @After
221     fun unregisterInstallResultReceiver() {
222         try {
223             context.unregisterReceiver(receiver)
224         } catch (ignored: IllegalArgumentException) {
225         }
226     }
227 
228     @After
229     fun uninstallTestPackage() {
230         uiDevice.executeShellCommand("pm uninstall $TEST_APK_PACKAGE_NAME")
231     }
232 
233     fun assumeWatch() {
234         assumeTrue("Test only valid for watch", hasFeatureWatch())
235     }
236 
237     fun assumeNotWatch() {
238         assumeFalse("Installing APKs not supported on watch", hasFeatureWatch())
239     }
240 
241     private fun hasFeatureWatch(): Boolean {
242         return pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
243     }
244 }
245