1 /* 2 * Copyright (C) 2019 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.role.controller.behavior; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.content.pm.ResolveInfo; 23 import android.net.Uri; 24 import android.os.UserHandle; 25 import android.util.ArraySet; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 30 import com.android.modules.utils.build.SdkLevel; 31 import com.android.role.controller.model.Permissions; 32 import com.android.role.controller.model.Role; 33 import com.android.role.controller.model.RoleBehavior; 34 import com.android.role.controller.model.VisibilityMixin; 35 import com.android.role.controller.util.CollectionUtils; 36 import com.android.role.controller.util.PackageUtils; 37 import com.android.role.controller.util.UserUtils; 38 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.List; 42 43 /** 44 * Class for behavior of the browser role. 45 * 46 * @see com.android.settings.applications.DefaultAppSettings 47 * @see com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController 48 * @see com.android.settings.applications.defaultapps.DefaultBrowserPicker 49 * @see com.android.server.pm.PackageManagerService#resolveAllBrowserApps(int) 50 */ 51 public class BrowserRoleBehavior implements RoleBehavior { 52 private static final Intent BROWSER_INTENT = new Intent() 53 .setAction(Intent.ACTION_VIEW) 54 .addCategory(Intent.CATEGORY_BROWSABLE) 55 .setData(Uri.fromParts("http", "", null)); 56 57 private static final List<String> SYSTEM_BROWSER_PERMISSIONS = Arrays.asList( 58 android.Manifest.permission.ACCESS_COARSE_LOCATION, 59 android.Manifest.permission.ACCESS_FINE_LOCATION 60 ); 61 62 @Nullable 63 @Override getFallbackHolderAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)64 public String getFallbackHolderAsUser(@NonNull Role role, @NonNull UserHandle user, 65 @NonNull Context context) { 66 List<String> qualifyingPackageNames = getQualifyingPackagesAsUserInternal(null, false, 67 user, context); 68 if (qualifyingPackageNames.size() == 1) { 69 return qualifyingPackageNames.get(0); 70 } 71 72 if (SdkLevel.isAtLeastS()) { 73 List<String> qualifyingSystemPackageNames = getQualifyingPackagesAsUserInternal(null, 74 true, user, context); 75 if (qualifyingSystemPackageNames.size() == 1) { 76 return qualifyingSystemPackageNames.get(0); 77 } 78 79 List<String> defaultPackageNames = role.getDefaultHoldersAsUser(user, context); 80 return CollectionUtils.firstOrNull(defaultPackageNames); 81 } else { 82 return null; 83 } 84 } 85 86 // PackageManager.queryIntentActivities() will only return the default browser if one was set. 87 // Code in the Settings app passes PackageManager.MATCH_ALL and perform its own filtering, so we 88 // do the same thing here. 89 @Nullable 90 @Override getQualifyingPackagesAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)91 public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user, 92 @NonNull Context context) { 93 return getQualifyingPackagesAsUserInternal(null, false, user, context); 94 } 95 96 @Nullable 97 @Override isPackageQualifiedAsUser(@onNull Role role, @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context)98 public Boolean isPackageQualifiedAsUser(@NonNull Role role, @NonNull String packageName, 99 @NonNull UserHandle user, @NonNull Context context) { 100 List<String> packageNames = getQualifyingPackagesAsUserInternal(packageName, false, 101 user, context); 102 return !packageNames.isEmpty(); 103 } 104 105 @NonNull getQualifyingPackagesAsUserInternal(@ullable String packageName, boolean matchSystemOnly, @NonNull UserHandle user, @NonNull Context context)106 private List<String> getQualifyingPackagesAsUserInternal(@Nullable String packageName, 107 boolean matchSystemOnly, @NonNull UserHandle user, @NonNull Context context) { 108 Context userContext = UserUtils.getUserContext(context, user); 109 PackageManager userPackageManager = userContext.getPackageManager(); 110 Intent intent = BROWSER_INTENT; 111 if (packageName != null) { 112 intent = new Intent(intent) 113 .setPackage(packageName); 114 } 115 // To one's surprise, MATCH_ALL doesn't include MATCH_DIRECT_BOOT_*. 116 int flags = PackageManager.MATCH_ALL | PackageManager.MATCH_DIRECT_BOOT_AWARE 117 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DEFAULT_ONLY; 118 if (matchSystemOnly) { 119 flags |= PackageManager.MATCH_SYSTEM_ONLY; 120 } 121 List<ResolveInfo> resolveInfos = userPackageManager.queryIntentActivities(intent, flags); 122 ArraySet<String> packageNames = new ArraySet<>(); 123 int resolveInfosSize = resolveInfos.size(); 124 for (int i = 0; i < resolveInfosSize; i++) { 125 ResolveInfo resolveInfo = resolveInfos.get(i); 126 127 if (!resolveInfo.activityInfo.exported || !resolveInfo.handleAllWebDataURI) { 128 continue; 129 } 130 packageNames.add(resolveInfo.activityInfo.packageName); 131 } 132 return new ArrayList<>(packageNames); 133 } 134 135 @Override grantAsUser(@onNull Role role, @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context)136 public void grantAsUser(@NonNull Role role, @NonNull String packageName, 137 @NonNull UserHandle user, @NonNull Context context) { 138 // @see com.android.server.pm.permission.DefaultPermissionGrantPolicy 139 // #grantDefaultPermissionsToDefaultBrowser(java.lang.String, int) 140 if (SdkLevel.isAtLeastS()) { 141 if (PackageUtils.isSystemPackageAsUser(packageName, user, context)) { 142 Permissions.grantAsUser(packageName, SYSTEM_BROWSER_PERMISSIONS, false, false, 143 true, false, false, user, context); 144 } 145 } 146 } 147 148 @Override revokeAsUser(@onNull Role role, @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context)149 public void revokeAsUser(@NonNull Role role, @NonNull String packageName, 150 @NonNull UserHandle user, @NonNull Context context) { 151 if (SdkLevel.isAtLeastT()) { 152 if (PackageUtils.isSystemPackageAsUser(packageName, user, context)) { 153 Permissions.revokeAsUser(packageName, SYSTEM_BROWSER_PERMISSIONS, true, false, 154 false, user, context); 155 } 156 } 157 } 158 159 @Override isVisibleAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)160 public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, 161 @NonNull Context context) { 162 return VisibilityMixin.isVisible("config_showBrowserRole", true, user, context); 163 } 164 } 165