<lambda>null1 package com.android.onboarding.bedsteadonboarding.providers
2 
3 import android.content.ContentProvider
4 import android.content.ContentValues
5 import android.content.Context
6 import android.content.UriMatcher
7 import android.content.pm.ProviderInfo
8 import android.database.Cursor
9 import android.database.MatrixCursor
10 import android.net.Uri
11 import android.os.Binder
12 import android.os.Build
13 import android.os.Bundle
14 import android.util.Log
15 import com.android.onboarding.bedsteadonboarding.data.NodeData
16 import com.android.onboarding.bedsteadonboarding.data.TestConfigData
17 import com.android.onboarding.bedsteadonboarding.fakes.FakeActivityNodeHelper
18 import com.android.onboarding.bedsteadonboarding.permissions.TestPermissions
19 import com.android.onboarding.bedsteadonboarding.providers.ConfigProviderUtil.TEST_NODE_CLASS_COLUMN
20 import com.android.onboarding.contracts.ContractResult
21 
22 /**
23  * Content Provider used for storing all the test configs. Each production onboarding app will have
24  * an instance of this provider to store test configuration to be used when such app is executing
25  * during tests. Only the test process has permissions to insert and delete the configurations
26  * whereas all onboarding apps can query even outside of tests.
27  */
28 class TestContentProvider : ContentProvider() {
29   private lateinit var baseTestConfigUri: Uri
30 
31   private val testConfigUriMatcher = UriMatcher(UriMatcher.NO_MATCH)
32 
33   private var testConfigData = TestConfigData(listOf())
34   private var fakeActivityNodesConfig = mapOf<String, ContractResult>()
35 
36   override fun attachInfo(context: Context?, info: ProviderInfo) {
37     super.attachInfo(context, info)
38     val authority = info.authority
39     baseTestConfigUri = ConfigProviderUtil.getBaseContentUri(authority)
40     testConfigUriMatcher.addURI(authority, ConfigProviderUtil.TEST_CONFIG_PATH, IS_TEST_CONFIG_KEY)
41     testConfigUriMatcher.addURI(
42       authority,
43       ConfigProviderUtil.FAKE_ACTIVITY_NODE_CONFIG_QUERY_PATH,
44       IS_FAKE_ACTIVITY_NODE_CONFIG_QUERY_KEY,
45     )
46     testConfigUriMatcher.addURI(
47       authority,
48       ConfigProviderUtil.FAKE_ACTIVITY_NODE_CONFIG_INSERT_PATH,
49       IS_FAKE_ACTIVITY_NODE_CONFIG_INSERT_KEY,
50     )
51   }
52 
53   override fun onCreate(): Boolean {
54     return true
55   }
56 
57   override fun query(
58     uri: Uri,
59     projection: Array<String>?,
60     selection: String?,
61     selectionArgs: Array<String>?,
62     sortOrder: String?,
63   ): Cursor {
64     when (testConfigUriMatcher.match(uri)) {
65       IS_TEST_CONFIG_KEY -> {
66         val matrixCursor = MatrixCursor(arrayOf(TEST_NODE_CLASS_COLUMN))
67         for (nodeData in testConfigData.testNodes) {
68           matrixCursor.newRow().add(TEST_NODE_CLASS_COLUMN, nodeData.allowedContractIdentifier)
69         }
70         return matrixCursor
71       }
72       IS_FAKE_ACTIVITY_NODE_CONFIG_QUERY_KEY -> {
73         val matrixCursor = MatrixCursor(arrayOf())
74         val contractIdentifier = uri.lastPathSegment
75         if (contractIdentifier != null) {
76           val contractResult = fakeActivityNodesConfig[contractIdentifier]
77           if (contractResult != null) {
78             matrixCursor.extras =
79               FakeActivityNodeHelper.createBundleFromContractResultAndIdentifier(
80                 contractResult,
81                 contractIdentifier,
82               )
83           }
84         }
85         return matrixCursor
86       }
87       else -> throw UnsupportedOperationException("Unsupported Query Uri $uri")
88     }
89   }
90 
91   override fun getType(uri: Uri): String? {
92     throw UnsupportedOperationException("getType")
93   }
94 
95   override fun insert(uri: Uri, values: ContentValues?): Uri {
96     throw UnsupportedOperationException("insert")
97   }
98 
99   override fun insert(uri: Uri, values: ContentValues?, extras: Bundle?): Uri? {
100     if (checkPermissionOrThrow()) {
101       when (testConfigUriMatcher.match(uri)) {
102         IS_FAKE_ACTIVITY_NODE_CONFIG_INSERT_KEY -> {
103           if (extras == null) {
104             Log.e(TAG, "Fake activity node config passed is null while inserting")
105             return Uri.EMPTY
106           }
107           val contractIdentifierAndResult =
108             FakeActivityNodeHelper.extractContractIdentifierAndContractResultFromBundle(extras)
109               ?: error("contract identifier in bundle in null")
110           val mutableFakeActivityNodesConfig = fakeActivityNodesConfig.toMutableMap()
111           mutableFakeActivityNodesConfig[contractIdentifierAndResult.first] =
112             contractIdentifierAndResult.second
113           fakeActivityNodesConfig = mutableFakeActivityNodesConfig.toMap()
114         }
115         else -> throw UnsupportedOperationException("Unsupported Insertion Uri $uri")
116       }
117     }
118     return Uri.EMPTY
119   }
120   override fun bulkInsert(uri: Uri, contentValuesList: Array<ContentValues>): Int {
121     if (checkPermissionOrThrow()) {
122       val nodeConfigList = mutableListOf<NodeData>()
123       when (testConfigUriMatcher.match(uri)) {
124         IS_TEST_CONFIG_KEY -> {
125           for (contentValues in contentValuesList) {
126             contentValues.getAsString(TEST_NODE_CLASS_COLUMN)?.let { testNodeClassName ->
127               nodeConfigList.add(NodeData(testNodeClassName))
128             }
129           }
130         }
131         else -> throw UnsupportedOperationException("Unsupported Insertion Uri $uri")
132       }
133       testConfigData = TestConfigData(nodeConfigList.toList())
134       return nodeConfigList.size
135     }
136     return 0
137   }
138 
139   override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
140     if (checkPermissionOrThrow()) {
141       when (testConfigUriMatcher.match(uri)) {
142         IS_TEST_CONFIG_KEY -> {
143           testConfigData = TestConfigData(listOf())
144           fakeActivityNodesConfig = mapOf()
145         }
146         else -> throw UnsupportedOperationException("Unsupported Deletion Uri $uri")
147       }
148       return 1
149     }
150     return 0
151   }
152 
153   override fun update(
154     uri: Uri,
155     values: ContentValues?,
156     selection: String?,
157     selectionArgs: Array<String>?,
158   ): Int {
159     throw UnsupportedOperationException("update")
160   }
161 
162   private fun checkPermissionOrThrow(): Boolean {
163     if (hasPermission()) return true
164     throw SecurityException("Does not have permission")
165   }
166 
167   private fun hasPermission(): Boolean {
168     val context = context
169     return when {
170       "robolectric" == Build.FINGERPRINT -> true
171       context != null ->
172         TestPermissions.canCallerExecuteTestFunctionality(context, Binder.getCallingUid())
173       else -> {
174         Log.e(TAG, "Can't check test permission since context is null")
175         false
176       }
177     }
178   }
179 
180   companion object {
181     private const val IS_TEST_CONFIG_KEY = 1
182     private const val IS_FAKE_ACTIVITY_NODE_CONFIG_QUERY_KEY = 2
183     private const val IS_FAKE_ACTIVITY_NODE_CONFIG_INSERT_KEY = 3
184     private const val TAG = "TestContentProvider"
185   }
186 }
187