1 /* 2 * Copyright (C) 2021 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.systemui.car.privacy; 18 19 import static android.os.UserHandle.USER_SYSTEM; 20 21 import android.Manifest; 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.UserInfo; 26 import android.os.UserHandle; 27 import android.os.UserManager; 28 import android.permission.PermGroupUsage; 29 import android.permission.PermissionManager; 30 import android.util.Log; 31 32 import androidx.annotation.Nullable; 33 import androidx.annotation.WorkerThread; 34 35 import com.android.systemui.dagger.SysUISingleton; 36 import com.android.systemui.privacy.PrivacyDialog; 37 import com.android.systemui.privacy.PrivacyItemController; 38 import com.android.systemui.privacy.PrivacyType; 39 import com.android.systemui.privacy.logging.PrivacyLogger; 40 import com.android.systemui.settings.UserTracker; 41 42 import java.util.ArrayList; 43 import java.util.Comparator; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Optional; 47 import java.util.stream.Collectors; 48 49 import javax.inject.Inject; 50 51 /** 52 * Implementation of {@link com.android.systemui.car.privacy.MicQcPanel.MicPrivacyElementsProvider} 53 */ 54 @SysUISingleton 55 public class MicPrivacyElementsProviderImpl implements MicQcPanel.MicPrivacyElementsProvider { 56 private static final String TAG = "MicPrivacyElementsProvider"; 57 private static final String EMPTY_APP_NAME = ""; 58 59 private static final Map<String, PrivacyType> PERM_GROUP_TO_PRIVACY_TYPE_MAP = 60 Map.of(Manifest.permission_group.CAMERA, PrivacyType.TYPE_CAMERA, 61 Manifest.permission_group.MICROPHONE, PrivacyType.TYPE_MICROPHONE, 62 Manifest.permission_group.LOCATION, PrivacyType.TYPE_LOCATION); 63 64 65 private final PermissionManager mPermissionManager; 66 private final UserTracker mUserTracker; 67 private final PrivacyLogger mPrivacyLogger; 68 private final PackageManager mPackageManager; 69 private final PrivacyItemController mPrivacyItemController; 70 private final UserManager mUserManager; 71 72 @Inject MicPrivacyElementsProviderImpl( Context context, PermissionManager permissionManager, PackageManager packageManager, PrivacyItemController privacyItemController, UserTracker userTracker, PrivacyLogger privacyLogger)73 public MicPrivacyElementsProviderImpl( 74 Context context, 75 PermissionManager permissionManager, 76 PackageManager packageManager, 77 PrivacyItemController privacyItemController, 78 UserTracker userTracker, 79 PrivacyLogger privacyLogger) { 80 mPermissionManager = permissionManager; 81 mPackageManager = packageManager; 82 mPrivacyItemController = privacyItemController; 83 mUserTracker = userTracker; 84 mPrivacyLogger = privacyLogger; 85 86 mUserManager = context.getSystemService(UserManager.class); 87 } 88 89 @Override getPrivacyElements()90 public List<PrivacyDialog.PrivacyElement> getPrivacyElements() { 91 List<PrivacyDialog.PrivacyElement> elements = filterAndSort(createPrivacyElements()); 92 mPrivacyLogger.logShowDialogContents(elements); 93 return elements; 94 } 95 createPrivacyElements()96 private List<PrivacyDialog.PrivacyElement> createPrivacyElements() { 97 List<UserInfo> userInfos = mUserTracker.getUserProfiles(); 98 List<PermGroupUsage> permGroupUsages = getPermGroupUsages(); 99 mPrivacyLogger.logUnfilteredPermGroupUsage(permGroupUsages); 100 List<PrivacyDialog.PrivacyElement> items = new ArrayList<>(); 101 102 permGroupUsages.forEach(usage -> { 103 PrivacyType type = 104 verifyType(PERM_GROUP_TO_PRIVACY_TYPE_MAP.get(usage.getPermGroupName())); 105 if (type == null) return; 106 107 int userId = UserHandle.getUserId(usage.getUid()); 108 Optional<UserInfo> optionalUserInfo = userInfos.stream() 109 .filter(ui -> ui.id == userId) 110 .findFirst(); 111 if (!optionalUserInfo.isPresent() && userId != USER_SYSTEM) return; 112 113 UserInfo userInfo = 114 optionalUserInfo.orElseGet(() -> mUserManager.getUserInfo(USER_SYSTEM)); 115 116 String appName = usage.isPhoneCall() 117 ? EMPTY_APP_NAME 118 : getLabelForPackage(usage.getPackageName(), usage.getUid()); 119 120 items.add( 121 new PrivacyDialog.PrivacyElement( 122 type, 123 usage.getPackageName(), 124 userId, 125 appName, 126 usage.getAttribution(), 127 usage.getLastAccess(), 128 usage.isActive(), 129 userInfo.isManagedProfile(), 130 usage.isPhoneCall()) 131 ); 132 }); 133 134 return items; 135 } 136 getApplicationInfo(String packageName, int userId)137 private Optional<ApplicationInfo> getApplicationInfo(String packageName, int userId) { 138 ApplicationInfo applicationInfo; 139 try { 140 applicationInfo = mPackageManager 141 .getApplicationInfoAsUser(packageName, /* flags= */ 0, userId); 142 return Optional.of(applicationInfo); 143 } catch (PackageManager.NameNotFoundException e) { 144 Log.w(TAG, "Application info not found for: " + packageName); 145 return Optional.empty(); 146 } 147 } 148 149 @WorkerThread getPermGroupUsages()150 private List<PermGroupUsage> getPermGroupUsages() { 151 return mPermissionManager.getIndicatorAppOpUsageData(); 152 } 153 154 @WorkerThread getLabelForPackage(String packageName, int userId)155 private String getLabelForPackage(String packageName, int userId) { 156 Optional<ApplicationInfo> applicationInfo = getApplicationInfo(packageName, userId); 157 158 if (!applicationInfo.isPresent()) return packageName; 159 160 return (String) applicationInfo.get().loadLabel(mPackageManager); 161 } 162 163 /** 164 * If {@link PrivacyType} is available then returns the argument, or else returns {@code null}. 165 */ 166 @Nullable verifyType(PrivacyType type)167 private PrivacyType verifyType(PrivacyType type) { 168 if ((type == PrivacyType.TYPE_CAMERA || type == PrivacyType.TYPE_MICROPHONE) 169 && mPrivacyItemController.getMicCameraAvailable()) { 170 return type; 171 } else if (type == PrivacyType.TYPE_LOCATION 172 && mPrivacyItemController.getLocationAvailable()) { 173 return type; 174 } else { 175 return null; 176 } 177 } 178 filterAndSort( List<PrivacyDialog.PrivacyElement> list)179 private List<PrivacyDialog.PrivacyElement> filterAndSort( 180 List<PrivacyDialog.PrivacyElement> list) { 181 return list.stream() 182 .filter(it -> it.getType() == PrivacyType.TYPE_MICROPHONE) 183 .sorted(new PrivacyElementComparator()) 184 .collect(Collectors.toList()); 185 } 186 187 private static class PrivacyElementComparator 188 implements Comparator<PrivacyDialog.PrivacyElement> { 189 @Override compare(PrivacyDialog.PrivacyElement it1, PrivacyDialog.PrivacyElement it2)190 public int compare(PrivacyDialog.PrivacyElement it1, PrivacyDialog.PrivacyElement it2) { 191 if (it1.getActive() && !it2.getActive()) { 192 return 1; 193 } else if (!it1.getActive() && it2.getActive()) { 194 return -1; 195 } else { 196 return Long.compare(it1.getLastActiveTimestamp(), it2.getLastActiveTimestamp()); 197 } 198 } 199 } 200 } 201