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