1 package com.android.onboarding.bedsteadonboarding.activities 2 3 import android.content.Context 4 import android.content.Intent 5 import android.os.Build 6 import android.os.Bundle 7 import android.util.Log 8 import androidx.activity.ComponentActivity 9 import com.android.onboarding.bedsteadonboarding.permissions.TestPermissions 10 import com.android.onboarding.bedsteadonboarding.providers.ConfigProviderUtil 11 12 /** 13 * It is an activity whose exported value is always set as true. It's purpose is to launch another 14 * activity of the same app whose exported value is set as false. Every onboarding app would have 15 * this activity added via dependency on bedstead-onboarding. 16 */ 17 class TrampolineActivity : ComponentActivity() { 18 onCreatenull19 override fun onCreate(savedInstanceState: Bundle?) { 20 super.onCreate(savedInstanceState) 21 if (!hasPermission()) { 22 Log.e(TAG, "Unauthorized request for creating TrampolineActivity ") 23 finish() 24 return 25 } 26 27 val nodeIntent = 28 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 29 intent.getParcelableExtra(EXTRA_NODE_START_INTENT_KEY, Intent::class.java) 30 } else { 31 intent.getParcelableExtra(EXTRA_NODE_START_INTENT_KEY) 32 } 33 34 if (nodeIntent != null) { 35 startActivity(nodeIntent) 36 } else { 37 Log.e(TAG, NODE_NOT_LAUNCHED_ERROR_MSG) 38 } 39 finish() 40 } 41 42 /** 43 * Returns true if either the code is triggered by Robolectric or else there is some test 44 * configuration set by Test Process and the caller is instrumented. It will always return false 45 * when it is called in production. 46 */ hasPermissionnull47 private fun hasPermission(): Boolean { 48 return when { 49 requireRobolectric() -> true 50 isTestConfigSet(context = this) -> { 51 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 52 return TestPermissions.uidIsInstrumented(this, launchedFromUid) 53 } else { 54 return true 55 } 56 } 57 else -> false 58 } 59 } 60 requireRobolectricnull61 private fun requireRobolectric() = "robolectric" == Build.FINGERPRINT 62 63 /** 64 * Returns if the test configuration has been set. It will always return false when it is called 65 * in production. 66 */ 67 private fun isTestConfigSet(context: Context): Boolean { 68 val uri = ConfigProviderUtil.getTestConfigUri(context) 69 val cursor = 70 context.contentResolver.query( 71 uri, 72 arrayOf(ConfigProviderUtil.TEST_NODE_CLASS_COLUMN), 73 /* selection= */ null, 74 /* selectionArgs= */ null, 75 /* sortOrder= */ null, 76 /* cancellationSignal= */ null, 77 ) ?: return false 78 return cursor.use { it.moveToFirst() } // Returns if there is first record 79 } 80 81 companion object { 82 private const val EXTRA_NODE_START_INTENT_KEY = 83 "com.android.onboarding.bedsteadonboarding.activities.extra.NODE_START_INTENT" 84 private const val NODE_NOT_LAUNCHED_ERROR_MSG = "Couldn't launch node as intent is null" 85 private const val TAG = "TrampolineActivity" 86 } 87 } 88