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.car.settings.privacy;
18 
19 import android.Manifest;
20 import android.car.drivingstate.CarUxRestrictions;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.hardware.SensorPrivacyManager;
24 import android.os.UserHandle;
25 import android.os.UserManager;
26 import android.permission.PermissionControllerManager;
27 
28 import androidx.preference.Preference;
29 
30 import com.android.car.settings.R;
31 import com.android.car.settings.common.FragmentController;
32 import com.android.car.settings.common.Logger;
33 import com.android.car.settings.common.PreferenceController;
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.settingslib.utils.StringUtil;
36 
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.concurrent.atomic.AtomicInteger;
42 
43 /**
44  * This controller displays the number of non-system apps that have access to microphone.
45  */
46 public class ManageMicPermissionsPreferenceController extends
47         PreferenceController<Preference> {
48     private static final Logger LOG = new Logger(
49             ManageMicPermissionsPreferenceController.class);
50 
51     private final AtomicInteger mLoadingInProgress = new AtomicInteger(0);
52     private int mNumTotal = 0;
53     private int mNumHasAccess = 0;
54 
55     private final SensorPrivacyManager mSensorPrivacyManager;
56     private final UserManager mUserManager;
57     private final SensorPrivacyManager.OnSensorPrivacyChangedListener mListener =
58             (sensor, enabled) -> refreshUi();
59 
ManageMicPermissionsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)60     public ManageMicPermissionsPreferenceController(Context context, String preferenceKey,
61             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
62         this(context, preferenceKey, fragmentController, uxRestrictions,
63                 SensorPrivacyManager.getInstance(context),
64                 context.getSystemService(UserManager.class));
65     }
66 
67     @VisibleForTesting
ManageMicPermissionsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, SensorPrivacyManager sensorPrivacyManager, UserManager userManager)68     ManageMicPermissionsPreferenceController(Context context, String preferenceKey,
69             FragmentController fragmentController, CarUxRestrictions uxRestrictions,
70             SensorPrivacyManager sensorPrivacyManager, UserManager userManager) {
71         super(context, preferenceKey, fragmentController, uxRestrictions);
72         mSensorPrivacyManager = sensorPrivacyManager;
73         mUserManager = userManager;
74     }
75 
76     @Override
getPreferenceType()77     protected Class<Preference> getPreferenceType() {
78         return Preference.class;
79     }
80 
81     @Override
onStartInternal()82     protected void onStartInternal() {
83         mSensorPrivacyManager.addSensorPrivacyListener(
84                 SensorPrivacyManager.Sensors.MICROPHONE, mListener);
85     }
86 
87     @Override
onStopInternal()88     protected void onStopInternal() {
89         mSensorPrivacyManager.removeSensorPrivacyListener(SensorPrivacyManager.Sensors.MICROPHONE,
90                 mListener);
91     }
92 
93     @Override
updateState(Preference preference)94     public void updateState(Preference preference) {
95         super.updateState(preference);
96         if (mSensorPrivacyManager.isSensorPrivacyEnabled(
97                 SensorPrivacyManager.Sensors.MICROPHONE)) {
98             getPreference().setSummary(getContext().getString(
99                     R.string.microphone_app_permission_summary_microphone_off));
100             return;
101         }
102         // Bail out if there's another loading request in progress.
103         if (mLoadingInProgress.get() != 0) {
104             return;
105         }
106         getPreference().setSummary(
107                 getContext().getString(R.string.microphone_settings_loading_app_permission_stats));
108 
109         mNumTotal = 0;
110         mNumHasAccess = 0;
111         // Retrieve a list of users inside the current user profile group.
112         List<UserHandle> users = mUserManager.getUserProfiles();
113         // need to asynchronously load 2 callbacks for each profile
114         mLoadingInProgress.set(2 * users.size());
115         for (UserHandle user : users) {
116             Context userContext = createPackageContextAsUser(getContext(), user);
117             if (userContext == null) {
118                 // remove the two callbacks that we would normally expect for this user
119                 if (mLoadingInProgress.addAndGet(-2) == 0) {
120                     setAppCounts(mNumTotal, mNumHasAccess);
121                 }
122                 continue;
123             }
124             PermissionControllerManager permController =
125                     userContext.getSystemService(PermissionControllerManager.class);
126             permController.countPermissionApps(
127                     Collections.singletonList(Manifest.permission.RECORD_AUDIO), /* flags= */ 0,
128                     (numApps) -> {
129                         mNumTotal += numApps;
130                         if (mLoadingInProgress.decrementAndGet() == 0) {
131                             setAppCounts(mNumTotal, mNumHasAccess);
132                         }
133                     }, /* handler= */ null);
134             permController.countPermissionApps(
135                     Collections.singletonList(Manifest.permission.RECORD_AUDIO),
136                     PermissionControllerManager.COUNT_ONLY_WHEN_GRANTED,
137                     (numApps) -> {
138                         mNumHasAccess += numApps;
139                         if (mLoadingInProgress.decrementAndGet() == 0) {
140                             setAppCounts(mNumTotal, mNumHasAccess);
141                         }
142                     }, /* handler= */  null);
143         }
144 
145     }
146 
setAppCounts(int numTotal, int numHasAccess)147     private void setAppCounts(int numTotal, int numHasAccess) {
148         Map<String, Object> arguments = new HashMap<>();
149         arguments.put("count", numHasAccess);
150         arguments.put("total_count", numTotal);
151         getPreference().setSummary(StringUtil.getIcuPluralsString(getContext(), arguments,
152                 R.string.microphone_app_permission_summary_microphone_on));
153     }
154 
155 
156     /**
157      * Returns a context created from the given context for the given user, or null if it fails.
158      */
createPackageContextAsUser(Context context, UserHandle userHandle)159     private Context createPackageContextAsUser(Context context, UserHandle userHandle) {
160         try {
161             return context.createPackageContextAsUser(
162                     context.getPackageName(), /* flags= */ 0, userHandle);
163         } catch (PackageManager.NameNotFoundException e) {
164             LOG.e("Failed to create user context", e);
165         }
166         return null;
167     }
168 }
169 
170