1 package com.android.onboarding.versions 2 3 import android.content.Context 4 import android.content.pm.PackageManager 5 import android.content.pm.PackageManager.NameNotFoundException 6 import android.provider.Settings 7 import android.util.Log 8 import com.android.onboarding.versions.annotations.ChangeId 9 import com.android.onboarding.versions.annotations.ChangeRadius 10 import com.android.onboarding.versions.annotations.ValidChangeId 11 import com.android.onboarding.versions.changes.ALL_CHANGE_IDS 12 import java.lang.IllegalArgumentException 13 import java.lang.IllegalStateException 14 import java.lang.UnsupportedOperationException 15 16 private const val LOG_TAG = "DefaultOnboardingChanges" 17 18 /** 19 * Default implementation of [OnboardingChanges]. 20 * 21 * For local testing, use `adb shell settings put global package.name.changeid 1` to enable and `adb 22 * shell settings put global package.name.changeid 0` to disable. For example to enable change ID 23 * 1234 on package `com.android.setup` we would use `adb shell settings put global 24 * com.android.setup.1234 1`. You can also use `adb shell settings delete global 25 * package.name.changeid` to use the default configured value. 26 */ 27 class DefaultOnboardingChanges(val context: Context) : OnboardingChanges { 28 29 private val version = Versions(context) 30 31 /** 32 * Map of package to the compliance date for that package. 33 * 34 * If a package has failed to be loaded, the value will be null. If it has not been attempted the 35 * key will be non-present. 36 */ 37 private val packageComplianceDates = <lambda>null38 mutableMapOf<String, ComplianceDate?>().apply { 39 put(context.packageName, version.myComplianceDate) 40 } 41 currentProcessSupportsChangenull42 override fun currentProcessSupportsChange(@ValidChangeId changeId: Long): Boolean { 43 return componentSupportsChange(context.packageName, changeId) 44 } 45 requireCurrentProcessSupportsChangenull46 override fun requireCurrentProcessSupportsChange(@ValidChangeId changeId: Long) { 47 if (currentProcessSupportsChange(changeId)) { 48 return 49 } 50 51 // currentProcessSupportsChange will throw if this is null 52 val changeData = ALL_CHANGE_IDS[changeId]!! 53 54 if (changeData.available == ChangeId.NOT_AVAILABLE) { 55 throw UnsupportedOperationException( 56 "This API is not available. The change ($changeId) is set to not available. See OnboardingChanges for options for enabling this change during test and development." 57 ) 58 } 59 60 throw UnsupportedOperationException( 61 "This API is not available. The change ($changeId) is available as of ${changeData.available} but the compliance date of this package is ${version.myComplianceDate}" 62 ) 63 } 64 loadSupportedChangesnull65 override fun loadSupportedChanges(component: String) { 66 val packageName = extractPackageFromComponent(component) 67 68 if (packageName == context.packageName) { 69 return 70 } 71 72 try { 73 val packageInfo = 74 context.packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA) 75 76 val complianceDateString = 77 packageInfo.applicationInfo?.metaData?.getString("onboarding-compliance-date") 78 ?: ComplianceDate.EARLIEST_COMPLIANCE_DATE_STRING 79 packageComplianceDates[packageName] = ComplianceDate(complianceDateString) 80 } catch (e: NameNotFoundException) { 81 Log.w(LOG_TAG, "Could not find package info for $packageName", e) 82 packageComplianceDates[packageName] = null 83 } 84 } 85 componentSupportsChangenull86 override fun componentSupportsChange( 87 component: String, 88 @ValidChangeId( 89 allowedChangeRadii = [ChangeRadius.MULTI_COMPONENT], 90 disallowedChangeRadiiError = 91 "You cannot query changes which are SINGLE_COMPONENT across process boundaries.", 92 ) 93 changeId: Long, 94 ): Boolean { 95 val packageName = extractPackageFromComponent(component) 96 97 val changeData = 98 ALL_CHANGE_IDS[changeId] 99 ?: throw IllegalStateException( 100 "Invalid change ID $changeId - this should have been blocked by the conformance checker" 101 ) 102 103 if ( 104 changeData.changeRadius == ChangeRadius.SINGLE_COMPONENT && packageName != context.packageName 105 ) { 106 // This should be blocked by conformance 107 throw IllegalArgumentException( 108 "You cannot query changes which are SINGLE_COMPONENT across process boundaries." 109 ) 110 } 111 112 val changeOverride = getChangeOverride(packageName, changeId) 113 if (changeOverride != null) { 114 return changeOverride 115 } 116 117 // If it's released, we assume it's available everywhere 118 // (this call should be blocked by conformance) 119 if (changeData.released != ChangeId.NOT_RELEASED) { 120 return true 121 } 122 123 if (!packageComplianceDates.containsKey(packageName)) { 124 loadSupportedChanges(component) 125 } 126 127 if (packageComplianceDates[packageName] == null) { 128 // If we can't fetch the supported features we assume no non-released features 129 return false 130 } 131 132 return packageComplianceDates[packageName]!!.isAvailable(changeData) 133 } 134 getChangeOverridenull135 private fun getChangeOverride(packageName: String, changeId: Long): Boolean? { 136 val settingKey = "$packageName.$changeId" 137 138 return try { 139 Settings.Global.getInt(context.contentResolver, settingKey) == 1 140 } catch (e: Settings.SettingNotFoundException) { 141 null 142 } 143 } 144 extractPackageFromComponentnull145 private fun extractPackageFromComponent(component: String) = component.split("/", limit = 2)[0] 146 } 147