• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.PackageInfo
30 import android.content.pm.PackageInstaller
31 import android.content.pm.PackageInstaller.EXTRA_PRE_APPROVAL
32 import android.content.pm.PackageInstaller.EXTRA_STATUS
33 import android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE
34 import android.content.pm.PackageInstaller.PreapprovalDetails
35 import android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID
36 import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
37 import android.content.pm.PackageInstaller.Session
38 import android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
39 import android.content.pm.PackageManager
40 import android.provider.DeviceConfig
41 import android.support.test.uiautomator.By
42 import android.support.test.uiautomator.BySelector
43 import android.support.test.uiautomator.UiDevice
44 import android.support.test.uiautomator.Until
45 import android.util.Log
46 import androidx.core.content.FileProvider
47 import androidx.test.InstrumentationRegistry
48 import androidx.test.rule.ActivityTestRule
49 import com.android.compatibility.common.util.DisableAnimationRule
50 import com.android.compatibility.common.util.FutureResultActivity
51 import com.android.compatibility.common.util.SystemUtil
52 import java.io.File
53 import java.util.concurrent.CompletableFuture
54 import java.util.concurrent.LinkedBlockingQueue
55 import java.util.concurrent.TimeUnit
56 import org.junit.After
57 import org.junit.Assert
58 import org.junit.Before
59 import org.junit.Rule
60 
61 open class PackageInstallerTestBase {
62 
63     companion object {
64         const val TAG = "PackageInstallerTest"
65 
66         const val INSTALL_BUTTON_ID = "button1"
67         const val CANCEL_BUTTON_ID = "button2"
68 
69         const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
70         const val TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts"
71         const val TEST_APK_LOCATION = "/data/local/tmp/cts/packageinstaller"
72 
73         const val INSTALL_ACTION_CB = "PackageInstallerTestBase.install_cb"
74 
75         const val CONTENT_AUTHORITY = "android.packageinstaller.install.cts.fileprovider"
76 
77         const val PACKAGE_INSTALLER_PACKAGE_NAME = "com.android.packageinstaller"
78         const val SYSTEM_PACKAGE_NAME = "android"
79         const val SHELL_PACKAGE_NAME = "com.android.shell"
80         const val APP_OP_STR = "REQUEST_INSTALL_PACKAGES"
81 
82         const val PROPERTY_IS_PRE_APPROVAL_REQUEST_AVAILABLE = "is_preapproval_available"
83         const val PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE =
84                 "is_update_ownership_enforcement_available"
85 
86         const val TIMEOUT = 60000L
87         const val INSTALL_INSTANT_APP = 0x00000800
88         const val INSTALL_REQUEST_UPDATE_OWNERSHIP = 0x02000000
89 
90         val context: Context = InstrumentationRegistry.getTargetContext()
91         val testUserId: Int = context.user.identifier
92     }
93 
94     @get:Rule
95     val disableAnimationsRule = DisableAnimationRule()
96 
97     @get:Rule
98     val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
99 
100     protected val pm: PackageManager = context.packageManager
101     protected val pi = pm.packageInstaller
102     protected val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
103     private val apkFile = File(context.filesDir, TEST_APK_NAME)
104 
105     data class SessionResult(val status: Int?, val preapproval: Boolean?, val message: String?)
106 
107     /** If a status was received the value of the status, otherwise null */
108     private var installSessionResult = LinkedBlockingQueue<SessionResult>()
109 
110     private val receiver = object : BroadcastReceiver() {
111         override fun onReceive(context: Context, intent: Intent) {
112             val status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID)
113             val preapproval = intent.getBooleanExtra(EXTRA_PRE_APPROVAL, false /* defaultValue */)
114             val msg = intent.getStringExtra(EXTRA_STATUS_MESSAGE)
115             Log.d(TAG, "status: $status, msg: $msg")
116 
117             if (status == STATUS_PENDING_USER_ACTION) {
118                 val activityIntent = intent.getParcelableExtra(EXTRA_INTENT, Intent::class.java)
119                 Assert.assertEquals(activityIntent!!.extras!!.keySet().size, 1)
120                 activityIntent.addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
121                 installDialogStarter.activity.startActivityForResult(activityIntent)
122             }
123 
124             installSessionResult.offer(SessionResult(status, preapproval, msg))
125         }
126     }
127 
128     @Before
129     fun copyTestApk() {
130         File(TEST_APK_LOCATION, TEST_APK_NAME).copyTo(target = apkFile, overwrite = true)
131     }
132 
133     @Before
134     fun wakeUpScreen() {
135         if (!uiDevice.isScreenOn) {
136             uiDevice.wakeUp()
137         }
138         uiDevice.executeShellCommand("wm dismiss-keyguard")
139     }
140 
141     @Before
142     fun assertTestPackageNotInstalled() {
143         try {
144             context.packageManager.getPackageInfo(TEST_APK_PACKAGE_NAME, 0)
145             Assert.fail("Package should not be installed")
146         } catch (expected: PackageManager.NameNotFoundException) {
147         }
148     }
149 
150     @Before
151     fun registerInstallResultReceiver() {
152         context.registerReceiver(receiver, IntentFilter(INSTALL_ACTION_CB),
153             Context.RECEIVER_EXPORTED)
154     }
155 
156     @Before
157     fun waitForUIIdle() {
158         uiDevice.waitForIdle()
159     }
160 
161     /**
162      * Wait for session's install result and return it
163      */
164     protected fun getInstallSessionResult(timeout: Long = TIMEOUT): SessionResult {
165         return getInstallSessionResult(installSessionResult, timeout)
166     }
167 
168     protected fun getInstallSessionResult(
169         installResult: LinkedBlockingQueue<SessionResult>,
170         timeout: Long = TIMEOUT
171     ): SessionResult {
172         return installResult.poll(timeout, TimeUnit.MILLISECONDS)
173             ?: SessionResult(null /* status */, null /* preapproval */, "Fail to poll result")
174     }
175 
176     protected fun startInstallationViaSessionNoPrompt() {
177         startInstallationViaSession(
178                 0 /* installFlags */,
179                 TEST_APK_NAME,
180                 null /* packageSource */,
181                 false /* expectedPrompt */
182         )
183     }
184 
185     protected fun startInstallationViaSessionWithPackageSource(packageSource: Int?) {
186         startInstallationViaSession(0 /* installFlags */, TEST_APK_NAME, packageSource)
187     }
188 
189     protected fun createSession(
190         installFlags: Int,
191         isMultiPackage: Boolean,
192         packageSource: Int?,
193         paramsBlock: (PackageInstaller.SessionParams) -> Unit = {},
194     ): Pair<Int, Session> {
195         // Create session
196         val sessionParam = PackageInstaller.SessionParams(MODE_FULL_INSTALL)
197         // Handle additional install flags
198         if (installFlags and INSTALL_INSTANT_APP != 0) {
199             sessionParam.setInstallAsInstantApp(true)
200         }
201         if (installFlags and INSTALL_REQUEST_UPDATE_OWNERSHIP != 0) {
202             sessionParam.setRequestUpdateOwnership(true)
203         }
204         if (isMultiPackage) {
205             sessionParam.setMultiPackage()
206         }
207         if (packageSource != null) {
208             sessionParam.setPackageSource(packageSource)
209         }
210 
211         paramsBlock(sessionParam)
212 
213         val sessionId = pi.createSession(sessionParam)
214         val session = pi.openSession(sessionId)!!
215 
216         return Pair(sessionId, session)
217     }
218 
219     protected fun writeSession(session: Session, apkName: String) {
220         val apkFile = File(context.filesDir, apkName)
221         // Write data to session
222         apkFile.inputStream().use { fileOnDisk ->
223             session.openWrite(apkName, 0, -1).use { sessionFile ->
224                 fileOnDisk.copyTo(sessionFile)
225             }
226         }
227     }
228 
229     protected fun commitSession(
230             session: Session,
231             expectedPrompt: Boolean = true,
232             needFuture: Boolean = false
233     ): CompletableFuture<Int>? {
234         var intent = Intent(INSTALL_ACTION_CB)
235                 .setPackage(context.getPackageName())
236                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
237         val pendingIntent = PendingIntent.getBroadcast(
238                 context, 0 /* requestCode */, intent, FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
239 
240         var dialog: CompletableFuture<Int>? = null
241 
242         if (!expectedPrompt) {
243             session.commit(pendingIntent.intentSender)
244             return dialog
245         }
246 
247         // Commit session
248         if (needFuture) {
249             dialog = FutureResultActivity.doAndAwaitStart {
250                 session.commit(pendingIntent.intentSender)
251             }
252         } else {
253             session.commit(pendingIntent.intentSender)
254         }
255 
256         // The system should have asked us to launch the installer
257         val result = getInstallSessionResult()
258         Assert.assertEquals(STATUS_PENDING_USER_ACTION, result.status)
259         Assert.assertEquals(false, result.preapproval)
260 
261         return dialog
262     }
263 
264     protected fun startRequestUserPreapproval(
265         session: Session,
266         details: PreapprovalDetails,
267         expectedPrompt: Boolean = true
268     ) {
269         // In some abnormal cases, passing expectedPrompt as false to return immediately without
270         // waiting for timeout (60 secs).
271         if (!expectedPrompt) { requestSession(session, details); return }
272 
273         FutureResultActivity.doAndAwaitStart {
274             requestSession(session, details)
275         }
276 
277         // The system should have asked us to launch the installer
278         val result = getInstallSessionResult()
279         Assert.assertEquals(STATUS_PENDING_USER_ACTION, result.status)
280         Assert.assertEquals(true, result.preapproval)
281     }
282 
283     private fun requestSession(session: Session, details: PreapprovalDetails) {
284         val pendingIntent = PendingIntent.getBroadcast(context, 0 /* requestCode */,
285                 Intent(INSTALL_ACTION_CB).setPackage(context.packageName),
286                 FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
287         session.requestUserPreapproval(details, pendingIntent.intentSender)
288     }
289 
290     protected fun startInstallationViaSession(
291         installFlags: Int = 0,
292         apkName: String = TEST_APK_NAME,
293         packageSource: Int? = null,
294         expectedPrompt: Boolean = true,
295         needFuture: Boolean = false,
296         paramsBlock: (PackageInstaller.SessionParams) -> Unit = {}
297     ): CompletableFuture<Int>? {
298         val (_, session) = createSession(installFlags, false, packageSource, paramsBlock)
299         writeSession(session, apkName)
300         return commitSession(session, expectedPrompt, needFuture)
301     }
302 
303     protected fun writeAndCommitSession(
304             apkName: String,
305             session: Session,
306             expectedPrompt: Boolean = true
307     ) {
308         writeSession(session, apkName)
309         commitSession(session, expectedPrompt)
310     }
311 
312     protected fun startInstallationViaMultiPackageSession(
313         installFlags: Int,
314         vararg apkNames: String,
315         needFuture: Boolean = false
316     ): CompletableFuture<Int>? {
317         val (sessionId, session) = createSession(installFlags, true, null)
318         for (apkName in apkNames) {
319             val (childSessionId, childSession) = createSession(installFlags, false, null)
320             writeSession(childSession, apkName)
321             session.addChildSessionId(childSessionId)
322         }
323         return commitSession(session, needFuture = needFuture)
324     }
325 
326     /**
327      * Start an installation via an Intent
328      */
329     protected fun startInstallationViaIntent(
330             intent: Intent = getInstallationIntent()
331     ): CompletableFuture<Int> {
332         return installDialogStarter.activity.startActivityForResult(intent)
333     }
334 
335     protected fun getInstallationIntent(): Intent {
336         val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
337         intent.data = FileProvider.getUriForFile(context, CONTENT_AUTHORITY, apkFile)
338         intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
339         intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
340 
341         return intent
342     }
343 
344     protected fun startInstallationViaPreapprovalSession(session: Session) {
345         val pendingIntent = PendingIntent.getBroadcast(context, 0 /* requestCode */,
346                 Intent(INSTALL_ACTION_CB).setPackage(context.packageName),
347                 FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
348         session.commit(pendingIntent.intentSender)
349     }
350 
351     fun assertInstalled(
352         flags: PackageManager.PackageInfoFlags = PackageManager.PackageInfoFlags.of(0)
353     ): PackageInfo {
354         // Throws exception if package is not installed.
355         return pm.getPackageInfo(TEST_APK_PACKAGE_NAME, flags)
356     }
357 
358     fun assertNotInstalled() {
359         try {
360             pm.getPackageInfo(TEST_APK_PACKAGE_NAME, PackageManager.PackageInfoFlags.of(0))
361             Assert.fail("Package should not be installed")
362         } catch (expected: PackageManager.NameNotFoundException) {
363         }
364     }
365 
366     /**
367      * Click a button in the UI of the installer app
368      *
369      * @param resId The resource ID of the button to click
370      */
371     fun clickInstallerUIButton(resId: String) {
372         clickInstallerUIButton(By.res(PACKAGE_INSTALLER_PACKAGE_NAME, resId))
373     }
374 
375     /**
376      * Click a button in the UI of the installer app
377      *
378      * @param bySelector The bySelector of the button to click
379      */
380     fun clickInstallerUIButton(bySelector: BySelector) {
381         val startTime = System.currentTimeMillis()
382         while (startTime + TIMEOUT > System.currentTimeMillis()) {
383             try {
384                 uiDevice.wait(Until.findObject(bySelector), 1000).click()
385                 return
386             } catch (ignore: Throwable) {
387             }
388         }
389         Assert.fail("Failed to click the button: $bySelector")
390     }
391 
392     /**
393      * Sets the given secure setting to the provided value.
394      */
395     fun setSecureSetting(secureSetting: String, value: Int) {
396         uiDevice.executeShellCommand("settings put --user $testUserId secure $secureSetting $value")
397     }
398 
399     fun setSecureFrp(secureFrp: Boolean) {
400         uiDevice.executeShellCommand("settings " +
401                 "put global secure_frp_mode ${if (secureFrp) 1 else 0}")
402     }
403 
404     @After
405     fun unregisterInstallResultReceiver() {
406         try {
407             context.unregisterReceiver(receiver)
408         } catch (ignored: IllegalArgumentException) {
409         }
410     }
411 
412     @After
413     @Before
414     fun uninstallTestPackage() {
415         uninstallPackage(TEST_APK_PACKAGE_NAME)
416     }
417 
418     fun uninstallPackage(packageName: String) {
419         uiDevice.executeShellCommand("pm uninstall $packageName")
420     }
421 
422     fun installTestPackage(extraArgs: String = "") {
423         installPackage(TEST_APK_NAME, extraArgs)
424     }
425 
426     fun installPackage(apkName: String, extraArgs: String = "") {
427         Log.d(TAG, "installPackage(): apkName=$apkName, extraArgs='$extraArgs'")
428         uiDevice.executeShellCommand("pm install $extraArgs " +
429                 File(TEST_APK_LOCATION, apkName).canonicalPath)
430     }
431 
432     fun getDeviceProperty(name: String): String? {
433         return SystemUtil.callWithShellPermissionIdentity {
434             DeviceConfig.getProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name)
435         }
436     }
437 
438     fun setDeviceProperty(name: String, value: String?) {
439         SystemUtil.callWithShellPermissionIdentity {
440             DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, name, value,
441                     false /* makeDefault */)
442         }
443     }
444 }
445