<lambda>null1 package com.android.onboarding.bedsteadonboarding.contractutils
2 
3 import android.content.Context
4 import android.os.Process
5 import android.util.Log
6 import com.android.onboarding.bedsteadonboarding.permissions.TestPermissions
7 import com.android.onboarding.bedsteadonboarding.providers.ConfigProviderUtil
8 import com.android.onboarding.bedsteadonboarding.providers.ConfigProviderUtil.TEST_NODE_CLASS_COLUMN
9 
10 private const val TAG = "TestFramework"
11 
12 /**
13  * Contains helper methods which different nodes can call to check if they are allowed to execute.
14  */
15 object ContractExecutionEligibilityChecker {
16 
17   private val isRunningOnDebuggableDevice: Boolean by lazy {
18     TestPermissions.isRunningOnDebuggableDevice()
19   }
20   internal val ALLOW_ALL_NODES = null
21 
22   /**
23    * Checks if the test configurations are set. If it is not set then node is allowed to execute.
24    * This will always be the case when the node is being executed in production. If it is set then
25    * it means that test process have set those configurations. So it would then check if the node is
26    * being allowed to execute. If not then it will terminate the app process.
27    *
28    * @param context context of the package being executed.
29    * @param contractIdentifier contract identifier of the node which is to be executed obtainable
30    *   via [ContractUtils].
31    */
32   fun terminateIfNodeIsTriggeredByTestAndIsNotAllowed(
33     context: Context,
34     contractIdentifier: String,
35   ) {
36     try {
37       // Fetch the list of contracts which are allowed to execute.
38       val allowedNodes = getAllowedNodes(context)
39 
40       if (allowedNodes == ALLOW_ALL_NODES) {
41         return
42       }
43 
44       val nodeToCheck = contractIdentifier
45 
46       // If the node is attempted to be executed as part of test but it is not allowed.
47       if (nodeToCheck !in allowedNodes) {
48         Log.w(TAG, "Contract $nodeToCheck is not allowed to execute")
49         // Kill the app under test.
50         Process.killProcess(Process.myPid())
51       }
52     } catch (t: Throwable) {
53       // For safety, ignore the exception since current function would run in production flow.
54       Log.e(TAG, "Error while fetching list of allowed nodes", t)
55     }
56   }
57 
58   fun terminateIfNodeIsTriggeredByTestAndIsNotAllowed(context: Context, contractClass: Class<*>) =
59     terminateIfNodeIsTriggeredByTestAndIsNotAllowed(
60       context,
61       ContractUtils.getContractIdentifier(contractClass),
62     )
63 
64   /**
65    * Fetches the list of contractClasses of nodes which are allowed to execute. It will always
66    * return [ALLOW_ALL_NODES] when the node is being executed in production.
67    */
68   internal fun getAllowedNodes(context: Context): Set<String>? {
69     if (!isRunningOnDebuggableDevice) return ALLOW_ALL_NODES
70 
71     val uri = ConfigProviderUtil.getTestConfigUri(context)
72     var allowedNodes: MutableSet<String>? = null
73     // Fetch the list of contracts which are allowed to execute.
74     context.contentResolver
75       .query(
76         uri,
77         arrayOf(TEST_NODE_CLASS_COLUMN),
78         /* selection= */ null,
79         /* selectionArgs= */ null,
80         /* sortOrder= */ null,
81         /* cancellationSignal= */ null,
82       )
83       .use { cursor ->
84         if ((cursor != null) && cursor.moveToFirst()) {
85           do {
86             val columnIndex = cursor.getColumnIndex(TEST_NODE_CLASS_COLUMN)
87             require(columnIndex != -1) { "Column $TEST_NODE_CLASS_COLUMN not found." }
88             val allowedNode = cursor.getString(columnIndex)
89             if (allowedNodes == null) {
90               allowedNodes = mutableSetOf()
91             }
92             allowedNodes!!.add(allowedNode)
93           } while (cursor.moveToNext())
94         }
95       }
96     return allowedNodes?.toSet() ?: ALLOW_ALL_NODES
97   }
98 }
99