1 /* 2 * 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 com.android.permissioncontroller.role.model; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.Process; 28 import android.os.UserHandle; 29 import android.util.ArraySet; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.Nullable; 33 34 import com.android.modules.utils.build.SdkLevel; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Objects; 39 40 /** 41 * Specifies a required component for an application to qualify for a {@link Role}. 42 */ 43 public abstract class RequiredComponent { 44 45 /** 46 * The {@code Intent} or {@code IntentFilter} data to match the components. 47 */ 48 @NonNull 49 private final IntentFilterData mIntentFilterData; 50 51 /** 52 * The minimum target SDK version for this component to be required. 53 * <p> 54 * This also implies a minimum platform SDK version for this component to be required. 55 */ 56 private final int mMinTargetSdkVersion; 57 58 /** 59 * Optional permission required on a component for match to succeed. 60 * 61 * @see android.content.pm.ActivityInfo#permission 62 * @see android.content.pm.ServiceInfo#permission 63 */ 64 @Nullable 65 private final String mPermission; 66 67 /** 68 * The query flags to match the components with. 69 */ 70 private final int mQueryFlags; 71 72 /** 73 * The meta data required on a component for match to succeed. 74 * 75 * @see android.content.pm.PackageItemInfo#metaData 76 */ 77 @NonNull 78 private final List<RequiredMetaData> mMetaData; 79 RequiredComponent(@onNull IntentFilterData intentFilterData, int minTargetSdkVersion, @Nullable String permission, int queryFlags, @NonNull List<RequiredMetaData> metaData)80 public RequiredComponent(@NonNull IntentFilterData intentFilterData, int minTargetSdkVersion, 81 @Nullable String permission, int queryFlags, @NonNull List<RequiredMetaData> metaData) { 82 mIntentFilterData = intentFilterData; 83 mMinTargetSdkVersion = minTargetSdkVersion; 84 mPermission = permission; 85 mQueryFlags = queryFlags; 86 mMetaData = metaData; 87 } 88 89 @NonNull getIntentFilterData()90 public IntentFilterData getIntentFilterData() { 91 return mIntentFilterData; 92 } 93 getMinTargetSdkVersion()94 public int getMinTargetSdkVersion() { 95 return mMinTargetSdkVersion; 96 } 97 98 /** 99 * Check whether this required component is available. 100 * 101 * @return whether this required component is available 102 */ isAvailable()103 public boolean isAvailable() { 104 // Workaround to match the value 33+ for T+ in roles.xml before SDK finalization. 105 if (mMinTargetSdkVersion >= 33) { 106 return SdkLevel.isAtLeastT(); 107 } else { 108 return Build.VERSION.SDK_INT >= mMinTargetSdkVersion; 109 } 110 } 111 112 /** 113 * Check whether this required component is required for a package. 114 * 115 * @param applicationInfo the {@link ApplicationInfo} for the package 116 * @return whether this required component is required 117 */ isRequired(@onNull ApplicationInfo applicationInfo)118 public boolean isRequired(@NonNull ApplicationInfo applicationInfo) { 119 return isAvailable() && applicationInfo.targetSdkVersion >= mMinTargetSdkVersion; 120 } 121 122 @Nullable getPermission()123 public String getPermission() { 124 return mPermission; 125 } 126 127 @NonNull getMetaData()128 public List<RequiredMetaData> getMetaData() { 129 return mMetaData; 130 } 131 132 /** 133 * Get the component that matches this required component within a package, if any. 134 * 135 * @param packageName the package name for this query 136 * @param context the {@code Context} to retrieve system services 137 * 138 * @return the matching component, or {@code null} if none. 139 */ 140 @Nullable getQualifyingComponentForPackage(@onNull String packageName, @NonNull Context context)141 public ComponentName getQualifyingComponentForPackage(@NonNull String packageName, 142 @NonNull Context context) { 143 List<ComponentName> componentNames = getQualifyingComponentsInternal(packageName, 144 Process.myUserHandle(), context); 145 return !componentNames.isEmpty() ? componentNames.get(0) : null; 146 } 147 148 /** 149 * Get the list of components that match this required component, <b>at most one component per 150 * package</b> and ordered from best to worst. 151 * 152 * @param user the user to get the qualifying components. 153 * @param context the {@code Context} to retrieve system services 154 * 155 * @return the list of matching components 156 * 157 * @see Role#getQualifyingPackagesAsUser(UserHandle, Context) 158 */ 159 @NonNull getQualifyingComponentsAsUser(@onNull UserHandle user, @NonNull Context context)160 public List<ComponentName> getQualifyingComponentsAsUser(@NonNull UserHandle user, 161 @NonNull Context context) { 162 return getQualifyingComponentsInternal(null, user, context); 163 } 164 165 @NonNull getQualifyingComponentsInternal(@ullable String packageName, @NonNull UserHandle user, @NonNull Context context)166 private List<ComponentName> getQualifyingComponentsInternal(@Nullable String packageName, 167 @NonNull UserHandle user, @NonNull Context context) { 168 Intent intent = mIntentFilterData.createIntent(); 169 if (packageName != null) { 170 intent.setPackage(packageName); 171 } 172 int flags = mQueryFlags | PackageManager.MATCH_DIRECT_BOOT_AWARE 173 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 174 boolean hasMetaData = !mMetaData.isEmpty(); 175 if (hasMetaData) { 176 flags |= PackageManager.GET_META_DATA; 177 } 178 List<ResolveInfo> resolveInfos = queryIntentComponentsAsUser(intent, flags, user, context); 179 180 ArraySet<String> componentPackageNames = new ArraySet<>(); 181 List<ComponentName> componentNames = new ArrayList<>(); 182 int resolveInfosSize = resolveInfos.size(); 183 for (int resolveInfosIndex = 0; resolveInfosIndex < resolveInfosSize; resolveInfosIndex++) { 184 ResolveInfo resolveInfo = resolveInfos.get(resolveInfosIndex); 185 186 if (mPermission != null) { 187 String componentPermission = getComponentPermission(resolveInfo); 188 if (!Objects.equals(componentPermission, mPermission)) { 189 continue; 190 } 191 } 192 193 if (hasMetaData) { 194 Bundle componentMetaData = getComponentMetaData(resolveInfo); 195 if (componentMetaData == null) { 196 componentMetaData = Bundle.EMPTY; 197 } 198 boolean isMetaDataQualified = true; 199 int metaDataSize = mMetaData.size(); 200 for (int metaDataIndex = 0; metaDataIndex < metaDataSize; metaDataIndex++) { 201 RequiredMetaData metaData = mMetaData.get(metaDataIndex); 202 203 if (!metaData.isQualified(componentMetaData)) { 204 isMetaDataQualified = false; 205 break; 206 } 207 } 208 if (!isMetaDataQualified) { 209 continue; 210 } 211 } 212 213 ComponentName componentName = getComponentComponentName(resolveInfo); 214 String componentPackageName = componentName.getPackageName(); 215 if (componentPackageNames.contains(componentPackageName)) { 216 continue; 217 } 218 219 componentPackageNames.add(componentPackageName); 220 componentNames.add(componentName); 221 } 222 return componentNames; 223 } 224 225 /** 226 * Query the {@code PackageManager} for components matching an {@code Intent}, ordered from best 227 * to worst. 228 * 229 * @param intent the {@code Intent} to match against 230 * @param flags the flags for this query 231 * @param user the user for this query 232 * @param context the {@code Context} to retrieve system services 233 * 234 * @return the list of matching components 235 */ 236 @NonNull queryIntentComponentsAsUser(@onNull Intent intent, int flags, @NonNull UserHandle user, @NonNull Context context)237 protected abstract List<ResolveInfo> queryIntentComponentsAsUser(@NonNull Intent intent, 238 int flags, @NonNull UserHandle user, @NonNull Context context); 239 240 /** 241 * Get the {@code ComponentName} of a component. 242 * 243 * @param resolveInfo the {@code ResolveInfo} of the component 244 * 245 * @return the {@code ComponentName} of the component 246 */ 247 @NonNull getComponentComponentName(@onNull ResolveInfo resolveInfo)248 protected abstract ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo); 249 250 /** 251 * Get the permission required to access a component. 252 * 253 * @param resolveInfo the {@code ResolveInfo} of the component 254 * 255 * @return the permission required to access a component 256 */ 257 @Nullable getComponentPermission(@onNull ResolveInfo resolveInfo)258 protected abstract String getComponentPermission(@NonNull ResolveInfo resolveInfo); 259 260 /** 261 * Get the meta data associated with a component. 262 * 263 * @param resolveInfo the {@code ResolveInfo} of the component 264 * 265 * @return the meta data associated with a component 266 */ 267 @Nullable getComponentMetaData(@onNull ResolveInfo resolveInfo)268 protected abstract Bundle getComponentMetaData(@NonNull ResolveInfo resolveInfo); 269 270 @Override toString()271 public String toString() { 272 return "RequiredComponent{" 273 + "mIntentFilterData=" + mIntentFilterData 274 + ", mMinTargetSdkVersion=" + mMinTargetSdkVersion 275 + ", mPermission='" + mPermission + '\'' 276 + ", mQueryFlags=" + mQueryFlags 277 + ", mMetaData=" + mMetaData 278 + '}'; 279 } 280 281 @Override equals(Object object)282 public boolean equals(Object object) { 283 if (this == object) { 284 return true; 285 } 286 if (object == null || getClass() != object.getClass()) { 287 return false; 288 } 289 RequiredComponent that = (RequiredComponent) object; 290 return Objects.equals(mIntentFilterData, that.mIntentFilterData) 291 && Objects.equals(mMinTargetSdkVersion, that.mMinTargetSdkVersion) 292 && Objects.equals(mPermission, that.mPermission) 293 && mQueryFlags == that.mQueryFlags 294 && Objects.equals(mMetaData, that.mMetaData); 295 } 296 297 @Override hashCode()298 public int hashCode() { 299 return Objects.hash(mIntentFilterData, mMinTargetSdkVersion, mPermission, mQueryFlags, 300 mMetaData); 301 } 302 } 303