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